Skip to content

Commit 38d174b

Browse files
committed
Read literal direction from its:dir; requires rdf:version >= 1.2.
1 parent 6b89d4c commit 38d174b

File tree

8 files changed

+201
-10
lines changed

8 files changed

+201
-10
lines changed

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ group :development do
1717
gem 'sxp', github: "dryruby/sxp.rb", branch: "develop"
1818

1919
gem "equivalent-xml"
20+
gem 'ostruct', '~> 0.6.1'
2021
end
2122

2223
group :debug do

Gemfile-pure

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ group :development do
1717
gem 'sxp', github: "dryruby/sxp.rb", branch: "develop"
1818

1919
gem "equivalent-xml"
20+
gem 'ostruct', '~> 0.6.1'
2021
end
2122

2223
group :debug do

lib/rdf/rdfxml.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
module RDF
55
XML = Class.new(Vocabulary("http://www.w3.org/XML/1998/namespace"))
66

7+
# Internationalization Tag Set
8+
ITS = Class.new(Vocabulary("http://www.w3.org/2005/11/its"))
9+
710
##
811
# **`RDF::RDFXML`** is an RDF/XML extension for RDF.rb.
912
#

lib/rdf/rdfxml/reader.rb

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,16 @@ class EvaluationContext
3030
attr_accessor :language
3131
attr_accessor :graph
3232
attr_accessor :li_counter
33+
attr_accessor :direction
34+
attr_accessor :version # RDF Version, mirrored here
3335

34-
def initialize(base, element, graph, &cb)
36+
def initialize(base, element, graph, version: nil, &cb)
3537
# Initialize the evaluation context, [5.1]
3638
self.base = RDF::URI(base)
3739
@uri_mappings = {}
3840
@language = nil
41+
@direction = nil
42+
@version = version
3943
@graph = graph
4044
@li_counter = 0
4145

@@ -47,6 +51,8 @@ def clone(element, **options, &cb)
4751
new_ec = EvaluationContext.new(@base, nil, @graph)
4852
new_ec.uri_mappings = self.uri_mappings.clone
4953
new_ec.language = self.language
54+
new_ec.direction = self.direction
55+
new_ec.version = self.version
5056

5157
new_ec.extract_from_element(element, &cb) if element
5258

@@ -68,6 +74,12 @@ def extract_from_ancestors(el, &cb)
6874
# Extract Evaluation Context from an element
6975
def extract_from_element(el, &cb)
7076
self.language = el.language if el.language
77+
# Direction only used in RDF 1.2 or greater
78+
self.direction = el.direction if el.direction && self.version.to_s >= "1.2"
79+
# Direction requires the appropriate ITS version
80+
if el.direction && el.its_version != '2.0'
81+
# XXX raise error?
82+
end
7183
if b = el.base
7284
b = RDF::URI(b)
7385
self.base = b.absolute? ? b : self.base.join(b)
@@ -100,7 +112,7 @@ def base=(b)
100112
end
101113

102114
def inspect
103-
v = %w(base subject language).map {|a| "#{a}='#{self.send(a).nil? ? 'nil' : self.send(a)}'"}
115+
v = %w(base subject language direction).map {|a| "#{a}='#{self.send(a).nil? ? 'nil' : self.send(a)}'"}
104116
v << "uri_mappings[#{uri_mappings.keys.length}]"
105117
v.join(",")
106118
end
@@ -112,6 +124,11 @@ def inspect
112124
# @return [Module]
113125
attr_reader :implementation
114126

127+
# Version of RDF to use. Currently, only "1.2" is defined.
128+
# @!attribute [r] version
129+
# @return [String]
130+
attr_reader :version
131+
115132
##
116133
# Initializes the RDF/XML reader instance.
117134
#
@@ -196,7 +213,11 @@ def each_statement(&block)
196213
if rdf_nodes.size == 0
197214
# If none found, root element may be processed as an RDF Node
198215

199-
ec = EvaluationContext.new(base_uri, root, @graph) do |prefix, value|
216+
# Extract RDF version from root
217+
@version = root.version
218+
add_debug(root, "version: #{@version.inspect}")
219+
220+
ec = EvaluationContext.new(base_uri, root, @graph, version: @version) do |prefix, value|
200221
prefix(prefix, value)
201222
end
202223

@@ -206,8 +227,13 @@ def each_statement(&block)
206227
log_fatal "node must be a proxy not a #{node.class}" unless node.is_a?(@implementation::NodeProxy)
207228
# XXX Skip this element if it's contained within another rdf:RDF element
208229

209-
# Extract base, lang and namespaces from parents to create proper evaluation context
210-
ec = EvaluationContext.new(base_uri, nil, @graph)
230+
# Extract RDF version from node
231+
# XXX potentially, one node is processed with version "1.2" and others are parsed without a version.
232+
@version = node.version
233+
add_debug(root, "version: #{@version.inspect}")
234+
235+
# Extract base, lang, direction, version and namespaces from parents to create proper evaluation context
236+
ec = EvaluationContext.new(base_uri, nil, @graph, version: @version)
211237
ec.extract_from_ancestors(node) do |prefix, value|
212238
prefix(prefix, value)
213239
end
@@ -322,7 +348,11 @@ def nodeElement(el, ec)
322348
elsif is_propertyAttr?(attr)
323349
# Attributes not RDF.type
324350
predicate = attr.uri
325-
lit = RDF::Literal.new(attr.value, language: ec.language, validate: validate?, canonicalize: canonicalize?)
351+
lit = RDF::Literal.new(attr.value,
352+
language: ec.language,
353+
direction: (ec.direction if ec.language),
354+
validate: validate?,
355+
canonicalize: canonicalize?)
326356
add_triple(attr, subject, predicate, lit)
327357
end
328358
end
@@ -391,6 +421,8 @@ def nodeElement(el, ec)
391421
when "nodeID" then nodeID = attr.value
392422
else attrs[attr] = attr.value
393423
end
424+
elsif attr.namespace.href == RDF::ITS.to_s
425+
# No production. Direction already extracted
394426
else
395427
attrs[attr] = attr.value
396428
end
@@ -430,6 +462,7 @@ def nodeElement(el, ec)
430462
literal_opts[:datatype] = uri(datatype)
431463
else
432464
literal_opts[:language] = child_ec.language
465+
literal_opts[:direction] = (child_ec.direction if child_ec.language)
433466
end
434467
literal = RDF::Literal.new(child.inner_text, **literal_opts)
435468
add_triple(child, subject, predicate, literal)
@@ -527,7 +560,7 @@ def nodeElement(el, ec)
527560

528561
if attrs.empty? && resourceAttr.nil? && nodeID.nil?
529562

530-
literal = RDF::Literal.new("", language: ec.language)
563+
literal = RDF::Literal.new("", language: ec.language, direction: ec.direction)
531564
add_triple(child, subject, predicate, literal)
532565

533566
# Reification
@@ -552,7 +585,7 @@ def nodeElement(el, ec)
552585
next unless is_propertyAttr?(attr)
553586

554587
# Attributes not in RDF.type
555-
lit = RDF::Literal.new(val, language: child_ec.language)
588+
lit = RDF::Literal.new(val, language: child_ec.language, direction: child_ec.direction)
556589
add_triple(child, resource, attr.uri, lit)
557590
end
558591
end

lib/rdf/rdfxml/reader/nokogiri.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,30 @@ def base
5454
@base == false ? nil : @base
5555
end
5656

57+
##
58+
# Element direction
59+
#
60+
# @return [String]
61+
def direction
62+
attribute_with_ns("dir", RDF::ITS.to_s)
63+
end
64+
65+
##
66+
# ITS version
67+
#
68+
# @return [String]
69+
def its_version
70+
attribute_with_ns("version", RDF::ITS.to_s)
71+
end
72+
73+
##
74+
# RDF version
75+
#
76+
# @return [String]
77+
def version
78+
attribute_with_ns("version", RDF.to_uri.to_s)
79+
end
80+
5781
##
5882
# Monkey patch attribute_with_ns, to insure nil is returned for #null?
5983
#

lib/rdf/rdfxml/reader/rexml.rb

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,30 @@ def base
7474
@base == false ? nil : @base
7575
end
7676

77+
##
78+
# Element direction
79+
#
80+
# @return [String]
81+
def direction
82+
@node.attribute("dir", RDF::ITS.to_s)
83+
end
84+
85+
##
86+
# ITS version
87+
#
88+
# @return [String]
89+
def its_version
90+
@node.attribute("version", RDF::ITS.to_s)
91+
end
92+
93+
##
94+
# RDF version
95+
#
96+
# @return [String]
97+
def version
98+
@node.attribute("version", RDF.to_uri.to_s)
99+
end
100+
77101
def attribute_with_ns(name, namespace)
78102
@node.attribute(name, namespace)
79103
end
@@ -120,7 +144,11 @@ def namespaces
120144
end
121145

122146
def namespace
123-
Namespace.new(@node.namespace, @node.prefix) unless @node.namespace.to_s.empty?
147+
if @node.namespace
148+
Namespace.new(@node.namespace, @node.prefix)
149+
else
150+
Namespace.new(RDF::XML.to_s, 'rdf')
151+
end
124152
end
125153

126154
def add_namespace(prefix, uri)

rdf-rdfxml.gemspec

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,11 @@ Gem::Specification.new do |gem|
2828
gem.required_ruby_version = '>= 3.0'
2929
gem.requirements = []
3030

31+
gem.add_runtime_dependency 'builder', '~> 3.2', '>= 3.2.4'
3132
gem.add_runtime_dependency 'rdf', '~> 3.3'
3233
gem.add_runtime_dependency 'rdf-xsd', '~> 3.3'
3334
gem.add_runtime_dependency 'htmlentities', '~> 4.3'
34-
gem.add_runtime_dependency 'builder', '~> 3.2', '>= 3.2.4'
35+
gem.add_runtime_dependency 'logger', '~> 1.5'
3536

3637
gem.add_development_dependency 'getoptlong', '~> 0.2'
3738
gem.add_development_dependency 'json-ld', '>= 3.3'

spec/reader_spec.rb

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,106 @@
224224
expect(graph).to be_equivalent_graph(expected, logger: logger)
225225
end
226226

227+
context :direction do
228+
{
229+
"Language with no direction": {
230+
input: %(<?xml version="1.0" ?>
231+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
232+
xmlns:ex="http://example.org/"
233+
xml:lang="en"
234+
rdf:version="1.2">
235+
<rdf:Description rdf:about="http://example.org/joe" ex:name="bar" />
236+
</rdf:RDF>),
237+
expected: %(
238+
<http://example.org/joe> <http://example.org/name> "bar"@en .
239+
)
240+
},
241+
"Language with direction": {
242+
input: %(<?xml version="1.0" ?>
243+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
244+
xmlns:ex="http://example.org/"
245+
xmlns:its="http://www.w3.org/2005/11/its"
246+
its:version="2.0"
247+
its:dir="ltr"
248+
xml:lang="en"
249+
rdf:version="1.2">
250+
<rdf:Description rdf:about="http://example.org/joe" ex:name="bar" />
251+
</rdf:RDF>),
252+
expected: %(
253+
<http://example.org/joe> <http://example.org/name> "bar"@en--ltr .
254+
)
255+
},
256+
"Language with direction and no RDF version": {
257+
input: %(<?xml version="1.0" ?>
258+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
259+
xmlns:ex="http://example.org/"
260+
xmlns:its="http://www.w3.org/2005/11/its"
261+
its:version="2.0"
262+
its:dir="ltr"
263+
xml:lang="en">
264+
<rdf:Description rdf:about="http://example.org/joe" ex:name="bar" />
265+
</rdf:RDF>),
266+
expected: %(
267+
<http://example.org/joe> <http://example.org/name> "bar"@en .
268+
)
269+
},
270+
"Language with direction and no ITS version": {
271+
# FIXME: Should this fail, or raise an exception?
272+
input: %(<?xml version="1.0" ?>
273+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
274+
xmlns:ex="http://example.org/"
275+
xmlns:its="http://www.w3.org/2005/11/its"
276+
its:dir="ltr"
277+
xml:lang="en"
278+
rdf:version="1.2">
279+
<rdf:Description rdf:about="http://example.org/joe" ex:name="bar" />
280+
</rdf:RDF>),
281+
expected: %(
282+
<http://example.org/joe> <http://example.org/name> "bar"@en--ltr .
283+
)
284+
},
285+
"Language with direction on element directly": {
286+
input: %(<?xml version="1.0" ?>
287+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
288+
xmlns:ex="http://example.org/"
289+
xmlns:its="http://www.w3.org/2005/11/its"
290+
rdf:version="1.2">
291+
<rdf:Description rdf:about="http://example.org/joe">
292+
<ex:name xml:lang="en" its:version="2.0" its:dir="ltr" >bar</ex:name>
293+
</rdf:Description>
294+
</rdf:RDF>),
295+
expected: %(
296+
<http://example.org/joe> <http://example.org/name> "bar"@en--ltr .
297+
)
298+
},
299+
"Direction with no language": {
300+
input: %(<?xml version="1.0" ?>
301+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
302+
xmlns:ex="http://example.org/"
303+
xmlns:its="http://www.w3.org/2005/11/its"
304+
its:version="2.0"
305+
its:dir="ltr"
306+
rdf:version="1.2">
307+
<rdf:Description rdf:about="http://example.org/joe" ex:name="bar" />
308+
</rdf:RDF>),
309+
expected: %(
310+
<http://example.org/joe> <http://example.org/name> "bar" .
311+
)
312+
},
313+
}.each do |title, properties|
314+
it title do
315+
if properties[:exception]
316+
expect do
317+
parse(properties[:input], validate: true)
318+
end.to raise_error(RDF::ReaderError)
319+
else
320+
graph = parse(properties[:input], validate: true)
321+
expect(graph).to be_equivalent_graph(properties[:expected], logger: logger)
322+
end
323+
end
324+
end
325+
end
326+
227327
context :exceptions do
228328
it "should raise an error if rdf:aboutEach is used, as per the negative parser test rdfms-abouteach-error001 (rdf:aboutEach attribute)" do
229329
sampledoc = %q(<?xml version="1.0" ?>

0 commit comments

Comments
 (0)