rdf-n3 0.2.3.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. data/.gitignore +1 -0
  2. data/.yardopts +4 -3
  3. data/{History.txt → History.md} +30 -6
  4. data/{README.rdoc → README.md} +56 -19
  5. data/Rakefile +15 -29
  6. data/VERSION +1 -1
  7. data/example-files/sp2b.n3 +50177 -0
  8. data/lib/rdf/n3.rb +2 -2
  9. data/lib/rdf/n3/reader.rb +560 -367
  10. data/lib/rdf/n3/reader/meta.rb +640 -0
  11. data/lib/rdf/n3/reader/n3-selectors.n3 +0 -0
  12. data/lib/rdf/n3/reader/parser.rb +229 -0
  13. data/lib/rdf/n3/vocab.rb +1 -0
  14. data/lib/rdf/n3/writer.rb +324 -265
  15. data/rdf-n3.gemspec +24 -26
  16. data/script/build_meta +242 -0
  17. data/script/parse +62 -13
  18. data/script/tc +4 -4
  19. data/spec/cwm_spec.rb +11 -3
  20. data/spec/n3reader_spec.rb +233 -63
  21. data/spec/rdf_helper.rb +15 -15
  22. data/spec/spec_helper.rb +10 -4
  23. data/spec/swap_spec.rb +11 -35
  24. data/spec/swap_test/n3parser.tests +14 -14
  25. data/spec/swap_test/n3parser.yml +0 -19
  26. data/spec/swap_test/nodeID/classes.ref.rdf +1 -1
  27. data/spec/swap_test/ref/contexts-1.n3 +12 -0
  28. data/spec/swap_test/ref/prefix2.rdf +33 -0
  29. data/spec/swap_test/ref/strquot.n3 +0 -1
  30. data/spec/swap_test/ref/xml-syntax-basic-serialization.rdf +1 -1
  31. data/spec/swap_test/regression.n3 +5 -5
  32. data/spec/swap_test/regression.yml +53 -23
  33. data/spec/turtle/manifest-bad.yml +91 -0
  34. data/spec/turtle/manifest.yml +187 -0
  35. data/spec/turtle_spec.rb +12 -20
  36. data/spec/writer_spec.rb +39 -37
  37. metadata +43 -48
  38. data/lib/rdf/n3/patches/qname_hacks.rb +0 -57
  39. data/lib/rdf/n3/patches/seq.rb +0 -34
  40. data/lib/rdf/n3/reader/n3_grammar.rb +0 -3764
  41. data/lib/rdf/n3/reader/n3_grammar.treetop +0 -227
  42. data/lib/rdf/n3/reader/n3_grammar_18.rb +0 -3764
  43. data/lib/rdf/n3/reader/n3_grammar_18.treetop +0 -227
  44. data/spec/literal_spec.rb +0 -245
@@ -24,8 +24,8 @@ module RDF
24
24
  require 'rdf/n3/vocab'
25
25
  require 'rdf/n3/patches/array_hacks'
26
26
  require 'rdf/n3/patches/graph_properties'
27
- require 'rdf/n3/patches/qname_hacks'
28
- require 'rdf/n3/patches/seq'
27
+ autoload :Meta, 'rdf/n3/reader/meta'
28
+ autoload :Parser, 'rdf/n3/reader/parser'
29
29
  autoload :Reader, 'rdf/n3/reader'
30
30
  autoload :VERSION, 'rdf/n3/version'
31
31
  autoload :Writer, 'rdf/n3/writer'
@@ -1,68 +1,88 @@
1
- require 'treetop'
2
-
3
- if defined?(::Encoding)
4
- # load full grammar
5
- Treetop.load(File.join(File.dirname(__FILE__), "reader", "n3_grammar"))
6
- else
7
- # load 1.8 grammar, doesn't include U00010000-\U000effff
8
- Treetop.load(File.join(File.dirname(__FILE__), "reader", "n3_grammar_18"))
9
- end
10
-
11
1
  module RDF::N3
12
2
  ##
13
3
  # A Notation-3/Turtle parser in Ruby
14
4
  #
5
+ # N3 Parser, based in librdf version of predictiveParser.py
6
+ # @see http://www.w3.org/2000/10/swap/grammar/predictiveParser.py
7
+ # @see http://www.w3.org/2000/10/swap/grammar/n3-selectors.n3
8
+ #
9
+ # Separate pass to create branch_table from n3-selectors.n3
10
+ #
11
+ # @todo
12
+ # Existentials, Universals and Formulae
13
+ #
15
14
  # @author [Gregg Kellogg](http://kellogg-assoc.com/)
16
15
  class Reader < RDF::Reader
17
16
  format Format
18
17
 
18
+ include Meta
19
+ include Parser
20
+
19
21
  N3_KEYWORDS = %w(a is of has keywords prefix base true false forSome forAny)
20
22
 
21
- NC_REGEXP = Regexp.new(
22
- %{^
23
- (?!\\\\u0301) # &#x301; is a non-spacing acute accent.
24
- # It is legal within an XML Name, but not as the first character.
25
- ( [a-zA-Z_]
26
- | \\\\u[0-9a-fA-F]
27
- )
28
- ( [0-9a-zA-Z_\.-]
29
- | \\\\u([0-9a-fA-F]{4})
30
- )*
31
- $},
32
- Regexp::EXTENDED)
33
-
34
23
  ##
35
24
  # Initializes the N3 reader instance.
36
25
  #
37
- # @param [IO, File, String] input
38
- # @option options [Array] :debug Array to place debug messages
39
- # @option options [Boolean] :strict Raise Error if true, continue with lax parsing, otherwise
40
- # @option options [Boolean] :base_uri (nil) Base URI to use for relative URIs.
26
+ # @param [IO, File, String] input
27
+ # the input stream to read
28
+ # @option options [Array] :debug
29
+ # Array to place debug messages
30
+ # @option options [#to_s] :base_uri (nil)
31
+ # the base URI to use when resolving relative URIs (not supported by
32
+ # all readers)
33
+ # @option options [Boolean] :validate (false)
34
+ # whether to validate the parsed statements and values
35
+ # @option options [Boolean] :canonicalize (false)
36
+ # whether to canonicalize parsed literals
37
+ # @option options [Boolean] :intern (true)
38
+ # whether to intern all parsed URIs
39
+ # @option options [Hash] :prefixes (Hash.new)
40
+ # the prefix mappings to use (not supported by all readers)
41
41
  # @return [reader]
42
- # @yield [reader]
43
- # @yieldparam [Reader] reader
44
- # @raise [Error]:: Raises RDF::ReaderError if _strict_
42
+ # @yield [reader] `self`
43
+ # @yieldparam [RDF::Reader] reader
44
+ # @yieldreturn [void] ignored
45
+ # @raise [Error]:: Raises RDF::ReaderError if _validate_
45
46
  def initialize(input = $stdin, options = {}, &block)
46
47
  super do
47
- @debug = options[:debug]
48
- @strict = options[:strict]
49
- @uri_mappings = {}
50
- @uri = uri(options[:base_uri], nil, false)
51
-
52
- @doc = input.respond_to?(:read) ? (input.rewind; input.read) : input
53
- @default_ns = uri("#{options[:base_uri]}#", nil, false) if @uri
54
- add_debug("@default_ns", "#{@default_ns.inspect}")
48
+ @input = input.respond_to?(:read) ? (input.rewind; input) : StringIO.new(input.to_s)
49
+ @lineno = 0
50
+ readline # Prime the pump
51
+
52
+ @memo = {}
53
+ @keyword_mode = false
54
+ @keywords = %w(a is of this has)
55
+ @productions = []
56
+ @prod_data = []
57
+
58
+ @branches = BRANCHES # Get from meta class
59
+ @regexps = REGEXPS # Get from meta class
55
60
 
56
- block.call(self) if block_given?
61
+ @formulae = [] # Nodes used as Formluae context identifiers
62
+ @variables = {} # variable definitions along with defining formula
63
+
64
+ if options[:base_uri]
65
+ @uri = uri(options[:base_uri])
66
+ add_debug("@uri", "#{@uri.inspect}")
67
+ namespace(nil, uri("#{options[:base_uri]}#"))
68
+ end
69
+ add_debug("validate", "#{validate?.inspect}")
70
+ add_debug("canonicalize", "#{canonicalize?.inspect}")
71
+ add_debug("intern", "#{intern?.inspect}")
72
+
73
+ # Prefixes that may be used without being defined
74
+ #prefix(:rdf, RDF.to_uri.to_s)
75
+ #prefix(:xsd, RDF::XSD.to_uri.to_s)
76
+
77
+ if block_given?
78
+ case block.arity
79
+ when 0 then instance_eval(&block)
80
+ else block.call(self)
81
+ end
82
+ end
57
83
  end
58
84
  end
59
85
 
60
- # No need to rewind, as parsing is done in initialize
61
- def rewind; end
62
-
63
- # Document closed when read in initialize
64
- def close; end
65
-
66
86
  ##
67
87
  # Iterates the given block for each RDF statement in the input.
68
88
  #
@@ -72,17 +92,9 @@ module RDF::N3
72
92
  def each_statement(&block)
73
93
  @callback = block
74
94
 
75
- parser = N3GrammerParser.new
76
- document = parser.parse(@doc)
77
- unless document
78
- puts parser.inspect if ::RDF::N3::debug?
79
- reason = parser.failure_reason
80
- raise RDF::ReaderError, reason
81
- end
82
-
83
- process_statements(document)
95
+ parse(START.to_sym)
84
96
  end
85
-
97
+
86
98
  ##
87
99
  # Iterates the given block for each RDF triple in the input.
88
100
  #
@@ -97,129 +109,405 @@ module RDF::N3
97
109
  end
98
110
  end
99
111
 
100
- private
112
+ protected
113
+ # Start of production
114
+ def onStart(prod)
115
+ handler = "#{prod}Start".to_sym
116
+ add_debug("#{handler}(#{respond_to?(handler)})", prod)
117
+ @productions << prod
118
+ send(handler, prod) if respond_to?(handler)
119
+ end
101
120
 
102
- # Keep track of allocated BNodes
103
- def bnode(value = nil)
104
- @bnode_cache ||= {}
105
- @bnode_cache[value.to_s] ||= RDF::Node.new(value)
121
+ # End of production
122
+ def onFinish
123
+ prod = @productions.pop()
124
+ handler = "#{prod}Finish".to_sym
125
+ add_debug("#{handler}(#{respond_to?(handler)})", "#{prod}: #{@prod_data.last.inspect}")
126
+ send(handler) if respond_to?(handler)
106
127
  end
107
128
 
108
- # Add debug event to debug array, if specified
109
- #
110
- # @param [XML Node, any] node:: XML Node or string for showing context
111
- # @param [String] message::
112
- def add_debug(node, message)
113
- puts "#{node}: #{message}" if ::RDF::N3::debug?
114
- @debug << "#{node}: #{message}" if @debug.is_a?(Array)
129
+ # Process of a token
130
+ def onToken(prod, tok)
131
+ unless @productions.empty?
132
+ parentProd = @productions.last
133
+ handler = "#{parentProd}Token".to_sym
134
+ add_debug("#{handler}(#{respond_to?(handler)})", "#{prod}, #{tok}: #{@prod_data.last.inspect}")
135
+ send(handler, prod, tok) if respond_to?(handler)
136
+ else
137
+ error("Token has no parent production")
138
+ end
139
+ end
140
+
141
+ def booleanToken(prod, tok)
142
+ lit = RDF::Literal.new(tok.delete("@"), :datatype => RDF::XSD.boolean, :validate => validate?, :canonicalize => canonicalize?)
143
+ add_prod_data(:literal, lit)
144
+ end
145
+
146
+ def declarationStart(prod)
147
+ @prod_data << {}
148
+ end
149
+
150
+ def declarationToken(prod, tok)
151
+ case prod
152
+ when "@prefix", "@base", "@keywords"
153
+ add_prod_data(:prod, prod)
154
+ when "prefix"
155
+ add_prod_data(:prefix, tok[0..-2])
156
+ when "explicituri"
157
+ add_prod_data(:explicituri, tok[1..-2])
158
+ else
159
+ add_prod_data(prod.to_sym, tok)
160
+ end
115
161
  end
116
162
 
117
- # add a statement, object can be literal or URI or bnode
118
- #
119
- # @param [Nokogiri::XML::Node, any] node:: XML Node or string for showing context
120
- # @param [URI, Node] subject:: the subject of the statement
121
- # @param [URI] predicate:: the predicate of the statement
122
- # @param [URI, Node, Literal] object:: the object of the statement
123
- # @return [Statement]:: Added statement
124
- # @raise [RDF::ReaderError]:: Checks parameter types and raises if they are incorrect if parsing mode is _strict_.
125
- def add_triple(node, subject, predicate, object)
126
- statement = RDF::Statement.new(subject, predicate, object)
127
- add_debug(node, "statement: #{statement}")
128
- @callback.call(statement)
163
+ def declarationFinish
164
+ decl = @prod_data.pop
165
+ case decl[:prod]
166
+ when "@prefix"
167
+ uri = process_uri(decl[:explicituri])
168
+ namespace(decl[:prefix], uri)
169
+ when "@base"
170
+ # Base, set or update document URI
171
+ uri = decl[:explicituri]
172
+ @uri = process_uri(uri)
173
+
174
+ # The empty prefix "" is by default , bound to "#" -- the local namespace of the file.
175
+ # The parser behaves as though there were a
176
+ # @prefix : <#>.
177
+ # just before the file.
178
+ # This means that <#foo> can be written :foo and using @keywords one can reduce that to foo.
179
+
180
+ namespace(nil, uri.match(/[\/\#]$/) ? @uri : process_uri("#{uri}#"))
181
+ add_debug("declarationFinish[@base]", "@base=#{@uri}")
182
+ when "@keywords"
183
+ add_debug("declarationFinish[@keywords]", @keywords.inspect)
184
+ # Keywords are handled in tokenizer and maintained in @keywords array
185
+ if (@keywords & N3_KEYWORDS) != @keywords
186
+ error("Undefined keywords used: #{(@keywords - N3_KEYWORDS).to_sentence}") if validate?
187
+ end
188
+ @userkeys = true
189
+ else
190
+ error("declarationFinish: FIXME #{decl.inspect}")
191
+ end
192
+ end
193
+
194
+ # Document start, instantiate
195
+ def documentStart(prod)
196
+ @formulae.push(nil)
197
+ @prod_data << {}
198
+ end
199
+
200
+ def dtlangToken(prod, tok)
201
+ add_prod_data(:langcode, tok) if prod == "langcode"
202
+ end
203
+
204
+ def existentialStart(prod)
205
+ @prod_data << {}
129
206
  end
130
207
 
131
- def namespace(uri, prefix)
132
- uri = uri.to_s
133
- if uri == "#"
134
- uri = @default_ns
208
+ # Apart from the set of statements, a formula also has a set of URIs of symbols which are universally quantified,
209
+ # and a set of URIs of symbols which are existentially quantified.
210
+ # Variables are then in general symbols which have been quantified.
211
+ #
212
+ # Here we allocate a variable (making up a name) and record with the defining formula. Quantification is done
213
+ # when the formula is completed against all in-scope variables
214
+ def existentialFinish
215
+ pd = @prod_data.pop
216
+ forSome = [pd[:symbol]].flatten.compact
217
+ forSome.each do |term|
218
+ @variables[term.to_s] = {:formula => @formulae.last, :var => RDF::Node.new(term.to_s.split(/[\/#]/).last)}
135
219
  end
136
- add_debug("namesspace", "'#{prefix}' <#{uri}>")
137
- @uri_mappings[prefix] = RDF::URI.intern(uri)
138
220
  end
221
+
222
+ def expressionStart(prod)
223
+ @prod_data << {}
224
+ end
225
+
226
+ # Process path items, and push on the last object for parent processing
227
+ def expressionFinish
228
+ expression = @prod_data.pop
229
+
230
+ # If we're in teh middle of a pathtail, append
231
+ if @prod_data.last[:pathtail] && expression[:pathitem] && expression[:pathtail]
232
+ path_list = [expression[:pathitem]] + expression[:pathtail]
233
+ add_debug("expressionFinish(pathtail)", "set pathtail to #{path_list.inspect}")
234
+ @prod_data.last[:pathtail] = path_list
139
235
 
140
- def process_statements(document)
141
- document.elements.find_all do |e|
142
- s = e.elements.first
143
- add_debug(*s.info("process_statements"))
236
+ dir_list = [expression[:direction]] if expression[:direction]
237
+ dir_list += expression[:directiontail] if expression[:directiontail]
238
+ @prod_data.last[:directiontail] = dir_list if dir_list
239
+ elsif expression[:pathitem] && expression[:pathtail]
240
+ add_prod_data(:expression, process_path(expression))
241
+ elsif expression[:pathitem]
242
+ add_prod_data(:expression, expression[:pathitem])
243
+ else
244
+ error("expressionFinish: FIXME #{expression.inspect}")
245
+ end
246
+ end
247
+
248
+ def literalStart(prod)
249
+ @prod_data << {}
250
+ end
251
+
252
+ def literalToken(prod, tok)
253
+ tok = tok[0, 3] == '"""' ? tok[3..-4] : tok[1..-2]
254
+ add_prod_data(:string, tok)
255
+ end
256
+
257
+ def literalFinish
258
+ lit = @prod_data.pop
259
+ content = RDF::NTriples.unescape(lit[:string])
260
+ language = lit[:langcode]
261
+ datatype = lit[:symbol]
262
+
263
+ lit = RDF::Literal.new(content, :language => language, :datatype => datatype, :validate => validate?, :canonicalize => canonicalize?)
264
+ add_prod_data(:literal, lit)
265
+ end
266
+
267
+ def objectStart(prod)
268
+ @prod_data << {}
269
+ end
270
+
271
+ def objectFinish
272
+ object = @prod_data.pop
273
+ if object[:expression]
274
+ add_prod_data(:object, object[:expression])
275
+ else
276
+ error("objectFinish: FIXME #{object.inspect}")
277
+ end
278
+ end
279
+
280
+ def pathitemStart(prod)
281
+ @prod_data << {}
282
+ end
283
+
284
+ def pathitemToken(prod, tok)
285
+ case prod
286
+ when "numericliteral"
287
+ nl = RDF::NTriples.unescape(tok)
288
+ datatype = case nl
289
+ when /e/i then RDF::XSD.double
290
+ when /\./ then RDF::XSD.decimal
291
+ else RDF::XSD.integer
292
+ end
144
293
 
145
- if s.respond_to?(:subject)
146
- subject = process_expression(s.subject)
147
- add_debug(*s.info("process_statements(#{subject})"))
148
- properties = process_properties(s.property_list)
149
- properties.each do |p|
150
- predicate = process_verb(p.verb)
151
- add_debug(*p.info("process_statements(#{subject}, #{predicate})"))
152
- raise RDF::ReaderError, %Q(Illegal statment: "#{predicate}" missing object) unless p.respond_to?(:object_list)
153
- objects = process_objects(p.object_list)
154
- objects.each do |object|
155
- if p.verb.respond_to?(:invert)
156
- add_triple("statement", object, predicate, subject)
157
- else
158
- add_triple("statement", subject, predicate, object)
159
- end
160
- end
161
- end
162
- elsif s.respond_to?(:anonnode)
163
- process_anonnode(s)
164
- elsif s.respond_to?(:pathitem)
165
- process_path(s)
166
- elsif s.respond_to?(:declaration)
167
- if s.respond_to?(:nprefix)
168
- add_debug(*s.info("process_statements(namespace)"))
169
- keyword_check("prefix") if s.text_value.index("prefix") == 0
170
- uri = process_uri(s.explicituri.uri)
171
- namespace(uri, s.nprefix.text_value)
172
- elsif s.respond_to?(:base)
173
- add_debug(*s.info("process_statements(base)"))
174
- keyword_check("base") if s.text_value.index("base") == 0
175
- # Base, set or update document URI
176
- uri = s.explicituri.uri.text_value
177
- @uri = process_uri(uri)
178
-
179
- # The empty prefix "" is by default , bound to "#" -- the local namespace of the file.
180
- # The parser behaves as though there were a
181
- # @prefix : <#>.
182
- # just before the file.
183
- # This means that <#foo> can be written :foo and using @keywords one can reduce that to foo.
184
-
185
- @default_ns = uri.match(/[\/\#]$/) ? @uri : process_uri("#{uri}#")
186
- add_debug("@default_ns", "#{@default_ns.inspect}")
187
- add_debug("@base", "#{@uri}")
188
- @uri
189
- elsif s.respond_to?(:keywords)
190
- add_debug(*s.info("process_statements(keywords)"))
191
- keyword_check("keywords") if s.text_value.index("keywords") == 0
192
- @keywords = process_barename_csl(s.barename_csl) ||[]
193
- add_debug("@keywords", @keywords.inspect)
194
- if (@keywords & N3_KEYWORDS) != @keywords
195
- raise RDF::ReaderError, "undefined keywords used: #{(@keywords - N3_KEYWORDS).to_sentence}" if @strict
196
- end
294
+ lit = RDF::Literal.new(nl, :datatype => datatype, :validate => validate?, :canonicalize => canonicalize?)
295
+ add_prod_data(:literal, lit)
296
+ when "quickvariable"
297
+ # There is a also a shorthand syntax ?x which is the same as :x except that it implies that x is
298
+ # universally quantified not in the formula but in its parent formula
299
+ uri = process_qname(tok.sub('?', ':'))
300
+ @variables[uri.to_s] = { :formula => @formulae[-2], :var => univar(uri) }
301
+ add_prod_data(:symbol, uri)
302
+ when "boolean"
303
+ lit = RDF::Literal.new(tok.delete("@"), :datatype => RDF::XSD.boolean, :validate => validate?, :canonicalize => canonicalize?)
304
+ add_prod_data(:literal, lit)
305
+ when "[", "("
306
+ # Push on state for content of blank node
307
+ @prod_data << {}
308
+ when "]", ")"
309
+ # Construct
310
+ symbol = process_anonnode(@prod_data.pop)
311
+ add_prod_data(:symbol, symbol)
312
+ when "{"
313
+ # A new formula, push on a graph as a formula context
314
+ context = RDF::Graph.new(RDF::Node.new)
315
+ @formulae << context
316
+ when "}"
317
+ # Pop off the formula, and remove any variables defined in this context
318
+ formula = @formulae.pop
319
+ @variables.delete_if {|k, v| v[:formula] == formula}
320
+ add_prod_data(:symbol, formula)
321
+ else
322
+ error("pathitemToken(#{prod}, #{tok}): FIXME")
323
+ end
324
+ end
325
+
326
+ def pathitemFinish
327
+ pathitem = @prod_data.pop
328
+ if pathitem[:pathlist]
329
+ error("pathitemFinish(pathlist): FIXME #{pathitem.inspect}")
330
+ elsif pathitem[:propertylist]
331
+ error("pathitemFinish(propertylist): FIXME #{pathitem.inspect}")
332
+ elsif pathitem[:symbol] || pathitem[:literal]
333
+ add_prod_data(:pathitem, pathitem[:symbol] || pathitem[:literal])
334
+ else
335
+ error("pathitemFinish: FIXME #{pathitem.inspect}")
336
+ end
337
+ end
338
+
339
+ def pathlistStart(prod)
340
+ @prod_data << {:pathlist => []}
341
+ end
342
+
343
+ def pathlistFinish
344
+ pathlist = @prod_data.pop
345
+ # Flatten propertylist into an array
346
+ expr = @prod_data.last.delete(:expression)
347
+ add_prod_data(:pathlist, expr) if expr
348
+ add_prod_data(:pathlist, pathlist[:pathlist]) if pathlist[:pathlist]
349
+ end
350
+
351
+ def pathtailStart(prod)
352
+ @prod_data << {:pathtail => []}
353
+ end
354
+
355
+ def pathtailToken(prod, tok)
356
+ case tok
357
+ when "!", "."
358
+ add_prod_data(:direction, :forward)
359
+ when "^"
360
+ add_prod_data(:direction, :reverse)
361
+ end
362
+ end
363
+
364
+ def pathtailFinish
365
+ pathtail = @prod_data.pop
366
+ add_prod_data(:pathtail, pathtail[:pathtail])
367
+ add_prod_data(:direction, pathtail[:direction]) if pathtail[:direction]
368
+ add_prod_data(:directiontail, pathtail[:directiontail]) if pathtail[:directiontail]
369
+ end
370
+
371
+ def propertylistStart(prod)
372
+ @prod_data << {}
373
+ end
374
+
375
+ def propertylistFinish
376
+ propertylist = @prod_data.pop
377
+ # Flatten propertylist into an array
378
+ ary = [propertylist, propertylist.delete(:propertylist)].flatten.compact
379
+ @prod_data.last[:propertylist] = ary
380
+ end
381
+
382
+ def simpleStatementStart(prod)
383
+ @prod_data << {}
384
+ end
385
+
386
+ # Completion of Simple Statement, all productions include :subject, and :propertyList
387
+ def simpleStatementFinish
388
+ statement = @prod_data.pop
389
+
390
+ subject = statement[:subject]
391
+ properties = [statement[:propertylist]].flatten.compact
392
+ properties.each do |p|
393
+ predicate = p[:verb]
394
+ next unless predicate
395
+ add_debug("simpleStatementFinish(pred)", predicate)
396
+ error(%(Illegal statment: "#{predicate}" missing object)) unless p.has_key?(:object)
397
+ objects =[ p[:object]].flatten.compact
398
+ objects.each do |object|
399
+ if p[:invert]
400
+ add_triple("simpleStatementFinish", object, predicate, subject)
401
+ else
402
+ add_triple("simpleStatementFinish", subject, predicate, object)
197
403
  end
198
404
  end
199
405
  end
200
406
  end
407
+
408
+ def subjectStart(prod)
409
+ @prod_data << {}
410
+ end
411
+
412
+ def subjectFinish
413
+ subject = @prod_data.pop
414
+
415
+ if subject[:expression]
416
+ add_prod_data(:subject, subject[:expression])
417
+ else
418
+ error("unknown expression type")
419
+ end
420
+ end
421
+
422
+ def symbolToken(prod, tok)
423
+ term = case prod
424
+ when 'explicituri'
425
+ process_uri(tok[1..-2])
426
+ when 'qname'
427
+ process_qname(tok)
428
+ else
429
+ error("symbolToken(#{prod}, #{tok}): FIXME #{term.inspect}")
430
+ end
431
+
432
+ add_prod_data(:symbol, term)
433
+ end
434
+
435
+ def universalStart(prod)
436
+ @prod_data << {}
437
+ end
438
+
439
+ # Apart from the set of statements, a formula also has a set of URIs of symbols which are universally quantified,
440
+ # and a set of URIs of symbols which are existentially quantified.
441
+ # Variables are then in general symbols which have been quantified.
442
+ #
443
+ # Here we allocate a variable (making up a name) and record with the defining formula. Quantification is done
444
+ # when the formula is completed against all in-scope variables
445
+ def universalFinish
446
+ pd = @prod_data.pop
447
+ forAll = [pd[:symbol]].flatten.compact
448
+ forAll.each do |term|
449
+ @variables[term.to_s] = { :formula => @formulae.last, :var => univar(term) }
450
+ end
451
+ end
452
+
453
+ def verbStart(prod)
454
+ @prod_data << {}
455
+ end
201
456
 
202
- def process_barename_csl(list)
203
- #add_debug(*list.info("process_barename_csl(list)"))
204
- res = [list.barename.text_value] if list.respond_to?(:barename)
205
- rest = process_barename_csl(list.barename_csl_tail) if list.respond_to?(:barename_csl_tail)
206
- rest ? res + rest : res
457
+ def verbToken(prod, tok)
458
+ term = case prod
459
+ when '<='
460
+ add_prod_data(:expression, RDF::LOG.implies)
461
+ add_prod_data(:invert, true)
462
+ when '=>'
463
+ add_prod_data(:expression, RDF::LOG.implies)
464
+ when '='
465
+ add_prod_data(:expression, RDF::OWL.sameAs)
466
+ when '@a'
467
+ add_prod_data(:expression, RDF.type)
468
+ when '@has', "@of"
469
+ # Syntactic sugar
470
+ when '@is'
471
+ add_prod_data(:invert, true)
472
+ else
473
+ error("verbToken(#{prod}, #{tok}): FIXME #{term.inspect}")
474
+ end
475
+
476
+ add_prod_data(:symbol, term)
207
477
  end
208
478
 
479
+ def verbFinish
480
+ verb = @prod_data.pop
481
+ if verb[:expression]
482
+ error("Literal may not be used as a predicate") if verb[:expression].is_a?(RDF::Literal)
483
+ error("Formula may not be used as a peredicate") if verb[:expression].is_a?(RDF::Graph)
484
+ add_prod_data(:verb, verb[:expression])
485
+ add_prod_data(:invert, true) if verb[:invert]
486
+ else
487
+ error("verbFinish: FIXME #{verb.inspect}")
488
+ end
489
+ end
490
+
491
+ private
492
+
493
+ ###################
494
+ # Utility Functions
495
+ ###################
496
+
209
497
  def process_anonnode(anonnode)
210
- add_debug(*anonnode.info("process_anonnode"))
498
+ add_debug("process_anonnode", anonnode.inspect)
211
499
  bnode = RDF::Node.new
212
500
 
213
- if anonnode.respond_to?(:property_list)
214
- properties = process_properties(anonnode.property_list)
501
+ if anonnode[:propertylist]
502
+ properties = anonnode[:propertylist]
215
503
  properties.each do |p|
216
- predicate = process_verb(p.verb)
217
- add_debug(*p.info("anonnode[#{predicate}]"))
218
- objects = process_objects(p.object_list)
504
+ predicate = p[:verb]
505
+ add_debug("process_anonnode(verb)", predicate.inspect)
506
+ objects = [p[:object]].flatten.compact
219
507
  objects.each { |object| add_triple("anonnode", bnode, predicate, object) }
220
508
  end
221
- elsif anonnode.respond_to?(:path_list)
222
- objects = process_objects(anonnode.path_list)
509
+ elsif anonnode[:pathlist]
510
+ objects = [anonnode[:pathlist]].flatten.compact
223
511
  last = objects.pop
224
512
  first_bnode = bnode
225
513
  objects.each do |object|
@@ -238,257 +526,162 @@ module RDF::N3
238
526
  bnode
239
527
  end
240
528
 
241
- def process_verb(verb)
242
- add_debug(*verb.info("process_verb"))
243
- case verb.text_value
244
- when "a"
245
- # If "a" is a keyword, then it's rdf:type, otherwise it's expanded from the default namespace
246
- if @keywords.nil? || @keywords.include?("a")
247
- RDF.type
248
- else
249
- build_uri("a")
250
- end
251
- when "@a" then RDF.type
252
- when "=" then RDF::OWL.sameAs
253
- when "=>" then RDF::LOG.implies
254
- when "<=" then RDF::LOG.implies
255
- when /^(@?is)\s+.*\s+(@?of)$/
256
- keyword_check("is") if $1 == "is"
257
- keyword_check("of") if $2 == "of"
258
- process_expression(verb.prop)
259
- when /^has\s+/
260
- keyword_check("has")
261
- process_expression(verb.prop)
262
- else
263
- if verb.respond_to?(:prop)
264
- process_expression(verb.prop)
265
- else
266
- process_expression(verb)
267
- end
268
- end
269
- end
270
-
271
- def process_expression(expression)
272
- if expression.respond_to?(:pathitem) && expression.respond_to?(:expression)
273
- add_debug(*expression.info("process_expression(pathitem && expression)"))
274
- process_path(expression) # Returns last object in chain
275
- elsif expression.respond_to?(:uri)
276
- add_debug(*expression.info("process_expression(uri)"))
277
- process_uri(expression.uri)
278
- elsif expression.respond_to?(:localname)
279
- add_debug(*expression.info("process_expression(localname)"))
280
- build_uri(expression)
281
- elsif expression.respond_to?(:anonnode)
282
- add_debug(*expression.info("process_expression(anonnode)"))
283
- process_anonnode(expression)
284
- elsif expression.respond_to?(:literal)
285
- add_debug(*expression.info("process_expression(literal)"))
286
- process_literal(expression)
287
- elsif expression.respond_to?(:numericliteral)
288
- add_debug(*expression.info("process_expression(numericliteral)"))
289
- process_numeric_literal(expression)
290
- elsif expression.respond_to?(:boolean)
291
- add_debug(*expression.info("process_expression(boolean)"))
292
- barename = expression.text_value.to_s
293
- if @keywords && !@keywords.include?(barename)
294
- build_uri(barename)
295
- else
296
- RDF::Literal.new(barename.delete("@"), :datatype => RDF::XSD.boolean, :validate => @strict, :canonicalize => true)
297
- end
298
- elsif expression.respond_to?(:barename)
299
- add_debug(*expression.info("process_expression(barename)"))
300
- barename = expression.text_value.to_s
301
-
302
- # Should only happen if @keywords is defined, and text_value is not a defined keyword
303
- case barename
304
- when "true" then RDF::Literal.new("true", :datatype => RDF::XSD.boolean)
305
- when "false" then RDF::Literal.new("false", :datatype => RDF::XSD.boolean)
306
- else
307
- # create URI using barename, unless it's in defined set, in which case it's an error
308
- raise RDF::ReaderError, %Q(Keyword "#{barename}" used as expression) if @keywords && @keywords.include?(barename)
309
- build_uri(barename)
310
- end
311
- else
312
- add_debug(*expression.info("process_expression(else)"))
313
- build_uri(expression)
314
- end
315
- end
316
-
317
529
  # Process a path, such as:
318
- # :a.:b means [is :b of :a]
319
- # :a!:b means [is :b of :a]
320
- # :a^:b means [:b :a]
530
+ # :a.:b means [is :b of :a] Deprecated
531
+ # :a!:b means [is :b of :a] => :a :b []
532
+ # :a^:b means [:b :a] => [] :b :a
321
533
  #
322
- # Elements may be strug together, with the last element the verb applied to the previous expression:
323
- # :a.:b.:c means [is :c of [ is :b of :a]]
324
- # :a!:b^:c meands [:c [ is :b of :a]]
325
- def process_path(path)
326
- add_debug(*path.info("process_path"))
534
+ # Create triple and return property used for next iteration
535
+ def process_path(expression)
536
+ add_debug("process_path", expression.inspect)
327
537
 
328
- object = process_expression(path.pathitem)
538
+ pathitem = expression[:pathitem]
539
+ pathtail = expression[:pathtail]
329
540
 
330
- # Create a list of direction/predicate pairs
331
- path_list = process_path_list(path.expression, path.respond_to?(:reverse))
332
- #puts path_list.inspect
333
- # Now we should have the following
334
- # [
335
- # [:forward, b]
336
- # [:forward, c]
337
- # ]
338
- path_list.each do |p|
339
- reverse, pred = p
541
+ direction_list = [expression[:direction], expression[:directiontail]].flatten.compact
542
+
543
+ pathtail.each do |pred|
544
+ direction = direction_list.shift
340
545
  bnode = RDF::Node.new
341
- if reverse
342
- add_triple("path(#{reverse})", bnode, pred, object)
546
+ if direction == :reverse
547
+ add_triple("process_path(reverse)", bnode, pred, pathitem)
343
548
  else
344
- add_triple("path(#{reverse})", object, pred, bnode)
549
+ add_triple("process_path(forward)", pathitem, pred, bnode)
345
550
  end
346
- object = bnode
551
+ pathitem = bnode
347
552
  end
348
- object
553
+ pathitem
349
554
  end
350
555
 
351
- # Returns array of [:forward/:reverse, element] pairs
352
- def process_path_list(path, reverse)
353
- add_debug(*path.info("process_path_list(#{reverse})"))
354
- if path.respond_to?(:pathitem)
355
- [[reverse, process_expression(path.pathitem)]] + process_path_list(path.expression, path.respond_to?(:reverse))
356
- else
357
- [[reverse, process_expression(path)]]
358
- end
359
- end
360
-
361
556
  def process_uri(uri)
362
- uri = uri.text_value if uri.respond_to?(:text_value)
363
557
  uri(@uri, RDF::NTriples.unescape(uri))
364
558
  end
365
559
 
366
- def process_properties(properties)
367
- add_debug(*properties.info("process_properties"))
368
- result = []
369
- result << properties if properties.respond_to?(:verb)
370
- result << process_properties(properties.property_list) if properties.respond_to?(:property_list)
371
- result.flatten
372
- end
373
-
374
- def process_objects(objects)
375
- add_debug(*objects.info("process_objects"))
376
- result = []
377
- if objects.respond_to?(:object)
378
- result << process_expression(objects.object)
379
- elsif objects.respond_to?(:pathitem)
380
- result << process_expression(objects)
381
- elsif objects.respond_to?(:expression)
382
- result << process_expression(objects.expression)
383
- result << process_objects(objects.path_list) if objects.respond_to?(:path_list)
384
- elsif !objects.text_value.empty? || objects.respond_to?(:nprefix)
385
- result << process_expression(objects)
560
+ def process_qname(tok)
561
+ if tok.include?(":")
562
+ prefix, name = tok.split(":")
563
+ elsif @userkeys
564
+ # If the @keywords directive is given, the keywords given will thereafter be recognized
565
+ # without a "@" prefix, and anything else is a local name in the default namespace.
566
+ prefix, name = "", tok
567
+ elsif %w(true false).include?(tok)
568
+ # The words true and false are boolean literals.
569
+ #
570
+ # They were added to Notation3 in 2006-02 in discussion with the SPARQL language developers, the Data
571
+ # Access Working Group. Note that no existing documents will have used a naked true or false word, without a
572
+ # @keyword statement which would make it clear that they were not to be treated as keywords. Furthermore any
573
+ # old parser encountering true or false naked or in a @keywords
574
+ return RDF::Literal.new(tok, :datatype => RDF::XSD.boolean)
575
+ else
576
+ error("Set user @keywords to use barenames.")
386
577
  end
387
- result << process_objects(objects.object_list) if objects.respond_to?(:object_list)
388
- result.flatten
389
- end
390
-
391
- def process_literal(object)
392
- add_debug(*object.info("process_literal"))
393
- encoding, language = nil, nil
394
- string, type = object.elements
395
578
 
396
- unless type.elements.nil?
397
- #puts type.elements.inspect
398
- if (type.elements[0].text_value=='@')
399
- language = type.elements[1].text_value
400
- else
401
- encoding = process_expression(type.elements[1])
402
- end
579
+ uri = if prefix(prefix)
580
+ add_debug('process_qname(ns)', "#{prefix(prefix)}, #{name}")
581
+ ns(prefix, name)
582
+ elsif prefix == '_'
583
+ add_debug('process_qname(bnode)', name)
584
+ bnode(name)
585
+ else
586
+ add_debug('process_qname(default_ns)', name)
587
+ namespace(nil, uri("#{@uri}#")) unless prefix(nil)
588
+ ns(nil, name)
403
589
  end
404
-
405
- # Evaluate text_value to remove redundant escapes
406
- #puts string.elements[1].text_value.dump
407
- RDF::Literal.new(RDF::NTriples.unescape(string.elements[1].text_value), :language => language, :validate => @strict, :datatype => encoding, :canonicalize => true)
590
+ add_debug('process_qname', uri.inspect)
591
+ uri
408
592
  end
409
593
 
410
- def process_numeric_literal(object)
411
- add_debug(*object.info("process_numeric_literal"))
594
+ # Add values to production data, values aranged as an array
595
+ def add_prod_data(sym, value)
596
+ case @prod_data.last[sym]
597
+ when nil
598
+ @prod_data.last[sym] = value
599
+ when Array
600
+ @prod_data.last[sym] << value
601
+ else
602
+ @prod_data.last[sym] = [@prod_data.last[sym], value]
603
+ end
604
+ end
412
605
 
413
- RDF::Literal.new(RDF::NTriples.unescape(object.text_value), :datatype => RDF::XSD[object.numericliteral], :validate => @strict, :canonicalize => true)
606
+ # Keep track of allocated BNodes
607
+ def bnode(value = nil)
608
+ @bnode_cache ||= {}
609
+ @bnode_cache[value.to_s] ||= RDF::Node.new(value)
414
610
  end
415
-
416
- def build_uri(expression)
417
- prefix = expression.respond_to?(:nprefix) ? expression.nprefix.text_value.to_s : ""
418
- localname = expression.localname.text_value if expression.respond_to?(:localname)
419
- localname ||= (expression.respond_to?(:text_value) ? expression.text_value : expression).to_s.sub(/^:/, "")
420
- localname = nil if localname.empty? # In N3/Turtle "_:" is not named
421
611
 
422
- if expression.respond_to?(:info)
423
- add_debug(*expression.info("build_uri(#{prefix.inspect}, #{localname.inspect})"))
424
- else
425
- add_debug("", "build_uri(#{prefix.inspect}, #{localname.inspect})")
612
+ def univar(label)
613
+ unless label
614
+ @unnamed_label ||= "var0"
615
+ label = @unnamed_label = @unnamed_label.succ
426
616
  end
617
+ RDF::Query::Variable.new(label.to_s)
618
+ end
427
619
 
428
- uri = if @uri_mappings[prefix]
429
- add_debug(*expression.info("build_uri: (ns): #{@uri_mappings[prefix]}, #{localname}")) if expression.respond_to?(:info)
430
- ns(prefix, localname.to_s)
431
- elsif prefix == '_'
432
- add_debug(*expression.info("build_uri: (bnode)")) if expression.respond_to?(:info)
433
- bnode(localname)
434
- elsif prefix == "rdf"
435
- add_debug(*expression.info("build_uri: (rdf)")) if expression.respond_to?(:info)
436
- # A special case
437
- RDF::RDF[localname.to_s]
438
- elsif prefix == "xsd"
439
- add_debug(*expression.info("build_uri: (xsd)")) if expression.respond_to?(:info)
440
- # A special case
441
- RDF::XSD[localname.to_s]
442
- else
443
- add_debug(*expression.info("build_uri: (default_ns)")) if expression.respond_to?(:info)
444
- @default_ns ||= uri("#{@uri}#", nil)
445
- ns(nil, localname.to_s)
620
+ # Add debug event to debug array, if specified
621
+ #
622
+ # @param [XML Node, any] node:: XML Node or string for showing context
623
+ # @param [String] message::
624
+ def add_debug(node, message)
625
+ puts "[#{@lineno},#{@pos}]#{' ' * @productions.length}#{node}: #{message}" if ::RDF::N3::debug?
626
+ @options[:debug] << "[#{@lineno},#{@pos}]#{' ' * @productions.length}#{node}: #{message}" if @options[:debug].is_a?(Array)
627
+ end
628
+
629
+ # add a statement, object can be literal or URI or bnode
630
+ #
631
+ # @param [Nokogiri::XML::Node, any] node:: XML Node or string for showing context
632
+ # @param [URI, Node] subject:: the subject of the statement
633
+ # @param [URI] predicate:: the predicate of the statement
634
+ # @param [URI, Node, Literal] object:: the object of the statement
635
+ # @return [Statement]:: Added statement
636
+ # @raise [RDF::ReaderError]:: Checks parameter types and raises if they are incorrect if parsing mode is _validate_.
637
+ def add_triple(node, subject, predicate, object)
638
+ context_opts = {:context => @formulae.last.context} if @formulae.last
639
+
640
+ # Replace graph with it's context
641
+ subject = subject.context if subject.graph?
642
+ object = object.context if object.graph?
643
+ statement = RDF::Statement.new(subject, predicate, object, context_opts || {})
644
+ add_debug(node, statement.to_s)
645
+ @callback.call(statement)
646
+ end
647
+
648
+ def namespace(prefix, uri)
649
+ uri = uri.to_s
650
+ if uri == "#"
651
+ uri = prefix(nil)
446
652
  end
447
- add_debug(*expression.info("build_uri: #{uri.inspect}")) if expression.respond_to?(:info)
448
- uri
653
+ add_debug("namesspace", "'#{prefix}' <#{uri}>")
654
+ prefix(prefix, uri(uri))
449
655
  end
450
-
656
+
451
657
  # Is this an allowable keyword?
452
658
  def keyword_check(kw)
453
659
  unless (@keywords || %w(a is of has)).include?(kw)
454
- raise RDF::ReaderError, "unqualified keyword '#{kw}' used without @keyword directive" if @strict
660
+ raise RDF::ReaderError, "unqualified keyword '#{kw}' used without @keyword directive" if validate?
455
661
  end
456
662
  end
457
663
 
458
664
  # Create URIs
459
- def uri(value, append, normalize = false)
460
- value = RDF::URI.intern(value)
665
+ def uri(value, append = nil)
666
+ value = RDF::URI.new(value)
461
667
  value = value.join(append) if append
668
+ value.validate! if validate? && value.respond_to?(:validate)
669
+ value.canonicalize! if canonicalize?
670
+ value = RDF::URI.intern(value) if intern?
671
+
672
+ # Variable substitution for in-scope variables. Variables are in scope if they are defined in anthing other than
673
+ # the current formula
674
+ var = @variables[value.to_s]
675
+ value = var[:var] if var
676
+
462
677
  value
463
678
  end
464
679
 
465
680
  def ns(prefix, suffix)
466
- prefix = prefix.nil? ? @default_ns.to_s : @uri_mappings[prefix].to_s
467
- suffix = suffix.to_s.sub(/^\#/, "") if prefix.index("#")
468
- add_debug("ns", "prefix: '#{prefix}', suffix: '#{suffix}'")
469
- RDF::URI.intern(prefix + suffix)
470
- end
471
- end
472
- end
473
-
474
- module Treetop
475
- module Runtime
476
- class SyntaxNode
477
- # Brief information about a syntax node
478
- def info(ctx = "")
479
- m = self.singleton_methods(true)
480
- if m.empty?
481
- ["@#{self.interval.first}", "#{ctx}['#{self.text_value}']"]
482
- else
483
- ["@#{self.interval.first}", "#{ctx}[" +
484
- self.singleton_methods(true).map do |mm|
485
- v = self.send(mm)
486
- v = v.text_value if v.is_a?(SyntaxNode)
487
- "#{mm}='#{v}'"
488
- end.join(", ") +
489
- "]"]
490
- end
491
- end
681
+ base = prefix(prefix).to_s
682
+ suffix = suffix.to_s.sub(/^\#/, "") if base.index("#")
683
+ add_debug("ns", "base: '#{base}', suffix: '#{suffix}'")
684
+ uri(base + suffix.to_s)
492
685
  end
493
686
  end
494
687
  end