rdf-n3 3.0.1 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +95 -51
- data/VERSION +1 -1
- data/lib/rdf/n3.rb +9 -6
- data/lib/rdf/n3/algebra.rb +125 -0
- data/lib/rdf/n3/algebra/formula.rb +185 -0
- data/lib/rdf/n3/algebra/list/append.rb +13 -0
- data/lib/rdf/n3/algebra/list/in.rb +9 -0
- data/lib/rdf/n3/algebra/list/last.rb +11 -0
- data/lib/rdf/n3/algebra/list/member.rb +7 -0
- data/lib/rdf/n3/algebra/log/conclusion.rb +9 -0
- data/lib/rdf/n3/algebra/log/conjunction.rb +9 -0
- data/lib/rdf/n3/algebra/log/equalTo.rb +7 -0
- data/lib/rdf/n3/algebra/log/implies.rb +77 -0
- data/lib/rdf/n3/algebra/log/includes.rb +13 -0
- data/lib/rdf/n3/algebra/log/notEqualTo.rb +7 -0
- data/lib/rdf/n3/algebra/log/notIncludes.rb +12 -0
- data/lib/rdf/n3/algebra/log/outputString.rb +7 -0
- data/lib/rdf/n3/algebra/math/absoluteValue.rb +9 -0
- data/lib/rdf/n3/algebra/math/difference.rb +9 -0
- data/lib/rdf/n3/algebra/math/equalTo.rb +9 -0
- data/lib/rdf/n3/algebra/math/exponentiation.rb +9 -0
- data/lib/rdf/n3/algebra/math/greaterThan.rb +9 -0
- data/lib/rdf/n3/algebra/math/integerQuotient.rb +9 -0
- data/lib/rdf/n3/algebra/math/lessThan.rb +9 -0
- data/lib/rdf/n3/algebra/math/memberCount.rb +9 -0
- data/lib/rdf/n3/algebra/math/negation.rb +9 -0
- data/lib/rdf/n3/algebra/math/notEqualTo.rb +9 -0
- data/lib/rdf/n3/algebra/math/notGreaterThan.rb +9 -0
- data/lib/rdf/n3/algebra/math/notLessThan.rb +9 -0
- data/lib/rdf/n3/algebra/math/product.rb +9 -0
- data/lib/rdf/n3/algebra/math/quotient.rb +9 -0
- data/lib/rdf/n3/algebra/math/remainder.rb +9 -0
- data/lib/rdf/n3/algebra/math/rounded.rb +9 -0
- data/lib/rdf/n3/algebra/math/sum.rb +9 -0
- data/lib/rdf/n3/algebra/str/concatenation.rb +9 -0
- data/lib/rdf/n3/algebra/str/contains.rb +9 -0
- data/lib/rdf/n3/algebra/str/containsIgnoringCase.rb +9 -0
- data/lib/rdf/n3/algebra/str/endsWith.rb +9 -0
- data/lib/rdf/n3/algebra/str/equalIgnoringCase.rb +9 -0
- data/lib/rdf/n3/algebra/str/format.rb +9 -0
- data/lib/rdf/n3/algebra/str/greaterThan.rb +9 -0
- data/lib/rdf/n3/algebra/str/lessThan.rb +9 -0
- data/lib/rdf/n3/algebra/str/matches.rb +9 -0
- data/lib/rdf/n3/algebra/str/notEqualIgnoringCase.rb +9 -0
- data/lib/rdf/n3/algebra/str/notGreaterThan.rb +9 -0
- data/lib/rdf/n3/algebra/str/notLessThan.rb +9 -0
- data/lib/rdf/n3/algebra/str/notMatches.rb +9 -0
- data/lib/rdf/n3/algebra/str/replace.rb +12 -0
- data/lib/rdf/n3/algebra/str/scrape.rb +9 -0
- data/lib/rdf/n3/algebra/str/startsWith.rb +56 -0
- data/lib/rdf/n3/extensions.rb +79 -0
- data/lib/rdf/n3/format.rb +1 -1
- data/lib/rdf/n3/reader.rb +181 -116
- data/lib/rdf/n3/reader/parser.rb +34 -32
- data/lib/rdf/n3/reasoner.rb +293 -0
- data/lib/rdf/n3/vocab.rb +8 -3
- data/lib/rdf/n3/writer.rb +358 -198
- metadata +109 -30
@@ -0,0 +1,9 @@
|
|
1
|
+
module RDF::N3::Algebra::Str
|
2
|
+
##
|
3
|
+
# The subject is a string; the object is is a regular expression in the perl, python style. It is true iff the string matches the regexp.
|
4
|
+
class Matches < SPARQL::Algebra::Operator::Binary
|
5
|
+
include RDF::Util::Logger
|
6
|
+
|
7
|
+
NAME = :strMatches
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module RDF::N3::Algebra::Str
|
2
|
+
##
|
3
|
+
# True iff the subject string is the NOT same as object string ignoring differences between upper and lower case.
|
4
|
+
class NotEqualIgnoringCase < SPARQL::Algebra::Operator::Binary
|
5
|
+
include RDF::Util::Logger
|
6
|
+
|
7
|
+
NAME = :strNotEqualIgnoringCase
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module RDF::N3::Algebra::Str
|
2
|
+
##
|
3
|
+
# True iff the string is NOT greater than the object when ordered according to Unicode(tm) code order.
|
4
|
+
class NotGreaterThan < SPARQL::Algebra::Operator::Binary
|
5
|
+
include RDF::Util::Logger
|
6
|
+
|
7
|
+
NAME = :strNotGreaterThan
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module RDF::N3::Algebra::Str
|
2
|
+
##
|
3
|
+
# The subject string; the object is is a regular expression in the perl, python style. It is true iff the string does NOT match the regexp.
|
4
|
+
class NotMatches < SPARQL::Algebra::Operator::Binary
|
5
|
+
include RDF::Util::Logger
|
6
|
+
|
7
|
+
NAME = :strNotMatches
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module RDF::N3::Algebra::Str
|
2
|
+
##
|
3
|
+
# A built-in for replacing characters or sub. takes a list of 3 strings; the first is the input data, the second the old and the third the new string. The object is calculated as the rplaced string.
|
4
|
+
#
|
5
|
+
# @example
|
6
|
+
# ("fofof bar", "of", "baz") string:replace "fbazbaz bar"
|
7
|
+
class Replace < SPARQL::Algebra::Operator::Binary
|
8
|
+
include RDF::Util::Logger
|
9
|
+
|
10
|
+
NAME = :strReplace
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module RDF::N3::Algebra::Str
|
2
|
+
##
|
3
|
+
# The subject is a list of two strings. The second string is a regular expression in the perl, python style. It must contain one group (a part in parentheses). If the first string in the list matches the regular expression, then the object is calculated as being thepart of the first string which matches the group.
|
4
|
+
class Scrape < SPARQL::Algebra::Operator::Binary
|
5
|
+
include RDF::Util::Logger
|
6
|
+
|
7
|
+
NAME = :strScrape
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module RDF::N3::Algebra::Str
|
2
|
+
##
|
3
|
+
# True iff the subject string starts with the object string.
|
4
|
+
class StartsWith < SPARQL::Algebra::Operator::Binary
|
5
|
+
include SPARQL::Algebra::Query
|
6
|
+
include SPARQL::Algebra::Update
|
7
|
+
include RDF::Enumerable
|
8
|
+
include RDF::Util::Logger
|
9
|
+
|
10
|
+
NAME = :strStartsWith
|
11
|
+
|
12
|
+
##
|
13
|
+
# The string:startsWith operator corresponds to the XPath fn:starts-with function. The arguments must be argument compatible otherwise an error is raised.
|
14
|
+
#
|
15
|
+
# For constant inputs that evaulate to true, the original solutions are returned.
|
16
|
+
#
|
17
|
+
# For constant inputs that evaluate to false, the empty solution set is returned. XXX
|
18
|
+
#
|
19
|
+
# Otherwise, for variable operands, it binds matching variables to the solution set.
|
20
|
+
#
|
21
|
+
# @param [RDF::Queryable] queryable
|
22
|
+
# @param [RDF::Query::Solutions] solutions
|
23
|
+
# @return [RDF::Query::Solutions]
|
24
|
+
# @raise [TypeError] if operands are not compatible
|
25
|
+
def execute(queryable, solutions:, **options)
|
26
|
+
log_debug {"strStartsWith #{operands.to_sxp}"}
|
27
|
+
@solutions = solutions.filter do |solution|
|
28
|
+
left, right = operands.map {|op| op.evaluate(solution.bindings)}
|
29
|
+
if !left.compatible?(right)
|
30
|
+
log_debug {"(strStartsWith incompatible operands #{[left, right].to_sxp})"}
|
31
|
+
false
|
32
|
+
elsif !left.to_s.start_with?(right.to_s)
|
33
|
+
log_debug {"(strStartsWith false #{[left, right].to_sxp})"}
|
34
|
+
false
|
35
|
+
else
|
36
|
+
log_debug {"(strStartsWith true #{[left, right].to_sxp})"}
|
37
|
+
true
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
##
|
43
|
+
# Does not yield statements.
|
44
|
+
#
|
45
|
+
# @yield [statement]
|
46
|
+
# each matching statement
|
47
|
+
# @yieldparam [RDF::Statement] solution
|
48
|
+
# @yieldreturn [void] ignored
|
49
|
+
def each(&block)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Graph name associated with this operation, using the name of the parent
|
53
|
+
# @return [RDF::Resource]
|
54
|
+
def graph_name; parent.graph_name; end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'rdf'
|
3
|
+
|
4
|
+
# Monkey-patch RDF::Enumerable to add `:existentials` and `:univerals` accessors
|
5
|
+
module RDF
|
6
|
+
module Enumerable
|
7
|
+
# Existential quantifiers defined on this enumerable
|
8
|
+
# @return [Array<RDF::Query::Variable>]
|
9
|
+
attr_accessor :existentials
|
10
|
+
|
11
|
+
# Universal quantifiers defined on this enumerable
|
12
|
+
# @return [Array<RDF::Query::Variable>]
|
13
|
+
attr_accessor :universals
|
14
|
+
|
15
|
+
##
|
16
|
+
# An enumerable contains another enumerable if every statement in other is a statement in self
|
17
|
+
#
|
18
|
+
# @param [RDF::Enumerable] other
|
19
|
+
# @return [Boolean]
|
20
|
+
def contain?(other)
|
21
|
+
other.all? {|statement| has_statement?(statement)}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Statement
|
26
|
+
# Override variable?
|
27
|
+
def variable?
|
28
|
+
to_a.any? {|term| !term.is_a?(RDF::Term) || term.variable?} || graph_name && graph_name.variable?
|
29
|
+
end
|
30
|
+
|
31
|
+
# Transform Statement into an SXP
|
32
|
+
# @return [Array]
|
33
|
+
def to_sxp_bin
|
34
|
+
[(variable? ? :pattern : :triple), subject, predicate, object, graph_name].compact
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Returns an S-Expression (SXP) representation
|
39
|
+
#
|
40
|
+
# @return [String]
|
41
|
+
def to_sxp
|
42
|
+
to_sxp_bin.to_sxp
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class Query::Solution
|
47
|
+
|
48
|
+
# Transform Statement into an SXP
|
49
|
+
# @return [Array]
|
50
|
+
def to_sxp_bin
|
51
|
+
[:solution] + bindings.map {|k, v| Query::Variable.new(k, v).to_sxp_bin}
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# Returns an S-Expression (SXP) representation
|
56
|
+
#
|
57
|
+
# @return [String]
|
58
|
+
def to_sxp
|
59
|
+
to_sxp_bin.to_sxp
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class Query::Variable
|
64
|
+
|
65
|
+
# Transform Statement into an SXP
|
66
|
+
# @return [Array]
|
67
|
+
def to_sxp_bin
|
68
|
+
[:var, name, (value.to_sxp_bin if value)].compact
|
69
|
+
end
|
70
|
+
|
71
|
+
##
|
72
|
+
# Returns an S-Expression (SXP) representation
|
73
|
+
#
|
74
|
+
# @return [String]
|
75
|
+
def to_sxp
|
76
|
+
to_sxp_bin.to_sxp
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/lib/rdf/n3/format.rb
CHANGED
@@ -15,7 +15,7 @@ module RDF::N3
|
|
15
15
|
# @example Obtaining serialization format file extension mappings
|
16
16
|
# RDF::Format.file_extensions #=> {n3: "text/n3"}
|
17
17
|
#
|
18
|
-
# @see
|
18
|
+
# @see https://www.w3.org/TR/rdf-testcases/#ntriples
|
19
19
|
class Format < RDF::Format
|
20
20
|
content_type 'text/n3', extension: :n3, aliases: %w(text/rdf+n3;q=0.2 application/rdf+n3;q=0.2)
|
21
21
|
content_encoding 'utf-8'
|
data/lib/rdf/n3/reader.rb
CHANGED
@@ -9,6 +9,8 @@ module RDF::N3
|
|
9
9
|
#
|
10
10
|
# Separate pass to create branch_table from n3-selectors.n3
|
11
11
|
#
|
12
|
+
# This implementation uses distinguished variables for both universal and explicit existential variables (defined with `@forSome`). Variables created from blank nodes are non-distinguished. Distinguished existential variables are tracked using `$`, internally, as the RDF `query_pattern` logic looses details of the variable definition in solutions, where the variable is represented using a symbol.
|
13
|
+
#
|
12
14
|
# @todo
|
13
15
|
# * Formulae as RDF::Query representations
|
14
16
|
# * Formula expansion similar to SPARQL Construct
|
@@ -20,9 +22,13 @@ module RDF::N3
|
|
20
22
|
include RDF::Util::Logger
|
21
23
|
include Meta
|
22
24
|
include Parser
|
23
|
-
|
25
|
+
|
24
26
|
N3_KEYWORDS = %w(a is of has keywords prefix base true false forSome forAny)
|
25
27
|
|
28
|
+
# The Blank nodes allocated for formula
|
29
|
+
# @return [Array<RDF::Node>]
|
30
|
+
attr_reader :formulae
|
31
|
+
|
26
32
|
##
|
27
33
|
# Initializes the N3 reader instance.
|
28
34
|
#
|
@@ -50,27 +56,40 @@ module RDF::N3
|
|
50
56
|
@input = input.respond_to?(:read) ? input : StringIO.new(input.to_s)
|
51
57
|
@lineno = 0
|
52
58
|
readline # Prime the pump
|
53
|
-
|
59
|
+
|
54
60
|
@memo = {}
|
55
61
|
@keyword_mode = false
|
56
|
-
@keywords = %w(a is of this has)
|
62
|
+
@keywords = %w(a is of this has).map(&:freeze).freeze
|
57
63
|
@productions = []
|
58
64
|
@prod_data = []
|
59
65
|
|
60
66
|
@branches = BRANCHES # Get from meta class
|
61
67
|
@regexps = REGEXPS # Get from meta class
|
62
68
|
|
63
|
-
@formulae = [] # Nodes used as
|
69
|
+
@formulae = [] # Nodes used as Formulae graph names
|
64
70
|
@formulae_nodes = {}
|
65
|
-
@
|
71
|
+
@label_uniquifier ||= "#{Random.new_seed}_000000"
|
72
|
+
@bnodes = {} # allocated bnodes by formula
|
73
|
+
@variables = {} # allocated variables by formula
|
66
74
|
|
67
75
|
if options[:base_uri]
|
68
|
-
|
76
|
+
log_info("@uri") { base_uri.inspect}
|
69
77
|
namespace(nil, uri("#{base_uri}#"))
|
70
78
|
end
|
71
|
-
|
72
|
-
|
73
|
-
|
79
|
+
|
80
|
+
# Prepopulate operator namespaces unless validating
|
81
|
+
unless validate?
|
82
|
+
namespace(:crypto, RDF::N3::Crypto)
|
83
|
+
namespace(:list, RDF::N3::List)
|
84
|
+
namespace(:log, RDF::N3::Log)
|
85
|
+
namespace(:math, RDF::N3::Math)
|
86
|
+
namespace(:rei, RDF::N3::Rei)
|
87
|
+
#namespace(:string, RDF::N3::String)
|
88
|
+
namespace(:time, RDF::N3::Time)
|
89
|
+
end
|
90
|
+
log_info("validate") {validate?.inspect}
|
91
|
+
log_info("canonicalize") {canonicalize?.inspect}
|
92
|
+
log_info("intern") {intern?.inspect}
|
74
93
|
|
75
94
|
if block_given?
|
76
95
|
case block.arity
|
@@ -87,7 +106,6 @@ module RDF::N3
|
|
87
106
|
|
88
107
|
##
|
89
108
|
# Iterates the given block for each RDF statement in the input.
|
90
|
-
#
|
91
109
|
# @yield [statement]
|
92
110
|
# @yieldparam [RDF::Statement] statement
|
93
111
|
# @return [void]
|
@@ -101,9 +119,9 @@ module RDF::N3
|
|
101
119
|
raise RDF::ReaderError, "Errors found during processing"
|
102
120
|
end
|
103
121
|
end
|
104
|
-
enum_for(:
|
122
|
+
enum_for(:each_statement)
|
105
123
|
end
|
106
|
-
|
124
|
+
|
107
125
|
##
|
108
126
|
# Iterates the given block for each RDF triple in the input.
|
109
127
|
#
|
@@ -112,29 +130,30 @@ module RDF::N3
|
|
112
130
|
# @yieldparam [RDF::URI] predicate
|
113
131
|
# @yieldparam [RDF::Value] object
|
114
132
|
# @return [void]
|
115
|
-
def each_triple
|
133
|
+
def each_triple
|
116
134
|
if block_given?
|
117
135
|
each_statement do |statement|
|
118
|
-
|
136
|
+
yield(*statement.to_triple)
|
119
137
|
end
|
120
138
|
end
|
121
139
|
enum_for(:each_triple)
|
122
140
|
end
|
123
|
-
|
141
|
+
|
124
142
|
protected
|
125
143
|
# Start of production
|
126
144
|
def onStart(prod)
|
127
145
|
handler = "#{prod}Start".to_sym
|
128
|
-
|
146
|
+
log_info("#{handler}(#{respond_to?(handler, true)})", prod, depth: depth)
|
129
147
|
@productions << prod
|
130
148
|
send(handler, prod) if respond_to?(handler, true)
|
149
|
+
|
131
150
|
end
|
132
151
|
|
133
152
|
# End of production
|
134
153
|
def onFinish
|
135
154
|
prod = @productions.pop()
|
136
155
|
handler = "#{prod}Finish".to_sym
|
137
|
-
|
156
|
+
log_info("#{handler}(#{respond_to?(handler, true)})", depth: depth) {"#{prod}: #{@prod_data.last.inspect}"}
|
138
157
|
send(handler) if respond_to?(handler, true)
|
139
158
|
end
|
140
159
|
|
@@ -143,22 +162,22 @@ module RDF::N3
|
|
143
162
|
unless @productions.empty?
|
144
163
|
parentProd = @productions.last
|
145
164
|
handler = "#{parentProd}Token".to_sym
|
146
|
-
|
165
|
+
log_info("#{handler}(#{respond_to?(handler, true)})", depth: depth) {"#{prod}, #{tok}: #{@prod_data.last.inspect}"}
|
147
166
|
send(handler, prod, tok) if respond_to?(handler, true)
|
148
167
|
else
|
149
168
|
error("Token has no parent production")
|
150
169
|
end
|
151
170
|
end
|
152
|
-
|
171
|
+
|
153
172
|
def booleanToken(prod, tok)
|
154
173
|
lit = RDF::Literal.new(tok.delete("@"), datatype: RDF::XSD.boolean, validate: validate?, canonicalize: canonicalize?)
|
155
174
|
add_prod_data(:literal, lit)
|
156
175
|
end
|
157
|
-
|
176
|
+
|
158
177
|
def declarationStart(prod)
|
159
178
|
@prod_data << {}
|
160
179
|
end
|
161
|
-
|
180
|
+
|
162
181
|
def declarationToken(prod, tok)
|
163
182
|
case prod
|
164
183
|
when "@prefix", "@base", "@keywords"
|
@@ -182,17 +201,17 @@ module RDF::N3
|
|
182
201
|
# Base, set or update document URI
|
183
202
|
uri = decl[:explicituri]
|
184
203
|
options[:base_uri] = process_uri(uri)
|
185
|
-
|
204
|
+
|
186
205
|
# The empty prefix "" is by default , bound to "#" -- the local namespace of the file.
|
187
206
|
# The parser behaves as though there were a
|
188
207
|
# @prefix : <#>.
|
189
208
|
# just before the file.
|
190
209
|
# This means that <#foo> can be written :foo and using @keywords one can reduce that to foo.
|
191
|
-
|
210
|
+
|
192
211
|
namespace(nil, uri.match(/[\/\#]$/) ? base_uri : process_uri("#{uri}#"))
|
193
|
-
log_debug("declarationFinish[@base]") {"@base=#{base_uri}"}
|
212
|
+
log_debug("declarationFinish[@base]", depth: depth) {"@base=#{base_uri}"}
|
194
213
|
when "@keywords"
|
195
|
-
log_debug("declarationFinish[@keywords]") {@keywords.inspect}
|
214
|
+
log_debug("declarationFinish[@keywords]", depth: depth) {@keywords.inspect}
|
196
215
|
# Keywords are handled in tokenizer and maintained in @keywords array
|
197
216
|
if (@keywords & N3_KEYWORDS) != @keywords
|
198
217
|
error("Undefined keywords used: #{(@keywords - N3_KEYWORDS).to_sentence}") if validate?
|
@@ -202,17 +221,17 @@ module RDF::N3
|
|
202
221
|
error("declarationFinish: FIXME #{decl.inspect}")
|
203
222
|
end
|
204
223
|
end
|
205
|
-
|
224
|
+
|
206
225
|
# Document start, instantiate
|
207
226
|
def documentStart(prod)
|
208
227
|
@formulae.push(nil)
|
209
228
|
@prod_data << {}
|
210
229
|
end
|
211
|
-
|
230
|
+
|
212
231
|
def dtlangToken(prod, tok)
|
213
232
|
add_prod_data(:langcode, tok) if prod == "langcode"
|
214
233
|
end
|
215
|
-
|
234
|
+
|
216
235
|
def existentialStart(prod)
|
217
236
|
@prod_data << {}
|
218
237
|
end
|
@@ -227,22 +246,23 @@ module RDF::N3
|
|
227
246
|
pd = @prod_data.pop
|
228
247
|
forSome = Array(pd[:symbol])
|
229
248
|
forSome.each do |term|
|
230
|
-
|
249
|
+
var = univar(term, existential: true)
|
250
|
+
add_var_to_formula(@formulae.last, term, var)
|
231
251
|
end
|
232
252
|
end
|
233
|
-
|
253
|
+
|
234
254
|
def expressionStart(prod)
|
235
255
|
@prod_data << {}
|
236
256
|
end
|
237
|
-
|
257
|
+
|
238
258
|
# Process path items, and push on the last object for parent processing
|
239
259
|
def expressionFinish
|
240
260
|
expression = @prod_data.pop
|
241
|
-
|
261
|
+
|
242
262
|
# If we're in teh middle of a pathtail, append
|
243
263
|
if @prod_data.last[:pathtail] && expression[:pathitem] && expression[:pathtail]
|
244
264
|
path_list = [expression[:pathitem]] + expression[:pathtail]
|
245
|
-
log_debug("expressionFinish(pathtail)") {"set pathtail to #{path_list.inspect}"}
|
265
|
+
log_debug("expressionFinish(pathtail)", depth: depth) {"set pathtail to #{path_list.inspect}"}
|
246
266
|
@prod_data.last[:pathtail] = path_list
|
247
267
|
|
248
268
|
dir_list = [expression[:direction]] if expression[:direction]
|
@@ -256,31 +276,31 @@ module RDF::N3
|
|
256
276
|
error("expressionFinish: FIXME #{expression.inspect}")
|
257
277
|
end
|
258
278
|
end
|
259
|
-
|
279
|
+
|
260
280
|
def literalStart(prod)
|
261
281
|
@prod_data << {}
|
262
282
|
end
|
263
|
-
|
283
|
+
|
264
284
|
def literalToken(prod, tok)
|
265
285
|
tok = tok[0, 3] == '"""' ? tok[3..-4] : tok[1..-2]
|
266
286
|
add_prod_data(:string, tok)
|
267
287
|
end
|
268
|
-
|
288
|
+
|
269
289
|
def literalFinish
|
270
290
|
lit = @prod_data.pop
|
271
291
|
content = RDF::NTriples.unescape(lit[:string])
|
272
292
|
language = lit[:langcode] if lit[:langcode]
|
273
293
|
language = language.downcase if language && canonicalize?
|
274
294
|
datatype = lit[:symbol]
|
275
|
-
|
295
|
+
|
276
296
|
lit = RDF::Literal.new(content, language: language, datatype: datatype, validate: validate?, canonicalize: canonicalize?)
|
277
297
|
add_prod_data(:literal, lit)
|
278
298
|
end
|
279
|
-
|
299
|
+
|
280
300
|
def objectStart(prod)
|
281
301
|
@prod_data << {}
|
282
302
|
end
|
283
|
-
|
303
|
+
|
284
304
|
def objectFinish
|
285
305
|
object = @prod_data.pop
|
286
306
|
if object[:expression]
|
@@ -289,11 +309,11 @@ module RDF::N3
|
|
289
309
|
error("objectFinish: FIXME #{object.inspect}")
|
290
310
|
end
|
291
311
|
end
|
292
|
-
|
312
|
+
|
293
313
|
def pathitemStart(prod)
|
294
314
|
@prod_data << {}
|
295
315
|
end
|
296
|
-
|
316
|
+
|
297
317
|
def pathitemToken(prod, tok)
|
298
318
|
case prod
|
299
319
|
when "numericliteral"
|
@@ -303,15 +323,19 @@ module RDF::N3
|
|
303
323
|
when /\./ then RDF::XSD.decimal
|
304
324
|
else RDF::XSD.integer
|
305
325
|
end
|
306
|
-
|
326
|
+
|
307
327
|
lit = RDF::Literal.new(nl, datatype: datatype, validate: validate?, canonicalize: canonicalize?)
|
308
328
|
add_prod_data(:literal, lit)
|
309
329
|
when "quickvariable"
|
310
330
|
# There is a also a shorthand syntax ?x which is the same as :x except that it implies that x is
|
311
331
|
# universally quantified not in the formula but in its parent formula
|
312
332
|
uri = process_qname(tok.sub('?', ':'))
|
313
|
-
|
314
|
-
|
333
|
+
var = uri.variable? ? uri : univar(uri)
|
334
|
+
add_var_to_formula(@formulae[-2], uri, var)
|
335
|
+
# Also add var to this formula
|
336
|
+
add_var_to_formula(@formulae.last, uri, var)
|
337
|
+
|
338
|
+
add_prod_data(:symbol, var)
|
315
339
|
when "boolean"
|
316
340
|
lit = RDF::Literal.new(tok.delete("@"), datatype: RDF::XSD.boolean, validate: validate?, canonicalize: canonicalize?)
|
317
341
|
add_prod_data(:literal, lit)
|
@@ -324,13 +348,18 @@ module RDF::N3
|
|
324
348
|
add_prod_data(:symbol, symbol)
|
325
349
|
when "{"
|
326
350
|
# A new formula, push on a node as a named graph
|
327
|
-
node = RDF::Node.new
|
351
|
+
node = RDF::Node.new(".form_#{unique_label}")
|
328
352
|
@formulae << node
|
329
353
|
@formulae_nodes[node] = true
|
354
|
+
|
355
|
+
# Promote variables defined on the earlier formula to this formula
|
356
|
+
@variables[node] = {}
|
357
|
+
@variables[@formulae[-2]].each do |name, var|
|
358
|
+
@variables[node][name] = var
|
359
|
+
end
|
330
360
|
when "}"
|
331
|
-
# Pop off the formula
|
361
|
+
# Pop off the formula
|
332
362
|
formula = @formulae.pop
|
333
|
-
@variables.delete_if {|k, v| v[:formula] == formula}
|
334
363
|
add_prod_data(:symbol, formula)
|
335
364
|
else
|
336
365
|
error("pathitemToken(#{prod}, #{tok}): FIXME")
|
@@ -349,11 +378,11 @@ module RDF::N3
|
|
349
378
|
error("pathitemFinish: FIXME #{pathitem.inspect}")
|
350
379
|
end
|
351
380
|
end
|
352
|
-
|
381
|
+
|
353
382
|
def pathlistStart(prod)
|
354
383
|
@prod_data << {pathlist: []}
|
355
384
|
end
|
356
|
-
|
385
|
+
|
357
386
|
def pathlistFinish
|
358
387
|
pathlist = @prod_data.pop
|
359
388
|
# Flatten propertylist into an array
|
@@ -361,11 +390,11 @@ module RDF::N3
|
|
361
390
|
add_prod_data(:pathlist, expr) if expr
|
362
391
|
add_prod_data(:pathlist, pathlist[:pathlist]) if pathlist[:pathlist]
|
363
392
|
end
|
364
|
-
|
393
|
+
|
365
394
|
def pathtailStart(prod)
|
366
395
|
@prod_data << {pathtail: []}
|
367
396
|
end
|
368
|
-
|
397
|
+
|
369
398
|
def pathtailToken(prod, tok)
|
370
399
|
case tok
|
371
400
|
when "!", "."
|
@@ -374,46 +403,46 @@ module RDF::N3
|
|
374
403
|
add_prod_data(:direction, :reverse)
|
375
404
|
end
|
376
405
|
end
|
377
|
-
|
406
|
+
|
378
407
|
def pathtailFinish
|
379
408
|
pathtail = @prod_data.pop
|
380
409
|
add_prod_data(:pathtail, pathtail[:pathtail])
|
381
410
|
add_prod_data(:direction, pathtail[:direction]) if pathtail[:direction]
|
382
411
|
add_prod_data(:directiontail, pathtail[:directiontail]) if pathtail[:directiontail]
|
383
412
|
end
|
384
|
-
|
413
|
+
|
385
414
|
def propertylistStart(prod)
|
386
415
|
@prod_data << {}
|
387
416
|
end
|
388
|
-
|
417
|
+
|
389
418
|
def propertylistFinish
|
390
419
|
propertylist = @prod_data.pop
|
391
420
|
# Flatten propertylist into an array
|
392
421
|
ary = [propertylist, propertylist.delete(:propertylist)].flatten.compact
|
393
422
|
@prod_data.last[:propertylist] = ary
|
394
423
|
end
|
395
|
-
|
424
|
+
|
396
425
|
def simpleStatementStart(prod)
|
397
426
|
@prod_data << {}
|
398
427
|
end
|
399
|
-
|
428
|
+
|
400
429
|
# Completion of Simple Statement, all productions include :subject, and :propertyList
|
401
430
|
def simpleStatementFinish
|
402
431
|
statement = @prod_data.pop
|
403
|
-
|
432
|
+
|
404
433
|
subject = statement[:subject]
|
405
434
|
properties = Array(statement[:propertylist])
|
406
435
|
properties.each do |p|
|
407
436
|
predicate = p[:verb]
|
408
437
|
next unless predicate
|
409
|
-
log_debug("simpleStatementFinish(pred)") {predicate.to_s}
|
438
|
+
log_debug("simpleStatementFinish(pred)", depth: depth) {predicate.to_s}
|
410
439
|
error(%(Illegal statment: "#{predicate}" missing object)) unless p.has_key?(:object)
|
411
440
|
objects = Array(p[:object])
|
412
441
|
objects.each do |object|
|
413
442
|
if p[:invert]
|
414
|
-
|
443
|
+
add_statement("simpleStatementFinish", object, predicate, subject)
|
415
444
|
else
|
416
|
-
|
445
|
+
add_statement("simpleStatementFinish", subject, predicate, object)
|
417
446
|
end
|
418
447
|
end
|
419
448
|
end
|
@@ -422,17 +451,17 @@ module RDF::N3
|
|
422
451
|
def subjectStart(prod)
|
423
452
|
@prod_data << {}
|
424
453
|
end
|
425
|
-
|
454
|
+
|
426
455
|
def subjectFinish
|
427
456
|
subject = @prod_data.pop
|
428
|
-
|
457
|
+
|
429
458
|
if subject[:expression]
|
430
459
|
add_prod_data(:subject, subject[:expression])
|
431
460
|
else
|
432
461
|
error("unknown expression type")
|
433
462
|
end
|
434
463
|
end
|
435
|
-
|
464
|
+
|
436
465
|
def symbolToken(prod, tok)
|
437
466
|
term = case prod
|
438
467
|
when 'explicituri'
|
@@ -442,7 +471,7 @@ module RDF::N3
|
|
442
471
|
else
|
443
472
|
error("symbolToken(#{prod}, #{tok}): FIXME #{term.inspect}")
|
444
473
|
end
|
445
|
-
|
474
|
+
|
446
475
|
add_prod_data(:symbol, term)
|
447
476
|
end
|
448
477
|
|
@@ -460,21 +489,21 @@ module RDF::N3
|
|
460
489
|
pd = @prod_data.pop
|
461
490
|
forAll = Array(pd[:symbol])
|
462
491
|
forAll.each do |term|
|
463
|
-
@
|
492
|
+
add_var_to_formula(@formulae.last, term, univar(term))
|
464
493
|
end
|
465
494
|
end
|
466
495
|
|
467
496
|
def verbStart(prod)
|
468
497
|
@prod_data << {}
|
469
498
|
end
|
470
|
-
|
499
|
+
|
471
500
|
def verbToken(prod, tok)
|
472
501
|
term = case prod
|
473
502
|
when '<='
|
474
|
-
add_prod_data(:expression, RDF::
|
503
|
+
add_prod_data(:expression, RDF::N3::Log.implies)
|
475
504
|
add_prod_data(:invert, true)
|
476
505
|
when '=>'
|
477
|
-
add_prod_data(:expression, RDF::
|
506
|
+
add_prod_data(:expression, RDF::N3::Log.implies)
|
478
507
|
when '='
|
479
508
|
add_prod_data(:expression, RDF::OWL.sameAs)
|
480
509
|
when '@a'
|
@@ -486,7 +515,7 @@ module RDF::N3
|
|
486
515
|
else
|
487
516
|
error("verbToken(#{prod}, #{tok}): FIXME #{term.inspect}")
|
488
517
|
end
|
489
|
-
|
518
|
+
|
490
519
|
add_prod_data(:symbol, term)
|
491
520
|
end
|
492
521
|
|
@@ -501,32 +530,39 @@ module RDF::N3
|
|
501
530
|
error("verbFinish: FIXME #{verb.inspect}")
|
502
531
|
end
|
503
532
|
end
|
504
|
-
|
533
|
+
|
505
534
|
private
|
506
|
-
|
535
|
+
|
507
536
|
###################
|
508
537
|
# Utility Functions
|
509
538
|
###################
|
510
539
|
|
511
540
|
def process_anonnode(anonnode)
|
512
|
-
log_debug("process_anonnode") {anonnode.inspect}
|
513
|
-
|
541
|
+
log_debug("process_anonnode", depth: depth) {anonnode.inspect}
|
542
|
+
|
514
543
|
if anonnode[:propertylist]
|
515
544
|
properties = anonnode[:propertylist]
|
516
|
-
bnode =
|
545
|
+
bnode = bnode()
|
517
546
|
properties.each do |p|
|
518
547
|
predicate = p[:verb]
|
519
|
-
log_debug("process_anonnode(verb)") {predicate.inspect}
|
548
|
+
log_debug("process_anonnode(verb)", depth: depth) {predicate.inspect}
|
520
549
|
objects = Array(p[:object])
|
521
|
-
objects.each
|
550
|
+
objects.each do |object|
|
551
|
+
if p[:invert]
|
552
|
+
add_statement("anonnode", object, predicate, bnode)
|
553
|
+
else
|
554
|
+
add_statement("anonnode", bnode, predicate, object)
|
555
|
+
end
|
556
|
+
end
|
522
557
|
end
|
523
558
|
bnode
|
524
559
|
elsif anonnode[:pathlist]
|
525
560
|
objects = Array(anonnode[:pathlist])
|
526
561
|
list = RDF::List[*objects]
|
562
|
+
list_subjects = {}
|
527
563
|
list.each_statement do |statement|
|
528
564
|
next if statement.predicate == RDF.type && statement.object == RDF.List
|
529
|
-
|
565
|
+
add_statement("anonnode(list)", statement.subject, statement.predicate, statement.object)
|
530
566
|
end
|
531
567
|
list.subject
|
532
568
|
end
|
@@ -539,20 +575,20 @@ module RDF::N3
|
|
539
575
|
#
|
540
576
|
# Create triple and return property used for next iteration
|
541
577
|
def process_path(expression)
|
542
|
-
log_debug("process_path") {expression.inspect}
|
578
|
+
log_debug("process_path", depth: depth) {expression.inspect}
|
543
579
|
|
544
580
|
pathitem = expression[:pathitem]
|
545
581
|
pathtail = expression[:pathtail]
|
546
|
-
|
582
|
+
|
547
583
|
direction_list = [expression[:direction], expression[:directiontail]].flatten.compact
|
548
584
|
|
549
585
|
pathtail.each do |pred|
|
550
586
|
direction = direction_list.shift
|
551
587
|
bnode = RDF::Node.new
|
552
588
|
if direction == :reverse
|
553
|
-
|
589
|
+
add_statement("process_path(reverse)", bnode, pred, pathitem)
|
554
590
|
else
|
555
|
-
|
591
|
+
add_statement("process_path(forward)", pathitem, pred, bnode)
|
556
592
|
end
|
557
593
|
pathitem = bnode
|
558
594
|
end
|
@@ -562,7 +598,7 @@ module RDF::N3
|
|
562
598
|
def process_uri(uri)
|
563
599
|
uri(base_uri, RDF::NTriples.unescape(uri))
|
564
600
|
end
|
565
|
-
|
601
|
+
|
566
602
|
def process_qname(tok)
|
567
603
|
if tok.include?(":")
|
568
604
|
prefix, name = tok.split(":")
|
@@ -579,24 +615,26 @@ module RDF::N3
|
|
579
615
|
# old parser encountering true or false naked or in a @keywords
|
580
616
|
return RDF::Literal.new(tok, datatype: RDF::XSD.boolean)
|
581
617
|
else
|
582
|
-
error("Set user @keywords to use barenames.")
|
618
|
+
error("Set user @keywords to use barenames (#{tok}).")
|
583
619
|
end
|
584
620
|
|
585
621
|
uri = if prefix(prefix)
|
586
|
-
log_debug('process_qname(ns)') {"#{prefix(prefix)}, #{name}"}
|
622
|
+
log_debug('process_qname(ns)', depth: depth) {"#{prefix(prefix)}, #{name}"}
|
587
623
|
ns(prefix, name)
|
588
624
|
elsif prefix == '_'
|
589
|
-
log_debug('process_qname(bnode)', name)
|
625
|
+
log_debug('process_qname(bnode)', name, depth: depth)
|
626
|
+
# If we're in a formula, create a non-distigushed variable instead
|
627
|
+
# Note from https://www.w3.org/TeamSubmission/n3/#bnodes, it seems the blank nodes are scoped to the formula, not the file.
|
590
628
|
bnode(name)
|
591
629
|
else
|
592
|
-
log_debug('process_qname(default_ns)', name)
|
630
|
+
log_debug('process_qname(default_ns)', name, depth: depth)
|
593
631
|
namespace(nil, uri("#{base_uri}#")) unless prefix(nil)
|
594
632
|
ns(nil, name)
|
595
633
|
end
|
596
|
-
log_debug('process_qname') {uri.inspect}
|
634
|
+
log_debug('process_qname', depth: depth) {uri.inspect}
|
597
635
|
uri
|
598
636
|
end
|
599
|
-
|
637
|
+
|
600
638
|
# Add values to production data, values aranged as an array
|
601
639
|
def add_prod_data(sym, value)
|
602
640
|
case @prod_data.last[sym]
|
@@ -609,33 +647,38 @@ module RDF::N3
|
|
609
647
|
end
|
610
648
|
end
|
611
649
|
|
612
|
-
# Keep track of allocated BNodes
|
613
|
-
def bnode(
|
614
|
-
|
615
|
-
|
650
|
+
# Keep track of allocated BNodes. Blank nodes are allocated to the formula.
|
651
|
+
def bnode(label = nil)
|
652
|
+
if label
|
653
|
+
value = "#{label}_#{unique_label}"
|
654
|
+
(@bnodes[@formulae.last] ||= {})[label.to_s] ||= RDF::Node.new(value)
|
655
|
+
else
|
656
|
+
RDF::Node.new
|
657
|
+
end
|
616
658
|
end
|
617
659
|
|
618
|
-
def univar(label)
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
end
|
623
|
-
RDF::Query::Variable.new(label.to_s)
|
660
|
+
def univar(label, existential: false)
|
661
|
+
# Label using any provided label, followed by seed, followed by incrementing index
|
662
|
+
value = "#{label}_#{unique_label}"
|
663
|
+
RDF::Query::Variable.new(value, existential: existential)
|
624
664
|
end
|
625
665
|
|
626
|
-
# add a
|
666
|
+
# add a pattern or statement
|
627
667
|
#
|
628
668
|
# @param [any] node string for showing graph_name
|
629
|
-
# @param [
|
630
|
-
# @param [URI] predicate the predicate of the statement
|
631
|
-
# @param [
|
669
|
+
# @param [RDF::Term] subject the subject of the statement
|
670
|
+
# @param [RDF::URI] predicate the predicate of the statement
|
671
|
+
# @param [RDF::Term] object the object of the statement
|
632
672
|
# @return [Statement] Added statement
|
633
673
|
# @raise [RDF::ReaderError] Checks parameter types and raises if they are incorrect if parsing mode is _validate_.
|
634
|
-
def
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
674
|
+
def add_statement(node, subject, predicate, object)
|
675
|
+
statement = if @formulae.last
|
676
|
+
# It's a pattern in a formula
|
677
|
+
RDF::Query::Pattern.new(subject, predicate, object, graph_name: @formulae.last)
|
678
|
+
else
|
679
|
+
RDF::Statement(subject, predicate, object)
|
680
|
+
end
|
681
|
+
log_debug("statement(#{node})", depth: depth) {statement.to_s}
|
639
682
|
@callback.call(statement)
|
640
683
|
end
|
641
684
|
|
@@ -644,7 +687,7 @@ module RDF::N3
|
|
644
687
|
if uri == '#'
|
645
688
|
uri = prefix(nil).to_s + '#'
|
646
689
|
end
|
647
|
-
log_debug("namespace") {"'#{prefix}' <#{uri}>"}
|
690
|
+
log_debug("namespace", depth: depth) {"'#{prefix}' <#{uri}>"}
|
648
691
|
prefix(prefix, uri(uri))
|
649
692
|
end
|
650
693
|
|
@@ -654,7 +697,7 @@ module RDF::N3
|
|
654
697
|
raise RDF::ReaderError, "unqualified keyword '#{kw}' used without @keyword directive" if validate?
|
655
698
|
end
|
656
699
|
end
|
657
|
-
|
700
|
+
|
658
701
|
# Create URIs
|
659
702
|
def uri(value, append = nil)
|
660
703
|
value = RDF::URI(value)
|
@@ -662,20 +705,42 @@ module RDF::N3
|
|
662
705
|
value.validate! if validate? && value.respond_to?(:validate)
|
663
706
|
value.canonicalize! if canonicalize?
|
664
707
|
value = RDF::URI.intern(value, {}) if intern?
|
665
|
-
|
666
|
-
# Variable substitution for in-scope variables. Variables are in scope if they are defined in anthing other than
|
667
|
-
|
668
|
-
|
669
|
-
value = var[:var] if var
|
708
|
+
|
709
|
+
# Variable substitution for in-scope variables. Variables are in scope if they are defined in anthing other than the current formula
|
710
|
+
var = find_var(@formulae.last, value)
|
711
|
+
value = var if var
|
670
712
|
|
671
713
|
value
|
672
714
|
end
|
673
|
-
|
715
|
+
|
674
716
|
def ns(prefix, suffix)
|
675
717
|
base = prefix(prefix).to_s
|
676
718
|
suffix = suffix.to_s.sub(/^\#/, "") if base.index("#")
|
677
|
-
log_debug("ns") {"base: '#{base}', suffix: '#{suffix}'"}
|
719
|
+
log_debug("ns", depth: depth) {"base: '#{base}', suffix: '#{suffix}'"}
|
678
720
|
uri(base + suffix.to_s)
|
679
721
|
end
|
722
|
+
|
723
|
+
# Returns a unique label
|
724
|
+
def unique_label
|
725
|
+
label, @label_uniquifier = @label_uniquifier, @label_uniquifier.succ
|
726
|
+
label
|
727
|
+
end
|
728
|
+
|
729
|
+
# Find any variable that may be defined in the formula identified by `bn`
|
730
|
+
# @param [RDF::Node] bn name of formula
|
731
|
+
# @param [#to_s] name
|
732
|
+
# @return [RDF::Query::Variable]
|
733
|
+
def find_var(sym, name)
|
734
|
+
(@variables[sym] ||= {})[name.to_s]
|
735
|
+
end
|
736
|
+
|
737
|
+
# Add a variable to the formula identified by `bn`, returning the variable. Useful as an LRU for variable name lookups
|
738
|
+
# @param [RDF::Node] bn name of formula
|
739
|
+
# @param [#to_s] name of variable for lookup
|
740
|
+
# @param [RDF::Query::Variable] var
|
741
|
+
# @return [RDF::Query::Variable]
|
742
|
+
def add_var_to_formula(bn, name, var)
|
743
|
+
(@variables[bn] ||= {})[name.to_s] = var
|
744
|
+
end
|
680
745
|
end
|
681
746
|
end
|