From 7ac9eaeb963961adabc6fa2a17d9d27f0c2a6cc9 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sat, 3 Aug 2024 12:13:33 -0700 Subject: [PATCH] New style reified triples/annotations with postfix/prefix reifiers. --- README.md | 29 ++- etc/turtle.bnf | 19 +- lib/rdf/turtle/reader.rb | 141 ++++++------ script/tc | 2 +- spec/ntriples_spec.rb | 2 +- spec/reader_spec.rb | 478 +++++++++++++++++++++++++-------------- spec/star_spec.rb | 2 +- 7 files changed, 422 insertions(+), 251 deletions(-) diff --git a/README.md b/README.md index 0507b78..bd48a0f 100755 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Write a graph to a file: ## RDF 1.2 -Both reader and writer include provisional support for [RDF 1.2][] quoted triples. +Both reader and writer include provisional support for [RDF 1.2][] triple terms. Both reader and writer include provisional support for [RDF 1.2][] directional language-tagged strings, which are literals of type `rdf:dirLangString` having both a `language` and `direction`. @@ -46,15 +46,16 @@ Internally, an `RDF::Statement` is treated as another resource, along with `RDF: **Note: This feature is subject to change or elimination as the standards process progresses.** -### Serializing a Graph containing quoted triples +### Serializing a Graph containing reified triples require 'rdf/turtle' - statement = RDF::Statement(RDF::URI('bob'), RDF::Vocab::FOAF.age, RDF::Literal(23)) - graph = RDF::Graph.new << [statement, RDF::URI("ex:certainty"), RDF::Literal(0.9)] + triple = RDF::Statement(RDF::URI('bob'), RDF::Vocab::FOAF.age, RDF::Literal(23)) + graph = RDF::Graph.new << [RDF::URI('r'), RDF::URI("ex:certainty"), RDF::Literal(0.9)] + graph << RDF::Statement(RDF::URI('r'), RDF.reifies, triple) graph.dump(:ttl, validate: false, standard_prefixes: true) # => '<< foaf:age 23>> 9.0e-1 .' -### Reading a Graph containing quoted triples +### Reading a Graph containing reified triples By default, the Turtle reader will reject a document containing a subject resource. @@ -73,7 +74,7 @@ Readers support a boolean valued `rdfstar` option; only one statement is asserte graph = RDF::Graph.new do |graph| RDF::Turtle::Reader.new(ttl, rdfstar: true) {|reader| graph << reader} end - graph.count #=> 1 + graph.count #=> 2 ### Reading a Graph containing statement annotations @@ -88,7 +89,21 @@ where the subject is the the triple ending with that annotation. graph = RDF::Graph.new do |graph| RDF::Turtle::Reader.new(ttl) {|reader| graph << reader} end - # => RDF::ReaderError + # => RDF::Graph.new do |g| + triple = RDF::Statement.new(RDF::URI('bob'), RDF::FOAF.age, RDF::Literal(23)) + bn = RDF::Node.new('anno) + g << triple + g << RDF::Statement.new(bn, RDF.reifies, triple) + g << RDF::Statement.new(bn, RDF::URI("http://example.com/certainty"), RDF::Literal.new(9.0e-1)) + end + +Annotations can also have a reifier identifier + + ttl = %( + @prefix foaf: . + @prefix ex: . + foaf:age 23 ~ ex:anno {| ex:certainty 9.0e-1 |} . + ) Note that this requires the `rdfstar` option to be set. diff --git a/etc/turtle.bnf b/etc/turtle.bnf index 24edb03..4c870ed 100644 --- a/etc/turtle.bnf +++ b/etc/turtle.bnf @@ -5,13 +5,15 @@ prefixID ::= '@prefix' PNAME_NS IRIREF '.' base ::= '@base' IRIREF '.' sparqlPrefix ::= "PREFIX" PNAME_NS IRIREF sparqlBase ::= "BASE" IRIREF -triples ::= subject predicateObjectList | blankNodePropertyList predicateObjectList? +triples ::= subject predicateObjectList + | blankNodePropertyList predicateObjectList? + | reifiedTriple predicateObjectList? predicateObjectList ::= verb objectList (';' (verb objectList)? )* -objectList ::= object annotation* ( ',' object annotation* )* +objectList ::= object annotation ( ',' object annotation )* verb ::= predicate | 'a' -subject ::= iri | BlankNode | collection | reifier +subject ::= iri | BlankNode | collection predicate ::= iri -object ::= iri | BlankNode | collection | blankNodePropertyList | literal | tripleTerm | reifier +object ::= iri | BlankNode | collection | blankNodePropertyList | literal | tripleTerm | reifiedTriple literal ::= RDFLiteral | NumericLiteral | BooleanLiteral blankNodePropertyList ::= '[' predicateObjectList ']' collection ::= '(' object* ')' @@ -23,10 +25,12 @@ String ::= STRING_LITERAL_QUOTE | STRING_LITERAL_SINGLE_QUOTE iri ::= IRIREF | PrefixedName PrefixedName ::= PNAME_LN | PNAME_NS BlankNode ::= BLANK_NODE_LABEL | ANON -reifier ::= '<<' ((iri | BlankNode) '|' )? subject predicate object '>>' -tripleTerm ::= '<<(' subject predicate ttObject ')>>' +reifier ::= '~' (iri | BlankNode)? +reifiedTriple ::= '<<' ttSubject predicate ttObject reifier* '>>' +tripleTerm ::= '<<(' ttSubject predicate ttObject ')>>' +ttSubject ::= iri | BlankNode ttObject ::= iri | BlankNode | literal | tripleTerm -annotation ::= '{|' ( (iri | BlankNode) '|' )? predicateObjectList '|}' +annotation ::= (reifier | '{|' predicateObjectList '|}')* @terminals @@ -45,7 +49,6 @@ STRING_LITERAL_LONG_SINGLE_QUOTE ::= "'''" ( ( "'" | "''" )? ( [^'\] | ECHAR | STRING_LITERAL_LONG_QUOTE ::= '"""' ( ( '"' | '""' )? ( [^"\] | ECHAR | UCHAR ) )* '"""' UCHAR ::= ( '\u' HEX HEX HEX HEX ) | ( '\U' HEX HEX HEX HEX HEX HEX HEX HEX ) ECHAR ::= ('\' [tbnrf\"']) -NIL ::= '(' WS* ')' WS ::= #x20 | #x9 | #xD | #xA /* #x20=space #x9=character tabulation #xD=carriage return #xA=new line */ ANON ::= '[' WS* ']' PN_CHARS_BASE ::= ([A-Z] diff --git a/lib/rdf/turtle/reader.rb b/lib/rdf/turtle/reader.rb index 622e5c9..94e4ecc 100644 --- a/lib/rdf/turtle/reader.rb +++ b/lib/rdf/turtle/reader.rb @@ -27,7 +27,7 @@ class Reader < RDF::Reader # String terminals terminal(nil, %r( <<\(|\)>> - | [\(\),.;\[\]Aa] + | [\(\),.;~\[\]Aa] | \^\^ | \{\| | \|\} @@ -38,7 +38,7 @@ class Reader < RDF::Reader terminal(:PREFIX, PREFIX) terminal(:BASE, BASE) - terminal(:LANG_DIR, LANG_DIR) + terminal(:LANG_DIR, LANG_DIR) ## # Reader options @@ -340,6 +340,9 @@ def read_triples # blankNodePropertyList predicateObjectList? subject = read_blankNodePropertyList || error("Failed to parse blankNodePropertyList", production: :triples, token: @lexer.first) read_predicateObjectList(subject) || subject + when '<<' + subject = read_reifiedTriple || error("Failed to parse reifiedTriple", production: :triples, token: @lexer.first) + read_predicateObjectList(subject) || subject else # subject predicateObjectList subject = read_subject || error("Failed to parse subject", production: :triples, token: @lexer.first) @@ -383,7 +386,7 @@ def read_objectList(subject, predicate) last_object = object # If object is followed by annotations, read them. - read_annotations(subject, predicate, object) + read_annotation(subject, predicate, object) break unless @lexer.first === ',' @lexer.shift while @lexer.first === ',' @@ -410,7 +413,7 @@ def read_verb end end - # subject ::= iri | BlankNode | collection | reifier + # subject ::= iri | BlankNode | collection # # @return [RDF::Resource] def read_subject @@ -418,7 +421,6 @@ def read_subject read_iri || read_BlankNode || read_collection || - read_reifier || error( "Expected subject", production: :subject, token: @lexer.first) end end @@ -427,7 +429,7 @@ def read_subject # Read object # # object ::= iri | BlankNode | collection | blankNodePropertyList - # | literal | tripleTerm | reifier + # | literal | tripleTerm | reifiedTriple # # @return [void] def read_object(subject = nil, predicate = nil) @@ -438,7 +440,7 @@ def read_object(subject = nil, predicate = nil) read_blankNodePropertyList || read_literal || read_tripleTerm || - read_reifier + read_reifiedTriple add_statement(:object, RDF::Statement(subject, predicate, object)) if subject && predicate object @@ -447,38 +449,34 @@ def read_object(subject = nil, predicate = nil) end ## - # Read reifier + # Read reifiedTriple # - # reifier ::= '<<' ((iri | BlankNode) '|' )? subject predicate object '>>' + # reifiedTriple ::= '<<' ttSubject predicate ttObject reifier? '>>' # # @return [RDF::Term] - def read_reifier + def read_reifiedTriple return unless @options[:rdfstar] if @lexer.first.value == '<<' - prod(:reifier) do + prod(:reifiedTriple) do @lexer.shift # eat << - # Optional identifier for reifier - id = read_iri || read_BlankNode - if id && @lexer.first.value == '|' - @lexer.shift # eat | - subject = read_subject || error("Failed to parse subject", production: :reifier, token: @lexer.first) - elsif @lexer.first.value == '|' - error("Failed to parse reifier identifier", production: :reifier, token: @lexer.first) - else - # No ID read or missing separator - subject = id || read_subject || error("Failed to parse subject", production: :reifier, token: @lexer.first) - id = bnode - end - predicate = read_verb || error("Failed to parse predicate", production: :reifier, token: @lexer.first) - object = read_object || error("Failed to parse object", production: :reifier, token: @lexer.first) + subject = read_ttSubject || error("Failed to parse subject", production: :reifiedTriple, token: @lexer.first) + predicate = read_verb || error("Failed to parse predicate", production: :reifiedTriple, token: @lexer.first) + object = read_ttObject || error("Failed to parse object", production: :reifiedTriple, token: @lexer.first) + tt = RDF::Statement(subject, predicate, object, tripleTerm: true) + + # An optional reifier. If not specified it is a new blank node. + id = if @lexer.first.value == '~' + @lexer.shift + read_iri || read_BlankNode + end || bnode + + statement = RDF::Statement(id, RDF.to_uri + 'reifies', tt) + add_statement('reifiedTriple', statement) + unless @lexer.first.value == '>>' - error("Failed to end of triple occurence", production: :reifier, token: @lexer.first) + error("Failed to end of triple occurence", production: :reifiedTriple, token: @lexer.first) end @lexer.shift - tt = RDF::Statement(subject, predicate, object, tripleTerm: true) - ## XXX replacement for rdf:reifies - statement = RDF::Statement(id, RDF.to_uri + 'reifies', tt) - add_statement('tripleOccurence', statement) id end end @@ -487,15 +485,15 @@ def read_reifier ## # Read triple term # - # tripleTerm ::= '<<(' subject predicate ttObject ')>>' + # tripleTerm ::= '<<(' ttSubject predicate ttObject ')>>' # - # @return [RDF::Term] + # @return [RDF::Statement] def read_tripleTerm return unless @options[:rdfstar] if @lexer.first.value == '<<(' prod(:tripleTerm) do @lexer.shift # eat <<( - subject = read_subject || error("Failed to parse subject", production: :tripleTerm, token: @lexer.first) + subject = read_ttSubject || error("Failed to parse subject", production: :tripleTerm, token: @lexer.first) predicate = read_verb || error("Failed to parse predicate", production: :tripleTerm, token: @lexer.first) object = read_ttObject || error("Failed to parse object", production: :tripleTerm, token: @lexer.first) unless @lexer.first.value == ')>>' @@ -508,6 +506,19 @@ def read_tripleTerm end end + ## + # Read ttSubject + # + # ttSubject::= iri | BlankNode + # + # @return [RDF::Term] + def read_ttSubject + prod(:ttSubject) do + read_iri || + read_BlankNode + end + end + ## # Read ttObject # @@ -526,46 +537,42 @@ def read_ttObject(subject = nil, predicate = nil) ## # Read an annotation on a triple # - # annotation := ('{|' ( (iri | BlankNode) '|' )? predicateObjectList '|}')* - def read_annotations(subject, predicate, object) + # annotation := (reifier | '{|' predicateObjectList '|}')* + # + # The `reifier` becomes the identifier for a subsequent annotation block (if it exists). If there is no reifier, then a blank node is created. + def read_annotation(subject, predicate, object) error("Unexpected end of file", production: :annotation) unless @lexer.first - while @lexer.first === '{|' - prod(:annotation, %(|})) do - @lexer.shift - # Optional identifier for reifier - tt = RDF::Statement(subject, predicate, object, tripleTerm: true) - id = read_iri || read_BlankNode - if id && @lexer.first.value == '|' - @lexer.shift # eat | - progress("anotation", depth: options[:depth]) {"identifier: #{id.to_ntriples}"} - # Parsed annotation identifier - elsif @lexer.first.value == '|' - # expected annotation identifier - error("Failed to parse annotation identifier", production: :annotation, token: @lexer.first) - elsif id - error("Expected IRI to use as predicate in predicateObjectList", - production: :annotation, - token: id) if id.node? - # Remember any IRI that was read anticipating that it would be an identifier to use as the verb of a prdicateObjectList. - @cached_verb = id if id - # No identifier, use a new blank node - id = bnode - else - # No identifier, use a new blank node - id = bnode - end - statement = RDF::Statement(id, RDF.reifies, tt) - add_statement('annotation', statement) + tt = RDF::Statement(subject, predicate, object, tripleTerm: true) + id = nil - # id becomes subject for predicateObjectList - read_predicateObjectList(id) || - error("Expected predicateObjectList", production: :annotation, token: @lexer.first) - error("annotation", "Expected closing '|}'") unless @lexer.first === '|}' - @lexer.shift + while %w(~ {|).include? @lexer.first.to_s + if @lexer.first === '~' + prod(:annotation, %(~})) do + @lexer.shift # eat '~' + # Emit any pending reifiedTriple if there was no annotation block + add_statement('annotation', RDF::Statement(id, RDF.reifies, tt)) if id + id = read_iri || read_BlankNode || bnode + end + else + prod(:annotation, %({||})) do + @lexer.shift # eat '{|' + id ||= bnode + # Emit the reifiedTriple + add_statement('annotation', RDF::Statement(id, RDF.reifies, tt)) + + # id becomes subject for predicateObjectList + read_predicateObjectList(id) || + error("Expected predicateObjectList", production: :annotation, token: @lexer.first) + error("annotation", "Expected closing '|}'") unless @lexer.first === '|}' + @lexer.shift # eat '|}' + id = nil + end end end + # Emit any pending reifiedTriple if there was no annotation block + add_statement('annotation', RDF::Statement(id, RDF.reifies, tt)) if id end # @return [RDF::Literal] diff --git a/script/tc b/script/tc index b5ba0b0..a630931 100755 --- a/script/tc +++ b/script/tc @@ -80,7 +80,7 @@ def run_tc(tc, **options) else begin graph << reader - raise RDF::ReaderError, "quoted triple" if graph.statements.any? {|s| s.to_a.any?(&:statement?)} + raise RDF::ReaderError, "triple term" if graph.statements.any? {|s| s.to_a.any?(&:statement?)} STDERR.puts "Expected exception" if options[:verbose] result = "failed" rescue RDF::ReaderError diff --git a/spec/ntriples_spec.rb b/spec/ntriples_spec.rb index d0a2109..63ed3c6 100644 --- a/spec/ntriples_spec.rb +++ b/spec/ntriples_spec.rb @@ -31,7 +31,7 @@ else expect { graph << reader - raise RDF::ReaderError, "quoted triple" if graph.statements.any? {|s| s.to_a.any?(&:statement?)} + raise RDF::ReaderError, "triple term" if graph.statements.any? {|s| s.to_a.any?(&:statement?)} #expect(graph.dump(:ntriples).should produce("", t.debug) }.to raise_error RDF::ReaderError end diff --git a/spec/reader_spec.rb b/spec/reader_spec.rb index 467bb2a..0e35de1 100644 --- a/spec/reader_spec.rb +++ b/spec/reader_spec.rb @@ -927,167 +927,296 @@ end context "triple reifications" do - { - "subject-iii" => [ - %( - @prefix ex: . - <> ex:p ex:o . - ), - %( - _:anon <<( )>> . - _:anon . - ) - ], - "subject-iib": [ - %( - @prefix ex: . - <> ex:p ex:o . - ), - %( - _:anon <<( _:o1)>> . - _:anon . - ) - ], - "subject-iil": [ - %( - @prefix ex: . - <> ex:p ex:o . - ), - %( - _:anon <<( "o1")>> . - _:anon . - ) - ], - "subject-iia": [ - %( - @prefix ex: . - <> ex:p ex:o . - ), - %( - _:anon <<( _:o1)>> . - _:anon . - ) - ], - "subject-bii": [ - %( - @prefix ex: . - <<_:s1 ex:p1 ex:o1>> ex:p ex:o . - ), - %( - _:anon <<(_:s1 )>> . - _:anon . - ) - ], - "subject-bib": [ - %( - @prefix ex: . - <<_:s1 ex:p1 _:o1>> ex:p ex:o . - ), - %( - _:anon <<(_:s1 _:o1)>> . - _:anon . - ) - ], - "subject-bil": [ - %( - @prefix ex: . - <<_:s1 ex:p1 "o1">> ex:p ex:o . - ), - %( - _:anon <<(_:s1 "o1")>> . - _:anon . - ) - ], - "subject-bia": [ - %( - @prefix ex: . - <<_:s1 ex:p1 []>> ex:p ex:o . - ), - %( - _:anon <<(_:s1 _:o1)>> . - _:anon . - ) - ], - "object-iii": [ - %( - @prefix ex: . - ex:s ex:p <> . - ), - %( - _:anon <<( )>> . - _:anon . - ) - ], - "object-iib": [ - %( - @prefix ex: . - ex:s ex:p <> . - ), - %( - _:anon <<( _:o1)>> . - _:anon . - ) - ], - "object-iil": [ - %( - @prefix ex: . - ex:s ex:p <> . - ), - %( - _:anon <<( "o1")>> . - _:anon . - ) - ], - "object-iia": [ - %( - @prefix ex: . - ex:s ex:p <> . - ), - %( - _:anon <<( _:o1)>> . - _:anon . - ) - ], - "recursive-subject": [ - %( - @prefix ex: . - << - <> - ex:p1 ex:o1 >> - ex:p ex:o . - ), - %( - _:a1 <<(_:a2 )>> . - _:a2 <<( )>> . - _:a1 . - ) - ], - "IRI identifier": [ - %( - @prefix ex: . - ex:s ex:p <> . - ), - %( - . - <<( _:o1)>> . - ) - ], - "BNode identifier": [ - %( - @prefix ex: . - ex:s ex:p <<_:id | ex:s1 ex:p1 []>> . - ), - %( - _:id . - _:id <<( _:o1)>> . - ) - ], - }.each do |name, (ttl, nt)| - it name do - expect_graph = RDF::Graph.new {|g| g << RDF::NTriples::Reader.new(nt, rdfstar: true)} - g = parse(ttl, rdfstar: true, validate: true) - #require 'byebug'; byebug - expect(parse(ttl, rdfstar: true, validate: true)).to be_equivalent_graph(expect_graph, logger: @logger) + context "triple terms" do + { + "subject-iii" => [ + %( + @prefix ex: . + <<(ex:s1 ex:p1 ex:o1)>> ex:p ex:o . + ), + RDF::ReaderError + ], + "subject-iib": [ + %( + @prefix ex: . + <<(ex:s1 ex:p1 _:o1)>> ex:p ex:o . + ), + RDF::ReaderError + ], + "subject-iil": [ + %( + @prefix ex: . + <<(ex:s1 ex:p1 "o1")>> ex:p ex:o . + ), + RDF::ReaderError + ], + "subject-iia": [ + %( + @prefix ex: . + <<(ex:s1 ex:p1 [])>> ex:p ex:o . + ), + RDF::ReaderError + ], + "subject-bii": [ + %( + @prefix ex: . + <<_(:s1 ex:p1 ex:o1)>> ex:p ex:o . + ), + RDF::ReaderError + ], + "subject-bib": [ + %( + @prefix ex: . + <<(_:s1 ex:p1 _:o1)>> ex:p ex:o . + ), + RDF::ReaderError + ], + "subject-bil": [ + %( + @prefix ex: . + <<(_:s1 ex:p1 "o1")>> ex:p ex:o . + ), + RDF::ReaderError + ], + "subject-bia": [ + %( + @prefix ex: . + <<(_:s1 ex:p1 [])>> ex:p ex:o . + ), + RDF::ReaderError + ], + "object-iii": [ + %( + @prefix ex: . + @prefix rdf: . + ex:s rdf:reifies <<(ex:s1 ex:p1 ex:o1)>> . + ), + %( + <<( )>> . + ) + ], + "object-iib": [ + %( + @prefix ex: . + @prefix rdf: . + ex:s rdf:reifies <<(ex:s1 ex:p1 _:o1)>> . + ), + %( + <<( _:o1)>> . + ) + ], + "object-iil": [ + %( + @prefix ex: . + @prefix rdf: . + ex:s rdf:reifies <<(ex:s1 ex:p1 "o1")>> . + ), + %( + <<( "o1")>> . + ) + ], + "object-iia": [ + %( + @prefix ex: . + @prefix rdf: . + ex:s rdf:reifies <<(ex:s1 ex:p1 [])>> . + ), + %( + <<( _:o1)>> . + ) + ], + }.each do |name, (ttl, nt)| + it name do + if nt.is_a?(String) + expect_graph = RDF::Graph.new {|g| g << RDF::NTriples::Reader.new(nt, rdfstar: true)} + g = parse(ttl, rdfstar: true, validate: true) + expect(parse(ttl, rdfstar: true, validate: true)).to be_equivalent_graph(expect_graph, logger: @logger) + else + expect { parse(ttl, rdfstar: true, validate: true) }.to raise_error(nt) + end + end + end + end + + context "reified triples" do + { + "subject-iii" => [ + %( + @prefix ex: . + <> ex:p ex:o . + ), + %( + _:anon <<( )>> . + _:anon . + ) + ], + "subject-iib": [ + %( + @prefix ex: . + <> ex:p ex:o . + ), + %( + _:anon <<( _:o1)>> . + _:anon . + ) + ], + "subject-iil": [ + %( + @prefix ex: . + <> ex:p ex:o . + ), + %( + _:anon <<( "o1")>> . + _:anon . + ) + ], + "subject-iia": [ + %( + @prefix ex: . + <> ex:p ex:o . + ), + %( + _:anon <<( _:o1)>> . + _:anon . + ) + ], + "subject-bii": [ + %( + @prefix ex: . + <<_:s1 ex:p1 ex:o1>> ex:p ex:o . + ), + %( + _:anon <<(_:s1 )>> . + _:anon . + ) + ], + "subject-bib": [ + %( + @prefix ex: . + <<_:s1 ex:p1 _:o1>> ex:p ex:o . + ), + %( + _:anon <<(_:s1 _:o1)>> . + _:anon . + ) + ], + "subject-bil": [ + %( + @prefix ex: . + <<_:s1 ex:p1 "o1">> ex:p ex:o . + ), + %( + _:anon <<(_:s1 "o1")>> . + _:anon . + ) + ], + "subject-bia": [ + %( + @prefix ex: . + <<_:s1 ex:p1 []>> ex:p ex:o . + ), + %( + _:anon <<(_:s1 _:o1)>> . + _:anon . + ) + ], + "object-iii": [ + %( + @prefix ex: . + ex:s ex:p <> . + ), + %( + _:anon <<( )>> . + _:anon . + ) + ], + "object-iib": [ + %( + @prefix ex: . + ex:s ex:p <> . + ), + %( + _:anon <<( _:o1)>> . + _:anon . + ) + ], + "object-iil": [ + %( + @prefix ex: . + ex:s ex:p <> . + ), + %( + _:anon <<( "o1")>> . + _:anon . + ) + ], + "object-iia": [ + %( + @prefix ex: . + ex:s ex:p <> . + ), + %( + _:anon <<( _:o1)>> . + _:anon . + ) + ], + "recursive-subject": [ + %( + @prefix ex: . + << + <> + ex:p1 ex:o1 >> + ex:p ex:o . + ), + RDF::ReaderError + ], + "IRI identifier": [ + %( + @prefix ex: . + ex:s ex:p <> . + ), + %( + . + <<( _:o1)>> . + ) + ], + "BNode identifier": [ + %( + @prefix ex: . + ex:s ex:p <> . + ), + %( + _:id . + _:id <<( _:o1)>> . + ) + ], + "no identifier": [ + %( + @prefix ex: . + ex:s ex:p <> . + ), + %( + _:anon . + _:anon <<( _:o1)>> . + ) + ], + "multiple identifiers": [ + %( + @prefix ex: . + ex:s ex:p <> . + ), + RDF::ReaderError + ], + }.each do |name, (ttl, nt)| + it name do + if nt.is_a?(String) + expect_graph = RDF::Graph.new {|g| g << RDF::NTriples::Reader.new(nt, rdfstar: true)} + g = parse(ttl, rdfstar: true, validate: true) + expect(parse(ttl, rdfstar: true, validate: true)).to be_equivalent_graph(expect_graph, logger: @logger) + else + expect { parse(ttl, rdfstar: true, validate: true) }.to raise_error(nt) + end + end end end @@ -1107,7 +1236,7 @@ "IRI identifier": [ %( PREFIX : - :s :p :o {| :id | :r :z |} . + :s :p :o ~:id {| :r :z |} . ), %( . @@ -1118,7 +1247,7 @@ "BNode identifier": [ %( PREFIX : - :s :p :o {| _:id | :r :z |} . + :s :p :o ~ _:id {| :r :z |} . ), %( . @@ -1153,7 +1282,7 @@ 'multiple annotations' => [ %( PREFIX : - :s :p :o {| :id1 | :r :z |} {| :id2 | :s :w |}. + :s :p :o ~ :id1 {| :r :z |} ~ :id2 {| :s :w |}. ), %( . @@ -1163,10 +1292,27 @@ . ) ], + 'multiple reifiers' => [ + %( + PREFIX : + :s :p :o ~ :id1 ~:id2 ~ . + ), + %( + . + <<( )>> . + <<( )>> . + _:anon <<( )>> . + ) + ], }.each do |name, (ttl, nt)| it name do - expect_graph = RDF::Graph.new {|g| g << RDF::NTriples::Reader.new(nt, rdfstar: true)} - expect(parse(ttl, rdfstar: true, validate: true)).to be_equivalent_graph(expect_graph, logger: @logger) + if nt.is_a?(String) + expect_graph = RDF::Graph.new {|g| g << RDF::NTriples::Reader.new(nt, rdfstar: true)} + g = parse(ttl, rdfstar: true, validate: true) + expect(parse(ttl, rdfstar: true, validate: true)).to be_equivalent_graph(expect_graph, logger: @logger) + else + expect { parse(ttl, rdfstar: true, validate: true) }.to raise_error(nt) + end end end end diff --git a/spec/star_spec.rb b/spec/star_spec.rb index cd82f5b..a50d8de 100644 --- a/spec/star_spec.rb +++ b/spec/star_spec.rb @@ -44,7 +44,7 @@ else expect { graph << reader - raise RDF::ReaderError, "quoted triple" if graph.statements.any? {|s| s.to_a.any?(&:statement?)} + raise RDF::ReaderError, "triple term" if graph.statements.any? {|s| s.to_a.any?(&:statement?)} expect(graph.dump(:ntriples)).to produce("not this", t) }.to raise_error(RDF::ReaderError) end