rdf-n3 0.2.3.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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