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.
- data/.gitignore +1 -0
- data/.yardopts +4 -3
- data/{History.txt → History.md} +30 -6
- data/{README.rdoc → README.md} +56 -19
- data/Rakefile +15 -29
- data/VERSION +1 -1
- data/example-files/sp2b.n3 +50177 -0
- data/lib/rdf/n3.rb +2 -2
- data/lib/rdf/n3/reader.rb +560 -367
- data/lib/rdf/n3/reader/meta.rb +640 -0
- data/lib/rdf/n3/reader/n3-selectors.n3 +0 -0
- data/lib/rdf/n3/reader/parser.rb +229 -0
- data/lib/rdf/n3/vocab.rb +1 -0
- data/lib/rdf/n3/writer.rb +324 -265
- data/rdf-n3.gemspec +24 -26
- data/script/build_meta +242 -0
- data/script/parse +62 -13
- data/script/tc +4 -4
- data/spec/cwm_spec.rb +11 -3
- data/spec/n3reader_spec.rb +233 -63
- data/spec/rdf_helper.rb +15 -15
- data/spec/spec_helper.rb +10 -4
- data/spec/swap_spec.rb +11 -35
- data/spec/swap_test/n3parser.tests +14 -14
- data/spec/swap_test/n3parser.yml +0 -19
- data/spec/swap_test/nodeID/classes.ref.rdf +1 -1
- data/spec/swap_test/ref/contexts-1.n3 +12 -0
- data/spec/swap_test/ref/prefix2.rdf +33 -0
- data/spec/swap_test/ref/strquot.n3 +0 -1
- data/spec/swap_test/ref/xml-syntax-basic-serialization.rdf +1 -1
- data/spec/swap_test/regression.n3 +5 -5
- data/spec/swap_test/regression.yml +53 -23
- data/spec/turtle/manifest-bad.yml +91 -0
- data/spec/turtle/manifest.yml +187 -0
- data/spec/turtle_spec.rb +12 -20
- data/spec/writer_spec.rb +39 -37
- metadata +43 -48
- data/lib/rdf/n3/patches/qname_hacks.rb +0 -57
- data/lib/rdf/n3/patches/seq.rb +0 -34
- data/lib/rdf/n3/reader/n3_grammar.rb +0 -3764
- data/lib/rdf/n3/reader/n3_grammar.treetop +0 -227
- data/lib/rdf/n3/reader/n3_grammar_18.rb +0 -3764
- data/lib/rdf/n3/reader/n3_grammar_18.treetop +0 -227
- data/spec/literal_spec.rb +0 -245
data/lib/rdf/n3.rb
CHANGED
@@ -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
|
-
|
28
|
-
|
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'
|
data/lib/rdf/n3/reader.rb
CHANGED
@@ -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) # ́ 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]
|
38
|
-
#
|
39
|
-
# @option options [
|
40
|
-
#
|
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
|
44
|
-
# @
|
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
|
-
@
|
48
|
-
@
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
@
|
53
|
-
@
|
54
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
103
|
-
def
|
104
|
-
|
105
|
-
|
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
|
-
#
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
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
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
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
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
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
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
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
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
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
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
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(
|
498
|
+
add_debug("process_anonnode", anonnode.inspect)
|
211
499
|
bnode = RDF::Node.new
|
212
500
|
|
213
|
-
if anonnode
|
214
|
-
properties =
|
501
|
+
if anonnode[:propertylist]
|
502
|
+
properties = anonnode[:propertylist]
|
215
503
|
properties.each do |p|
|
216
|
-
predicate =
|
217
|
-
add_debug(
|
218
|
-
objects =
|
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
|
222
|
-
objects =
|
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
|
-
#
|
323
|
-
|
324
|
-
|
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
|
-
|
538
|
+
pathitem = expression[:pathitem]
|
539
|
+
pathtail = expression[:pathtail]
|
329
540
|
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
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("
|
546
|
+
if direction == :reverse
|
547
|
+
add_triple("process_path(reverse)", bnode, pred, pathitem)
|
343
548
|
else
|
344
|
-
add_triple("
|
549
|
+
add_triple("process_path(forward)", pathitem, pred, bnode)
|
345
550
|
end
|
346
|
-
|
551
|
+
pathitem = bnode
|
347
552
|
end
|
348
|
-
|
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
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
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
|
-
|
397
|
-
#
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
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
|
-
|
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
|
-
|
411
|
-
|
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
|
-
|
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
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
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
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
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(
|
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
|
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
|
460
|
-
value = RDF::URI.
|
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
|
-
|
467
|
-
suffix = suffix.to_s.sub(/^\#/, "") if
|
468
|
-
add_debug("ns", "
|
469
|
-
|
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
|