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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +95 -51
  3. data/VERSION +1 -1
  4. data/lib/rdf/n3.rb +9 -6
  5. data/lib/rdf/n3/algebra.rb +125 -0
  6. data/lib/rdf/n3/algebra/formula.rb +185 -0
  7. data/lib/rdf/n3/algebra/list/append.rb +13 -0
  8. data/lib/rdf/n3/algebra/list/in.rb +9 -0
  9. data/lib/rdf/n3/algebra/list/last.rb +11 -0
  10. data/lib/rdf/n3/algebra/list/member.rb +7 -0
  11. data/lib/rdf/n3/algebra/log/conclusion.rb +9 -0
  12. data/lib/rdf/n3/algebra/log/conjunction.rb +9 -0
  13. data/lib/rdf/n3/algebra/log/equalTo.rb +7 -0
  14. data/lib/rdf/n3/algebra/log/implies.rb +77 -0
  15. data/lib/rdf/n3/algebra/log/includes.rb +13 -0
  16. data/lib/rdf/n3/algebra/log/notEqualTo.rb +7 -0
  17. data/lib/rdf/n3/algebra/log/notIncludes.rb +12 -0
  18. data/lib/rdf/n3/algebra/log/outputString.rb +7 -0
  19. data/lib/rdf/n3/algebra/math/absoluteValue.rb +9 -0
  20. data/lib/rdf/n3/algebra/math/difference.rb +9 -0
  21. data/lib/rdf/n3/algebra/math/equalTo.rb +9 -0
  22. data/lib/rdf/n3/algebra/math/exponentiation.rb +9 -0
  23. data/lib/rdf/n3/algebra/math/greaterThan.rb +9 -0
  24. data/lib/rdf/n3/algebra/math/integerQuotient.rb +9 -0
  25. data/lib/rdf/n3/algebra/math/lessThan.rb +9 -0
  26. data/lib/rdf/n3/algebra/math/memberCount.rb +9 -0
  27. data/lib/rdf/n3/algebra/math/negation.rb +9 -0
  28. data/lib/rdf/n3/algebra/math/notEqualTo.rb +9 -0
  29. data/lib/rdf/n3/algebra/math/notGreaterThan.rb +9 -0
  30. data/lib/rdf/n3/algebra/math/notLessThan.rb +9 -0
  31. data/lib/rdf/n3/algebra/math/product.rb +9 -0
  32. data/lib/rdf/n3/algebra/math/quotient.rb +9 -0
  33. data/lib/rdf/n3/algebra/math/remainder.rb +9 -0
  34. data/lib/rdf/n3/algebra/math/rounded.rb +9 -0
  35. data/lib/rdf/n3/algebra/math/sum.rb +9 -0
  36. data/lib/rdf/n3/algebra/str/concatenation.rb +9 -0
  37. data/lib/rdf/n3/algebra/str/contains.rb +9 -0
  38. data/lib/rdf/n3/algebra/str/containsIgnoringCase.rb +9 -0
  39. data/lib/rdf/n3/algebra/str/endsWith.rb +9 -0
  40. data/lib/rdf/n3/algebra/str/equalIgnoringCase.rb +9 -0
  41. data/lib/rdf/n3/algebra/str/format.rb +9 -0
  42. data/lib/rdf/n3/algebra/str/greaterThan.rb +9 -0
  43. data/lib/rdf/n3/algebra/str/lessThan.rb +9 -0
  44. data/lib/rdf/n3/algebra/str/matches.rb +9 -0
  45. data/lib/rdf/n3/algebra/str/notEqualIgnoringCase.rb +9 -0
  46. data/lib/rdf/n3/algebra/str/notGreaterThan.rb +9 -0
  47. data/lib/rdf/n3/algebra/str/notLessThan.rb +9 -0
  48. data/lib/rdf/n3/algebra/str/notMatches.rb +9 -0
  49. data/lib/rdf/n3/algebra/str/replace.rb +12 -0
  50. data/lib/rdf/n3/algebra/str/scrape.rb +9 -0
  51. data/lib/rdf/n3/algebra/str/startsWith.rb +56 -0
  52. data/lib/rdf/n3/extensions.rb +79 -0
  53. data/lib/rdf/n3/format.rb +1 -1
  54. data/lib/rdf/n3/reader.rb +181 -116
  55. data/lib/rdf/n3/reader/parser.rb +34 -32
  56. data/lib/rdf/n3/reasoner.rb +293 -0
  57. data/lib/rdf/n3/vocab.rb +8 -3
  58. data/lib/rdf/n3/writer.rb +358 -198
  59. metadata +109 -30
@@ -0,0 +1,9 @@
1
+ module RDF::N3::Algebra::Str
2
+ ##
3
+ # True iff the string is greater than the object when ordered according to Unicode(tm) code order
4
+ class GreaterThan < SPARQL::Algebra::Operator::Binary
5
+ include RDF::Util::Logger
6
+
7
+ NAME = :strGreaterThan
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module RDF::N3::Algebra::Str
2
+ ##
3
+ # True iff the string is less than the object when ordered according to Unicode(tm) code order.
4
+ class LessThan < SPARQL::Algebra::Operator::Binary
5
+ include RDF::Util::Logger
6
+
7
+ NAME = :strLessThan
8
+ end
9
+ end
@@ -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
+ # True iff the string is NOT less than the object when ordered according to Unicode(tm) code order.
4
+ class NotLessThan < SPARQL::Algebra::Operator::Binary
5
+ include RDF::Util::Logger
6
+
7
+ NAME = :strNotLessThan
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
@@ -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 http://www.w3.org/TR/rdf-testcases/#ntriples
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'
@@ -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 Formluae graph names
69
+ @formulae = [] # Nodes used as Formulae graph names
64
70
  @formulae_nodes = {}
65
- @variables = {} # variable definitions along with defining formula
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
- log_debug("@uri") { base_uri.inspect}
76
+ log_info("@uri") { base_uri.inspect}
69
77
  namespace(nil, uri("#{base_uri}#"))
70
78
  end
71
- log_debug("validate") {validate?.inspect}
72
- log_debug("canonicalize") {canonicalize?.inspect}
73
- log_debug("intern") {intern?.inspect}
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(:each_triple)
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(&block)
133
+ def each_triple
116
134
  if block_given?
117
135
  each_statement do |statement|
118
- block.call(*statement.to_triple)
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
- log_debug("#{handler}(#{respond_to?(handler, true)})", prod)
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
- log_debug("#{handler}(#{respond_to?(handler, true)})") {"#{prod}: #{@prod_data.last.inspect}"}
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
- log_debug("#{handler}(#{respond_to?(handler, true)})") {"#{prod}, #{tok}: #{@prod_data.last.inspect}"}
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
- @variables[term.to_s] = {formula: @formulae.last, var: RDF::Node.new(term.to_s.split(/[\/#]/).last)}
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
- @variables[uri.to_s] = { formula: @formulae[-2], var: univar(uri) }
314
- add_prod_data(:symbol, uri)
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, and remove any variables defined in this graph
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
- add_triple("simpleStatementFinish", object, predicate, subject)
443
+ add_statement("simpleStatementFinish", object, predicate, subject)
415
444
  else
416
- add_triple("simpleStatementFinish", subject, predicate, object)
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
- @variables[term.to_s] = { formula: @formulae.last, var: univar(term) }
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::LOG.implies)
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::LOG.implies)
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 = RDF::Node.new
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 { |object| add_triple("anonnode", bnode, predicate, object) }
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
- add_triple("anonnode(list)", statement.subject, statement.predicate, statement.object)
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
- add_triple("process_path(reverse)", bnode, pred, pathitem)
589
+ add_statement("process_path(reverse)", bnode, pred, pathitem)
554
590
  else
555
- add_triple("process_path(forward)", pathitem, pred, bnode)
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(value = nil)
614
- @bnode_cache ||= {}
615
- @bnode_cache[value.to_s] ||= RDF::Node.new(value)
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
- unless label
620
- @unnamed_label ||= "var0"
621
- label = @unnamed_label = @unnamed_label.succ
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 statement, object can be literal or URI or bnode
666
+ # add a pattern or statement
627
667
  #
628
668
  # @param [any] node string for showing graph_name
629
- # @param [URI, Node] subject the subject of the statement
630
- # @param [URI] predicate the predicate of the statement
631
- # @param [URI, Node, Literal] object the object of the statement
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 add_triple(node, subject, predicate, object)
635
- graph_name_opts = @formulae.last ? {graph_name: @formulae.last} : {}
636
-
637
- statement = RDF::Statement(subject, predicate, object, graph_name_opts)
638
- log_debug(node) {statement.to_s}
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
- # the current formula
668
- var = @variables[value.to_s]
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