Skip to content

Commit 9d29d61

Browse files
committed
Add triple term support with parseType="Triple".
Fixes #50.
1 parent 4430a5f commit 9d29d61

File tree

3 files changed

+244
-6
lines changed

3 files changed

+244
-6
lines changed

README.md

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,49 @@ Write a graph to a file:
4141
writer << graph
4242
end
4343

44+
### Provisional support for RDF 1.2
45+
46+
#### Version Announcement
47+
48+
Features added for RDF 1.2 require announcement using the `rdf:version` attribute.
49+
50+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
51+
xmlns:ex="http://www.example.org/"
52+
rdf:version="1.2">
53+
<rdf:Description rdf:about='http://example.org/s>
54+
<ex:p xml:lang="en">value</ex:p>
55+
</rdf:Description>
56+
</rdf:RDF>
57+
58+
#### Initial Text Direction
59+
60+
The Initial Text Direction of a languaged-tagged literal can be set using the `its:dir` attribute with a value of `"ltr"` or `"rtl"`. This should also be accompanied by a `its:version` attribute with the value `"2.0"`.
61+
62+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
63+
xmlns:ex="http://www.example.org/"
64+
rdf:version="1.2">
65+
<rdf:Description rdf:about='http://example.org/s>
66+
<ex:p xml:lang="en" its:dir="rtl" its:version="2.0">value</ex:p>
67+
</rdf:Description>
68+
</rdf:RDF>
69+
70+
#### parseType="Literal" (Triple Terms)
71+
72+
RDF 1.2 introduces [triple terms](https://www.w3.org/TR/rdf12-concepts/#dfn-triple-term) which allow the object of a triple to be a triple term. Note that this is diffrent from traditional RDF/XML reification.
73+
74+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
75+
xmlns:ex="http://example.org/stuff/1.0/"
76+
xml:base="http://example.org/triples/"
77+
rdf:version="1.2">
78+
<rdf:Description rdf:about="http://example.org/">
79+
<ex:prop rdf:parseType="Triple">
80+
<rdf:Description rdf:about="http://example.org/stuff/1.0/s">
81+
<ex:p rdf:resource="http://example.org/stuff/1.0/o" />
82+
</rdf:Description>
83+
</ex:prop>
84+
</rdf:Description>
85+
</rdf:RDF>
86+
4487
## Change Log
4588

4689
See [Release Notes on GitHub](https://github.com/ruby-rdf/rdf-rdfxml/releases)
@@ -104,7 +147,7 @@ see <https://unlicense.org/> or the accompanying {file:UNLICENSE} file.
104147
[Ruby]: https://ruby-lang.org/
105148
[RDF]: https://www.w3.org/RDF/
106149
[RDF.rb]: https://rubygems.org/gems/rdf
107-
[RDF/XML]: http://www.w3.org/TR/rdf-syntax-grammar/ "RDF/XML Syntax Specification"
150+
[RDF/XML]: http://www.w3.org/TR/rdf12-xml/ "RDF 1.2 XML Syntax"
108151
[YARD]: https://yardoc.org/
109152
[YARD-GS]: https://rubydoc.info/docs/yard/file/docs/GettingStarted.md
110153
[PDD]: https://unlicense.org/#unlicensing-contributions

lib/rdf/rdfxml/reader.rb

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -301,12 +301,12 @@ def add_error(node, message = "", &block)
301301
# @param [URI, BNode] subject the subject of the statement
302302
# @param [URI] predicate the predicate of the statement
303303
# @param [URI, BNode, Literal] object the object of the statement
304-
# @return [Statement] Added statement
304+
# @yield [RDF::Statement] if block given, otherwise to saved `@callback`
305305
# @raise [RDF::ReaderError] Checks parameter types and raises if they are incorrect if validating.
306306
def add_triple(node, subject, predicate, object)
307307
statement = RDF::Statement(subject, predicate, object)
308308
add_debug(node) {"statement: #{statement}"}
309-
@callback.call(statement)
309+
block_given? ? yield(statement) : @callback.call(statement)
310310
end
311311

312312
# XML nodeElement production
@@ -460,7 +460,7 @@ def nodeElement(el, ec)
460460
literal = RDF::Literal.new(child.inner_text, **literal_opts)
461461
add_triple(child, subject, predicate, literal)
462462
reify(id, child, subject, predicate, literal, ec) if id
463-
elsif parseType == "Resource"
463+
elsif parseType == 'Resource'
464464
# Production parseTypeResourcePropertyElt
465465
add_debug(child, "parseTypeResourcePropertyElt")
466466

@@ -492,7 +492,7 @@ def nodeElement(el, ec)
492492
prefix(prefix, value)
493493
end
494494
nodeElement(node, new_ec)
495-
elsif parseType == "Collection"
495+
elsif parseType == 'Collection'
496496
# Production parseTypeCollectionPropertyElt
497497
add_debug(child, "parseTypeCollectionPropertyElt")
498498

@@ -520,6 +520,39 @@ def nodeElement(el, ec)
520520
add_triple(child, n, RDF.first, object)
521521
add_triple(child, n, RDF.rest, o ? o : RDF.nil)
522522
end
523+
elsif parseType == 'Triple'
524+
# The contents describes a single triple.
525+
# Production parseTypeTriplePropertyElt
526+
add_debug(child, "parseTypeTriplePropertyElt")
527+
528+
unless child_ec.version.to_s >= "1.2"
529+
add_debug(child, "Triple Property with wrong version (#{child_ec.version})")
530+
next
531+
end
532+
533+
unless attrs.empty?
534+
add_error(child, "Triple Property with extra attributes") {attrs.inspect}
535+
end
536+
537+
# Create a triple from the content, and use as a triple term resource in a new statement
538+
triple = []
539+
saved_callback = @callback
540+
@callback = ->(t) {triple << t}
541+
element_nodes.each do |node|
542+
new_ec = child_ec.clone(nil) do |prefix, value|
543+
prefix(prefix, value)
544+
end
545+
nodeElement(node, new_ec)
546+
end
547+
@callback = saved_callback
548+
549+
if triple.length != 1
550+
add_error(child, "Triple property must encode a single triple") {attrs.inspect}
551+
next
552+
end
553+
triple = triple.first
554+
triple.options[:tripleTerm] = true
555+
add_triple(child, subject, predicate, triple)
523556
elsif parseType # Literal or Other
524557
# Production parseTypeResourcePropertyElt
525558
add_debug(child, parseType == "Literal" ? "parseTypeResourcePropertyElt" : "parseTypeOtherPropertyElt (#{parseType})")

spec/reader_spec.rb

Lines changed: 163 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,9 @@
122122
it "should be able to parse a simple single-triple document" do
123123
sampledoc = %q(<?xml version="1.0" ?>
124124
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
125-
xmlns:ex="http://www.example.org/" xml:lang="en" xml:base="http://www.example.org/foo">
125+
xmlns:ex="http://www.example.org/"
126+
xml:lang="en"
127+
xml:base="http://www.example.org/foo">
126128
<ex:Thing rdf:about="http://example.org/joe" ex:name="bar">
127129
<ex:belongsTo rdf:resource="http://tommorris.org/" />
128130
<ex:sampleText rdf:datatype="http://www.w3.org/2001/XMLSchema#string">foo</ex:sampleText>
@@ -435,6 +437,166 @@
435437
end
436438
end
437439

440+
context "Triple Terms" do
441+
{
442+
"ignored triple term": {
443+
input: %(<?xml version="1.0"?>
444+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
445+
xmlns:ex="http://example.org/stuff/1.0/"
446+
xml:base="http://example.org/triples/">
447+
<rdf:Description rdf:about="http://example.org/">
448+
<ex:prop rdf:parseType="Triple">
449+
<rdf:Description rdf:about="http://example.org/stuff/1.0/s">
450+
<ex:p rdf:resource="http://example.org/stuff/1.0/o" />
451+
</rdf:Description>
452+
</ex:prop>
453+
</rdf:Description>
454+
</rdf:RDF>),
455+
expected: %()
456+
},
457+
"Triple term having IRI subject": {
458+
input: %(<?xml version="1.0"?>
459+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
460+
xmlns:ex="http://example.org/stuff/1.0/"
461+
xml:base="http://example.org/triples/"
462+
rdf:version="1.2">
463+
<rdf:Description rdf:about="http://example.org/">
464+
<ex:prop rdf:parseType="Triple">
465+
<rdf:Description rdf:about="http://example.org/stuff/1.0/s">
466+
<ex:p rdf:resource="http://example.org/stuff/1.0/o" />
467+
</rdf:Description>
468+
</ex:prop>
469+
</rdf:Description>
470+
</rdf:RDF>),
471+
expected: %(
472+
<http://example.org/> <http://example.org/stuff/1.0/prop> <<(<http://example.org/stuff/1.0/s> <http://example.org/stuff/1.0/p> <http://example.org/stuff/1.0/o>)>> .
473+
)
474+
},
475+
"Triple term having BNode subject": {
476+
input: %(<?xml version="1.0"?>
477+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
478+
xmlns:ex="http://example.org/stuff/1.0/"
479+
xml:base="http://example.org/triples/"
480+
rdf:version="1.2">
481+
<rdf:Description rdf:about="http://example.org/">
482+
<ex:prop rdf:parseType="Triple">
483+
<rdf:Description>
484+
<ex:p rdf:resource="http://example.org/stuff/1.0/o" />
485+
</rdf:Description>
486+
</ex:prop>
487+
</rdf:Description>
488+
</rdf:RDF>),
489+
expected: %(
490+
<http://example.org/> <http://example.org/stuff/1.0/prop> <<(_:b1 <http://example.org/stuff/1.0/p> <http://example.org/stuff/1.0/o>)>> .
491+
)
492+
},
493+
"Triple term having a type": {
494+
input: %(<?xml version="1.0"?>
495+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
496+
xmlns:ex="http://example.org/stuff/1.0/"
497+
xml:base="http://example.org/triples/"
498+
rdf:version="1.2">
499+
<rdf:Description rdf:about="http://example.org/">
500+
<ex:prop rdf:parseType="Triple">
501+
<rdf:Description rdf:type="http://example.org/stuff/1.0/t" />
502+
</ex:prop>
503+
</rdf:Description>
504+
</rdf:RDF>),
505+
expected: %(
506+
<http://example.org/> <http://example.org/stuff/1.0/prop> <<(_:b1 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/stuff/1.0/t>)>> .
507+
)
508+
},
509+
"Triple term having BNode object": {
510+
input: %(<?xml version="1.0"?>
511+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
512+
xmlns:ex="http://example.org/stuff/1.0/"
513+
xml:base="http://example.org/triples/"
514+
rdf:version="1.2">
515+
<rdf:Description rdf:about="http://example.org/">
516+
<ex:prop rdf:parseType="Triple">
517+
<rdf:Description rdf:about="http://example.org/stuff/1.0/s">
518+
<ex:p rdf:nodeID="b1" />
519+
</rdf:Description>
520+
</ex:prop>
521+
</rdf:Description>
522+
</rdf:RDF>),
523+
expected: %(
524+
<http://example.org/> <http://example.org/stuff/1.0/prop> <<(<http://example.org/stuff/1.0/s> <http://example.org/stuff/1.0/p> _:b1)>> .
525+
)
526+
},
527+
"Recursive triple term": {
528+
input: %(<?xml version="1.0"?>
529+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
530+
xmlns:ex="http://example.org/stuff/1.0/"
531+
xml:base="http://example.org/triples/"
532+
rdf:version="1.2">
533+
<rdf:Description rdf:about="http://example.org/">
534+
<ex:prop rdf:parseType="Triple">
535+
<rdf:Description rdf:about="http://example.org/stuff/1.0/s">
536+
<ex:p rdf:parseType="Triple">
537+
<rdf:Description rdf:about="http://example.org/stuff/1.0/s2">
538+
<ex:p2 rdf:resource="http://example.org/stuff/1.0/o2" />
539+
</rdf:Description>
540+
</ex:p>
541+
</rdf:Description>
542+
</ex:prop>
543+
</rdf:Description>
544+
</rdf:RDF>),
545+
expected: %(
546+
<http://example.org/> <http://example.org/stuff/1.0/prop> <<(<http://example.org/stuff/1.0/s> <http://example.org/stuff/1.0/p> <<(<http://example.org/stuff/1.0/s2> <http://example.org/stuff/1.0/p2> <http://example.org/stuff/1.0/o2>)>>)>> .
547+
)
548+
},
549+
"Invalid triple term having no predicate or object": {
550+
input: %(<?xml version="1.0"?>
551+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
552+
xmlns:ex="http://example.org/stuff/1.0/"
553+
xml:base="http://example.org/triples/"
554+
rdf:version="1.2">
555+
<rdf:Description rdf:about="http://example.org/">
556+
<ex:prop rdf:parseType="Triple">
557+
<rdf:Description rdf:about="http://example.org/stuff/1.0/s">
558+
</rdf:Description>
559+
</ex:prop>
560+
</rdf:Description>
561+
</rdf:RDF>),
562+
exception: RDF::ReaderError
563+
},
564+
"Invalid triple term having multiple triples": {
565+
input: %(<?xml version="1.0"?>
566+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
567+
xmlns:ex="http://example.org/stuff/1.0/"
568+
xml:base="http://example.org/triples/"
569+
rdf:version="1.2">
570+
<rdf:Description rdf:about="http://example.org/">
571+
<ex:prop rdf:parseType="Triple">
572+
<rdf:Description rdf:about="http://example.org/stuff/1.0/s">
573+
<ex:p rdf:resource="http://example.org/stuff/1.0/o1" />
574+
<ex:p rdf:resource="http://example.org/stuff/1.0/o2" />
575+
</rdf:Description>
576+
</ex:prop>
577+
</rdf:Description>
578+
</rdf:RDF>),
579+
exception: RDF::ReaderError
580+
},
581+
}.each do |title, properties|
582+
it title do
583+
if properties[:exception]
584+
expect do
585+
parse(properties[:input], validate: true)
586+
end.to raise_error(RDF::ReaderError)
587+
else
588+
graph = begin
589+
parse(properties[:input], validate: true)
590+
rescue RDF::ReaderError => e
591+
fail("Parse failed:#{logger.to_s}")
592+
end
593+
egraph = RDF::Graph.new {|g| g << RDF::NTriples::Reader.new(properties[:expected], rdfstar: true)}
594+
expect(graph).to be_equivalent_graph(egraph, logger: logger)
595+
end
596+
end
597+
end
598+
end
599+
438600
context :reification do
439601
it "should be able to reify according to §2.17 of RDF/XML Syntax Specification" do
440602
sampledoc = %q(<?xml version="1.0"?>

0 commit comments

Comments
 (0)