From d0d30ac56bb2bb7cd424039239378778623674ed Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sat, 20 Apr 2024 13:42:11 -0700 Subject: [PATCH] * Allow annotation* in objectList; reads 0 or many annotations for a given object. * Change "reification" to "reifier". --- etc/turtle.bnf | 8 +++---- lib/rdf/turtle/reader.rb | 51 ++++++++++++++++++++-------------------- spec/reader_spec.rb | 15 +++++++++++- 3 files changed, 43 insertions(+), 31 deletions(-) diff --git a/etc/turtle.bnf b/etc/turtle.bnf index aff26fc..24edb03 100644 --- a/etc/turtle.bnf +++ b/etc/turtle.bnf @@ -7,11 +7,11 @@ sparqlPrefix ::= "PREFIX" PNAME_NS IRIREF sparqlBase ::= "BASE" IRIREF triples ::= subject predicateObjectList | blankNodePropertyList predicateObjectList? predicateObjectList ::= verb objectList (';' (verb objectList)? )* -objectList ::= object annotation? ( ',' object annotation? )* +objectList ::= object annotation* ( ',' object annotation* )* verb ::= predicate | 'a' -subject ::= iri | BlankNode | collection | reification +subject ::= iri | BlankNode | collection | reifier predicate ::= iri -object ::= iri | BlankNode | collection | blankNodePropertyList | literal | tripleTerm | reification +object ::= iri | BlankNode | collection | blankNodePropertyList | literal | tripleTerm | reifier literal ::= RDFLiteral | NumericLiteral | BooleanLiteral blankNodePropertyList ::= '[' predicateObjectList ']' collection ::= '(' object* ')' @@ -23,7 +23,7 @@ String ::= STRING_LITERAL_QUOTE | STRING_LITERAL_SINGLE_QUOTE iri ::= IRIREF | PrefixedName PrefixedName ::= PNAME_LN | PNAME_NS BlankNode ::= BLANK_NODE_LABEL | ANON -reification ::= '<<' ((iri | BlankNode) '|' )? subject predicate object '>>' +reifier ::= '<<' ((iri | BlankNode) '|' )? subject predicate object '>>' tripleTerm ::= '<<(' subject predicate ttObject ')>>' ttObject ::= iri | BlankNode | literal | tripleTerm annotation ::= '{|' ( (iri | BlankNode) '|' )? predicateObjectList '|}' diff --git a/lib/rdf/turtle/reader.rb b/lib/rdf/turtle/reader.rb index 292478d..622e5c9 100644 --- a/lib/rdf/turtle/reader.rb +++ b/lib/rdf/turtle/reader.rb @@ -373,7 +373,7 @@ def read_predicateObjectList(subject) ## # Read objectList # - # objectList ::= object annotation? ( ',' object annotation? )* + # objectList ::= object annotation* ( ',' object annotation* )* # # @return [RDF::Term] the last matched subject def read_objectList(subject, predicate) @@ -382,8 +382,8 @@ def read_objectList(subject, predicate) while object = prod(:_objectList_2) {read_object(subject, predicate)} last_object = object - # If object is followed by an annotation, read that and also emit the reification. - read_annotation(subject, predicate, object) + # If object is followed by annotations, read them. + read_annotations(subject, predicate, object) break unless @lexer.first === ',' @lexer.shift while @lexer.first === ',' @@ -410,7 +410,7 @@ def read_verb end end - # subject ::= iri | BlankNode | collection | reification + # subject ::= iri | BlankNode | collection | reifier # # @return [RDF::Resource] def read_subject @@ -418,7 +418,7 @@ def read_subject read_iri || read_BlankNode || read_collection || - read_reification || + read_reifier || error( "Expected subject", production: :subject, token: @lexer.first) end end @@ -427,7 +427,7 @@ def read_subject # Read object # # object ::= iri | BlankNode | collection | blankNodePropertyList - # | literal | tripleTerm | reification + # | literal | tripleTerm | reifier # # @return [void] def read_object(subject = nil, predicate = nil) @@ -438,7 +438,7 @@ def read_object(subject = nil, predicate = nil) read_blankNodePropertyList || read_literal || read_tripleTerm || - read_reification + read_reifier add_statement(:object, RDF::Statement(subject, predicate, object)) if subject && predicate object @@ -447,32 +447,32 @@ def read_object(subject = nil, predicate = nil) end ## - # Read reification + # Read reifier # - # reification ::= '<<' ((iri | BlankNode) '|' )? subject predicate object '>>' + # reifier ::= '<<' ((iri | BlankNode) '|' )? subject predicate object '>>' # # @return [RDF::Term] - def read_reification + def read_reifier return unless @options[:rdfstar] if @lexer.first.value == '<<' - prod(:reification) do + prod(:reifier) do @lexer.shift # eat << - # Optional identifier for reification + # 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: :reification, token: @lexer.first) + subject = read_subject || error("Failed to parse subject", production: :reifier, token: @lexer.first) elsif @lexer.first.value == '|' - error("Failed to parse reification identifier", production: :reification, token: @lexer.first) + 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: :reification, token: @lexer.first) + 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: :reification, token: @lexer.first) - object = read_object || error("Failed to parse object", production: :reification, token: @lexer.first) + 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) unless @lexer.first.value == '>>' - error("Failed to end of triple occurence", production: :reification, token: @lexer.first) + error("Failed to end of triple occurence", production: :reifier, token: @lexer.first) end @lexer.shift tt = RDF::Statement(subject, predicate, object, tripleTerm: true) @@ -526,18 +526,18 @@ def read_ttObject(subject = nil, predicate = nil) ## # Read an annotation on a triple # - # annotation := '{|' ( (iri | BlankNode) '|' )? predicateObjectList '|}' - # - def read_annotation(subject, predicate, object) - error("Unexpected end of file", production: :annotation) unless token = @lexer.first - if token === '{|' + # annotation := ('{|' ( (iri | BlankNode) '|' )? predicateObjectList '|}')* + def read_annotations(subject, predicate, object) + error("Unexpected end of file", production: :annotation) unless @lexer.first + while @lexer.first === '{|' prod(:annotation, %(|})) do @lexer.shift - # Optional identifier for reification + # 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 @@ -555,8 +555,7 @@ def read_annotation(subject, predicate, object) id = bnode end - ## XXX replacement for rdf:reifies - statement = RDF::Statement(id, RDF.to_uri + 'reifies', tt) + statement = RDF::Statement(id, RDF.reifies, tt) add_statement('annotation', statement) # id becomes subject for predicateObjectList diff --git a/spec/reader_spec.rb b/spec/reader_spec.rb index b2cdc24..467bb2a 100644 --- a/spec/reader_spec.rb +++ b/spec/reader_spec.rb @@ -1149,7 +1149,20 @@ _:anno2 . _:anno2 "2020-12-31"^^ . ) - ] + ], + 'multiple annotations' => [ + %( + PREFIX : + :s :p :o {| :id1 | :r :z |} {| :id2 | :s :w |}. + ), + %( + . + <<( )>> . + . + <<( )>> . + . + ) + ], }.each do |name, (ttl, nt)| it name do expect_graph = RDF::Graph.new {|g| g << RDF::NTriples::Reader.new(nt, rdfstar: true)}