Skip to content

Commit

Permalink
Replace quoted triples with triple/terms and reifications.
Browse files Browse the repository at this point in the history
  • Loading branch information
gkellogg committed Mar 26, 2024
1 parent c39bac1 commit b21da3a
Show file tree
Hide file tree
Showing 9 changed files with 521 additions and 143 deletions.
12 changes: 6 additions & 6 deletions etc/turtle.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ triples ::= subject predicateObjectList | blankNodePropertyList pr
predicateObjectList ::= verb objectList (';' (verb objectList)? )*
objectList ::= object annotation? ( ',' object annotation? )*
verb ::= predicate | 'a'
subject ::= iri | BlankNode | collection | quotedTriple
subject ::= iri | BlankNode | collection | reification
predicate ::= iri
object ::= iri | BlankNode | collection | blankNodePropertyList | literal | quotedTriple
object ::= iri | BlankNode | collection | blankNodePropertyList | literal | tripleTerm | reification
literal ::= RDFLiteral | NumericLiteral | BooleanLiteral
blankNodePropertyList ::= '[' predicateObjectList ']'
collection ::= '(' object* ')'
Expand All @@ -23,10 +23,10 @@ String ::= STRING_LITERAL_QUOTE | STRING_LITERAL_SINGLE_QUOTE
iri ::= IRIREF | PrefixedName
PrefixedName ::= PNAME_LN | PNAME_NS
BlankNode ::= BLANK_NODE_LABEL | ANON
quotedTriple ::= '<<' qtSubject predicate qtObject '>>'
qtSubject ::= iri | BlankNode | quotedTriple
qtObject ::= iri | BlankNode | literal | quotedTriple
annotation ::= '{|' predicateObjectList '|}'
reification ::= '<<' ((iri | BlankNode) '|' )? subject predicate object '>>'
tripleTerm ::= '<<(' subject predicate ttObject ')>>'
ttObject ::= iri | BlankNode | literal | tripleTerm
annotation ::= '{|' ( (iri | BlankNode) '|' )? predicateObjectList '|}'

@terminals

Expand Down
164 changes: 137 additions & 27 deletions lib/rdf/turtle/reader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@ class Reader < RDF::Reader

# String terminals
terminal(nil, %r(
[\(\),.;\[\]Aa]
<<\(|\)>>
| [\(\),.;\[\]Aa]
| \^\^
| \{\|
| \|\}
| true|false
| \|
| <<|>>
)x)

Expand Down Expand Up @@ -247,6 +249,11 @@ def bnode(value = nil)
end

protected
##
# Read objectList
#
# statement ::= directive | triples '.'
#
# @return [void]
def read_statement
prod(:statement, %w{.}) do
Expand All @@ -267,6 +274,11 @@ def read_statement
end
end

##
# Read directive
#
# directive ::= prefixID | base | sparqlPrefix | sparqlBase
#
# @return [void]
def read_directive
prod(:directive, %w{.}) do
Expand Down Expand Up @@ -314,6 +326,11 @@ def read_directive
end
end

##
# Read triples
#
# triples ::= subject predicateObjectList | blankNodePropertyList predicateObjectList?
#
# @return [Object] returns the last verb matched, or subject BNode on predicateObjectList?
def read_triples
prod(:triples, %w{.}) do
Expand All @@ -331,6 +348,11 @@ def read_triples
end
end

##
# Read predicateObjectList
#
# predicateObjectList ::= verb objectList (';' (verb objectList)? )*
#
# @param [RDF::Resource] subject
# @return [RDF::URI] the last matched verb
def read_predicateObjectList(subject)
Expand All @@ -348,14 +370,19 @@ def read_predicateObjectList(subject)
end
end

##
# Read objectList
#
# objectList ::= object annotation? ( ',' object annotation? )*
#
# @return [RDF::Term] the last matched subject
def read_objectList(subject, predicate)
prod(:objectList, %{,}) do
last_object = nil
while object = prod(:_objectList_2) {read_object(subject, predicate)}
last_object = object

# If object is followed by an annotation, read that and also emit an embedded triple.
# If object is followed by an annotation, read that and also emit the reification.
read_annotation(subject, predicate, object)

break unless @lexer.first === ','
Expand All @@ -365,26 +392,43 @@ def read_objectList(subject, predicate)
end
end

##
# Read verb
#
# verb ::= predicate | 'a'
#
# @return [RDF::URI]
def read_verb
if @cached_verb
v, @cached_verb = @cached_verb, nil
return v
end
error("read_verb", "Unexpected end of file") unless token = @lexer.first
case token.type || token.value
when 'a' then prod(:verb) {@lexer.shift && RDF.type}
else prod(:verb) {read_iri}
end
end

# subject ::= iri | BlankNode | collection | reification
#
# @return [RDF::Resource]
def read_subject
prod(:subject) do
read_iri ||
read_BlankNode ||
read_collection ||
read_quotedTriple ||
read_reification ||
error( "Expected subject", production: :subject, token: @lexer.first)
end
end

##
# Read object
#
# object ::= iri | BlankNode | collection | blankNodePropertyList
# | literal | tripleTerm | reification
#
# @return [void]
def read_object(subject = nil, predicate = nil)
prod(:object) do
Expand All @@ -393,64 +437,130 @@ def read_object(subject = nil, predicate = nil)
read_collection ||
read_blankNodePropertyList ||
read_literal ||
read_quotedTriple
read_tripleTerm ||
read_reification

add_statement(:object, RDF::Statement(subject, predicate, object)) if subject && predicate
object
end
end
end

# Read a quoted triple
# @return [RDF::Statement]
def read_quotedTriple
##
# Read reification
#
# reification ::= '<<' ((iri | BlankNode) '|' )? subject predicate object '>>'
#
# @return [RDF::Term]
def read_reification
return unless @options[:rdfstar]
if @lexer.first.value == '<<'
prod(:quotedTriple) do
prod(:reification) do
@lexer.shift # eat <<
subject = read_qtSubject || error("Failed to parse subject", production: :quotedTriple, token: @lexer.first)
predicate = read_verb || error("Failed to parse predicate", production: :quotedTriple, token: @lexer.first)
object = read_qtObject || error("Failed to parse object", production: :quotedTriple, token: @lexer.first)
# Optional identifier for reification
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)
elsif @lexer.first.value == '|'
error("Failed to parse reification identifier", production: :reification, token: @lexer.first)
else
# No ID read or missing separator
subject = id || read_subject || error("Failed to parse subject", production: :reification, 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)
unless @lexer.first.value == '>>'
error("Failed to end of embedded triple", production: :quotedTriple, token: @lexer.first)
error("Failed to end of triple occurence", production: :reification, token: @lexer.first)
end
@lexer.shift
statement = RDF::Statement(subject, predicate, object, quoted: true)
statement
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
end

# @return [RDF::Resource]
def read_qtSubject
prod(:qtSubject) do
read_iri ||
read_BlankNode ||
read_quotedTriple ||
error( "Expected embedded subject", production: :qtSubject, token: @lexer.first)
##
# Read triple term
#
# tripleTerm ::= '<<(' subject predicate ttObject ')>>'
#
# @return [RDF::Term]
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)
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 == ')>>'
error("Failed to end of triple term", production: :tripleTerm, token: @lexer.first)
end
@lexer.shift
statement = RDF::Statement(subject, predicate, object, tripleTerm: true)
statement
end
end
end

##
# Read ttObject
#
# ttObject::= iri | BlankNode | literal | tripleTerm
#
# @return [RDF::Term]
def read_qtObject(subject = nil, predicate = nil)
prod(:qtObject) do
def read_ttObject(subject = nil, predicate = nil)
prod(:ttObject) do
read_iri ||
read_BlankNode ||
read_literal ||
read_quotedTriple
read_tripleTerm
end
end

##
# 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 === '{|'
prod(:annotation, %(|})) do
@lexer.shift
# Optional identifier for reification
tt = RDF::Statement(subject, predicate, object, tripleTerm: true)
id = read_iri || read_BlankNode
if id && @lexer.first.value == '|'
@lexer.shift # eat |
# 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

## XXX replacement for rdf:reifies
statement = RDF::Statement(id, RDF.to_uri + 'reifies', tt)
add_statement('annotation', statement)

# Statement becomes subject for predicateObjectList
statement = RDF::Statement(subject, predicate, object, quoted: true)
read_predicateObjectList(statement) ||
# 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
Expand Down
Loading

0 comments on commit b21da3a

Please sign in to comment.