ld-patch 0.1.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.
- checksums.yaml +7 -0
- data/AUTHORS +1 -0
- data/LICENSE +25 -0
- data/README.md +81 -0
- data/VERSION +1 -0
- data/bin/ldpatch +91 -0
- data/lib/ld/patch.rb +79 -0
- data/lib/ld/patch/algebra.rb +24 -0
- data/lib/ld/patch/algebra/add.rb +59 -0
- data/lib/ld/patch/algebra/bind.rb +90 -0
- data/lib/ld/patch/algebra/constraint.rb +56 -0
- data/lib/ld/patch/algebra/cut.rb +56 -0
- data/lib/ld/patch/algebra/delete.rb +59 -0
- data/lib/ld/patch/algebra/index.rb +34 -0
- data/lib/ld/patch/algebra/patch.rb +40 -0
- data/lib/ld/patch/algebra/path.rb +71 -0
- data/lib/ld/patch/algebra/prefix.rb +48 -0
- data/lib/ld/patch/algebra/reverse.rb +39 -0
- data/lib/ld/patch/algebra/update_list.rb +77 -0
- data/lib/ld/patch/meta.rb +2666 -0
- data/lib/ld/patch/parser.rb +621 -0
- data/lib/ld/patch/terminals.rb +91 -0
- data/lib/ld/patch/version.rb +18 -0
- metadata +275 -0
@@ -0,0 +1,621 @@
|
|
1
|
+
require 'ebnf'
|
2
|
+
require 'ebnf/ll1/parser'
|
3
|
+
require 'ld/patch/meta'
|
4
|
+
|
5
|
+
module LD::Patch
|
6
|
+
##
|
7
|
+
# A parser for the LD Patch grammar.
|
8
|
+
#
|
9
|
+
# @see http://www.w3.org/TR/ldpatch/#concrete-syntax
|
10
|
+
# @see http://en.wikipedia.org/wiki/LR_parser
|
11
|
+
class Parser
|
12
|
+
include LD::Patch::Meta
|
13
|
+
include LD::Patch::Terminals
|
14
|
+
include EBNF::LL1::Parser
|
15
|
+
|
16
|
+
##
|
17
|
+
# Any additional options for the parser.
|
18
|
+
#
|
19
|
+
# @return [Hash]
|
20
|
+
attr_reader :options
|
21
|
+
|
22
|
+
##
|
23
|
+
# The current input string being processed.
|
24
|
+
#
|
25
|
+
# @return [String]
|
26
|
+
attr_accessor :input
|
27
|
+
|
28
|
+
##
|
29
|
+
# The current input tokens being processed.
|
30
|
+
#
|
31
|
+
# @return [Array<Token>]
|
32
|
+
attr_reader :tokens
|
33
|
+
|
34
|
+
##
|
35
|
+
# The internal representation of the result
|
36
|
+
# @return [Array]
|
37
|
+
attr_accessor :result
|
38
|
+
|
39
|
+
# Terminals passed to lexer. Order matters!
|
40
|
+
terminal(:ANON, ANON) do |prod, token, input|
|
41
|
+
input[:resource] = bnode
|
42
|
+
end
|
43
|
+
terminal(:BLANK_NODE_LABEL, BLANK_NODE_LABEL) do |prod, token, input|
|
44
|
+
input[:resource] = bnode(token.value[2..-1])
|
45
|
+
end
|
46
|
+
terminal(:IRIREF, IRIREF, unescape: true) do |prod, token, input|
|
47
|
+
begin
|
48
|
+
input[:iri] = iri(token.value[1..-2])
|
49
|
+
rescue ArgumentError => e
|
50
|
+
raise ParseError, e.message
|
51
|
+
end
|
52
|
+
end
|
53
|
+
terminal(:DOUBLE, DOUBLE) do |prod, token, input|
|
54
|
+
# Note that a Turtle Double may begin with a '.[eE]', so tack on a leading
|
55
|
+
# zero if necessary
|
56
|
+
value = token.value.sub(/\.([eE])/, '.0\1')
|
57
|
+
input[:literal] = literal(value, datatype: RDF::XSD.double)
|
58
|
+
end
|
59
|
+
terminal(:DECIMAL, DECIMAL) do |prod, token, input|
|
60
|
+
# Note that a Turtle Decimal may begin with a '.', so tack on a leading
|
61
|
+
# zero if necessary
|
62
|
+
value = token.value
|
63
|
+
value = "0#{token.value}" if token.value[0,1] == "."
|
64
|
+
input[:literal] = literal(value, datatype: RDF::XSD.decimal)
|
65
|
+
end
|
66
|
+
terminal(:INTEGER, INTEGER) do |prod, token, input|
|
67
|
+
input[:literal] = literal(token.value, datatype: RDF::XSD.integer)
|
68
|
+
end
|
69
|
+
terminal(:PNAME_LN, PNAME_LN, unescape: true) do |prod, token, input|
|
70
|
+
prefix, suffix = token.value.split(":", 2)
|
71
|
+
input[:iri] = ns(prefix, suffix)
|
72
|
+
end
|
73
|
+
terminal(:PNAME_NS, PNAME_NS) do |prod, token, input|
|
74
|
+
prefix = token.value[0..-2]
|
75
|
+
|
76
|
+
# Two contexts, one when prefix is being defined, the other when being used
|
77
|
+
case prod
|
78
|
+
when :prefixID
|
79
|
+
input[:prefix] = prefix
|
80
|
+
else
|
81
|
+
input[:iri] = ns(prefix, nil)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
terminal(:STRING_LITERAL_LONG_SINGLE_QUOTE, STRING_LITERAL_LONG_SINGLE_QUOTE, unescape: true) do |prod, token, input|
|
85
|
+
input[:string] = token.value[3..-4]
|
86
|
+
end
|
87
|
+
terminal(:STRING_LITERAL_LONG_QUOTE, STRING_LITERAL_LONG_QUOTE, unescape: true) do |prod, token, input|
|
88
|
+
input[:string] = token.value[3..-4]
|
89
|
+
end
|
90
|
+
terminal(:STRING_LITERAL_QUOTE, STRING_LITERAL_QUOTE, unescape: true) do |prod, token, input|
|
91
|
+
input[:string] = token.value[1..-2]
|
92
|
+
end
|
93
|
+
terminal(:STRING_LITERAL_SINGLE_QUOTE, STRING_LITERAL_SINGLE_QUOTE, unescape: true) do |prod, token, input|
|
94
|
+
input[:string] = token.value[1..-2]
|
95
|
+
end
|
96
|
+
terminal(:VAR1, VAR1) do |prod, token, input|
|
97
|
+
input[:resource] = variable(token.value[1..-1])
|
98
|
+
end
|
99
|
+
|
100
|
+
# Keyword terminals
|
101
|
+
terminal(nil, STR_EXPR) do |prod, token, input|
|
102
|
+
case token.value
|
103
|
+
when '^' then input[:reverse] = token.value
|
104
|
+
when '/' then input[:slash] = token.value
|
105
|
+
when '!' then input[:not] = token.value
|
106
|
+
when 'a' then input[:predicate] = (a = RDF.type.dup; a.lexical = 'a'; a)
|
107
|
+
when /true|false/ then input[:literal] = RDF::Literal::Boolean.new(token.value)
|
108
|
+
when '@prefix' then input[:prefix] = token.value
|
109
|
+
when %r{
|
110
|
+
AddNew|Add|A|
|
111
|
+
Bind|B|
|
112
|
+
Cut|C|
|
113
|
+
DeleteExisting|Delete|DE|D|
|
114
|
+
UpdateList|UL|
|
115
|
+
@prefix
|
116
|
+
}x
|
117
|
+
input[token.value.to_sym] = token.value
|
118
|
+
else
|
119
|
+
#add_prod_datum(:string, token.value)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
terminal(:LANGTAG, LANGTAG) do |prod, token, input|
|
124
|
+
add_prod_datum(:language, token.value[1..-1])
|
125
|
+
end
|
126
|
+
|
127
|
+
# [1] ldpatch ::= prologue statement*
|
128
|
+
production(:ldpatch) do |input, current, callback|
|
129
|
+
patch = Algebra::Patch.new(*current[:statements])
|
130
|
+
input[:ldpatch] = if prefixes.empty?
|
131
|
+
patch
|
132
|
+
else
|
133
|
+
Algebra::Prefix.new(prefixes.to_a, patch)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# [4] bind ::= ("Bind" | "B") VAR1 value path? "."
|
138
|
+
production(:bind) do |input, current, callback|
|
139
|
+
path = Algebra::Path.new(*Array(current[:path]))
|
140
|
+
(input[:statements] ||= []) << Algebra::Bind.new(current[:resource], current[:value], path)
|
141
|
+
end
|
142
|
+
|
143
|
+
# [5] add ::= ("Add" | "A") "{" graph "}" "."
|
144
|
+
production(:add) do |input, current, callback|
|
145
|
+
(input[:statements] ||= []) << Algebra::Add.new(current[:graph], new: false)
|
146
|
+
end
|
147
|
+
|
148
|
+
# [6] addNew ::= ("AddNew" | "AN") "{" graph "}" "."
|
149
|
+
production(:addNew) do |input, current, callback|
|
150
|
+
(input[:statements] ||= []) << Algebra::Add.new(current[:graph], new: true)
|
151
|
+
end
|
152
|
+
|
153
|
+
# [7] delete ::= ("Delete" | "D") "{" graph "}" "."
|
154
|
+
production(:delete) do |input, current, callback|
|
155
|
+
(input[:statements] ||= []) << Algebra::Delete.new(current[:graph], existing: false)
|
156
|
+
end
|
157
|
+
|
158
|
+
# [8] deleteExisting ::= ("DeleteExisting" | "DE") "{" graph "}" "."
|
159
|
+
production(:deleteExisting) do |input, current, callback|
|
160
|
+
(input[:statements] ||= []) << Algebra::Delete.new(current[:graph], existing: true)
|
161
|
+
end
|
162
|
+
|
163
|
+
# [9] cut ::= ("Cut" | "C") VAR1 "."
|
164
|
+
production(:cut) do |input, current, callback|
|
165
|
+
(input[:statements] ||= []) << Algebra::Cut.new(current[:resource])
|
166
|
+
end
|
167
|
+
|
168
|
+
# [10] updateList ::= ("UpdateList" | "UL") varOrIRI predicate slice collection "."
|
169
|
+
production(:updateList) do |input, current, callback|
|
170
|
+
var_or_iri = current[:resource] || current[:iri]
|
171
|
+
(input[:statements] ||= []) << Algebra::UpdateList.new(var_or_iri, current[:predicate], current[:slice1], current[:slice2], current[:collection].to_a)
|
172
|
+
end
|
173
|
+
|
174
|
+
# [12] value ::= iri | literal | VAR1
|
175
|
+
production(:value) do |input, current, callback|
|
176
|
+
input[:value] = current[:iri] || current[:literal] || current[:resource]
|
177
|
+
end
|
178
|
+
|
179
|
+
# [13] path ::= ( '/' step | constraint )*
|
180
|
+
# ( '/' step | constraint )
|
181
|
+
production(:_path_1) do |input, current, callback|
|
182
|
+
step = case
|
183
|
+
when current[:literal] then Algebra::Index.new(current[:literal])
|
184
|
+
when current[:constraint] then current[:constraint]
|
185
|
+
when current[:reverse] then Algebra::Reverse.new(current[:iri])
|
186
|
+
else current[:iri]
|
187
|
+
end
|
188
|
+
(input[:path] ||= []) << step
|
189
|
+
end
|
190
|
+
|
191
|
+
# [15] constraint ::= '[' path ( '=' value )? ']' | '!'
|
192
|
+
production(:constraint) do |input, current, callback|
|
193
|
+
path = Algebra::Path.new(*Array(current[:path]))
|
194
|
+
input[:constraint] = if current[:value]
|
195
|
+
Algebra::Constraint.new(path, current[:value])
|
196
|
+
elsif current[:path]
|
197
|
+
Algebra::Constraint.new(path)
|
198
|
+
else
|
199
|
+
Algebra::Constraint.new(:unique)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
# [16] slice ::= INDEX? '..' INDEX?
|
204
|
+
production(:_slice_1) do |input, current, callback|
|
205
|
+
input[:slice1] = current[:literal]
|
206
|
+
end
|
207
|
+
production(:_slice_2) do |input, current, callback|
|
208
|
+
input[:slice2] = current[:literal]
|
209
|
+
end
|
210
|
+
|
211
|
+
# [4t] prefixID defines a prefix mapping
|
212
|
+
production(:prefixID) do |input, current, callback|
|
213
|
+
prefix = current[:prefix]
|
214
|
+
iri = current[:iri]
|
215
|
+
debug("prefixID") {"Defined prefix #{prefix.inspect} mapping to #{iri.inspect}"}
|
216
|
+
prefix(prefix, iri)
|
217
|
+
end
|
218
|
+
|
219
|
+
# [18] graph ::= triples ( '.' triples )* '.'?
|
220
|
+
production(:graph) do |input, current, callback|
|
221
|
+
input[:graph] = current[:triples]
|
222
|
+
end
|
223
|
+
|
224
|
+
# [10t*] subject ::= iri | BlankNode | collection | VAR1
|
225
|
+
production(:subject) do |input, current, callback|
|
226
|
+
if list = current[:collection]
|
227
|
+
# Add collection patterns
|
228
|
+
list.each_statement do |statement|
|
229
|
+
(input[:triples] ||= []) << RDF::Query::Pattern.from(statement)
|
230
|
+
end
|
231
|
+
|
232
|
+
current[:resource] = current[:collection].subject
|
233
|
+
end
|
234
|
+
|
235
|
+
(input[:triples] ||= []).concat(current[:triples]) if current[:triples]
|
236
|
+
input[:subject] = current[:resource] || current[:iri]
|
237
|
+
end
|
238
|
+
|
239
|
+
# [11t] predicate ::= iri
|
240
|
+
production(:predicate) do |input, current, callback|
|
241
|
+
input[:predicate] = current[:iri]
|
242
|
+
end
|
243
|
+
|
244
|
+
# [12t*] object ::= iri | BlankNode | collection | blankNodePropertyList | literal | VAR1
|
245
|
+
production(:object) do |input, current, callback|
|
246
|
+
if list = current[:collection]
|
247
|
+
# Add collection patterns
|
248
|
+
list.each_statement do |statement|
|
249
|
+
(input[:triples] ||= []) << RDF::Query::Pattern.from(statement)
|
250
|
+
end
|
251
|
+
|
252
|
+
current[:resource] = current[:collection].subject
|
253
|
+
end
|
254
|
+
|
255
|
+
# Add triples from blankNodePropertyList
|
256
|
+
(input[:triples] ||= []).concat(current[:triples]) if current[:triples]
|
257
|
+
|
258
|
+
if input[:object_list]
|
259
|
+
# Part of an rdf:List collection
|
260
|
+
input[:object_list] << (current[:resource] || current[:iri] || current[:literal])
|
261
|
+
else
|
262
|
+
debug("object") {"current: #{current.inspect}"}
|
263
|
+
object = current[:resource] || current[:literal] || current[:iri]
|
264
|
+
(input[:triples] ||= []) << RDF::Query::Pattern.new(input[:subject], input[:predicate], object)
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
# [14t] blankNodePropertyList ::= "[" predicateObjectList "]"
|
269
|
+
start_production(:blankNodePropertyList) do |input, current, callback|
|
270
|
+
current[:subject] = self.bnode
|
271
|
+
end
|
272
|
+
|
273
|
+
production(:blankNodePropertyList) do |input, current, callback|
|
274
|
+
input[:subject] = input[:resource] = current[:subject]
|
275
|
+
(input[:triples] ||= []).concat(current[:triples]) if current[:triples]
|
276
|
+
end
|
277
|
+
|
278
|
+
# [15t] collection ::= "(" object* ")"
|
279
|
+
start_production(:collection) do |input, current, callback|
|
280
|
+
# Tells the object production to collect and not generate statements
|
281
|
+
current[:object_list] = []
|
282
|
+
end
|
283
|
+
|
284
|
+
production(:collection) do |input, current, callback|
|
285
|
+
# Create an RDF list
|
286
|
+
objects = current[:object_list]
|
287
|
+
(input[:triples] ||= []).concat(current[:triples]) if current[:triples]
|
288
|
+
input[:collection] = RDF::List[*objects]
|
289
|
+
end
|
290
|
+
|
291
|
+
# [129s] RDFLiteral ::= String ( LANGTAG | ( '^^' iri ) )?
|
292
|
+
production(:RDFLiteral) do |input, current, callback|
|
293
|
+
if current[:string]
|
294
|
+
lit = current.dup
|
295
|
+
str = lit.delete(:string)
|
296
|
+
lit[:datatype] = lit.delete(:iri) if lit[:iri]
|
297
|
+
lit[:language] = lit.delete(:language).last.downcase if lit[:language]
|
298
|
+
input[:literal] = RDF::Literal.new(str, lit) if str
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
##
|
303
|
+
# Initializes a new parser instance.
|
304
|
+
#
|
305
|
+
# @param [String, IO, StringIO, #to_s] input
|
306
|
+
# @param [Hash{Symbol => Object}] options
|
307
|
+
# @option options [#to_s] :base_uri (nil)
|
308
|
+
# the base URI to use when resolving relative URIs
|
309
|
+
# @option options [#to_s] :anon_base ("b0")
|
310
|
+
# Basis for generating anonymous Nodes
|
311
|
+
# @option options [Boolean] :resolve_iris (false)
|
312
|
+
# Resolve prefix and relative IRIs, otherwise, when serializing the parsed SSE as S-Expressions, use the original prefixed and relative URIs along with `base` and `prefix` definitions.
|
313
|
+
# @option options [Boolean] :validate (false)
|
314
|
+
# whether to validate the parsed statements and values
|
315
|
+
# @option options [Array] :errors
|
316
|
+
# array for placing errors found when parsing
|
317
|
+
# @option options [Array] :warnings
|
318
|
+
# array for placing warnings found when parsing
|
319
|
+
# @option options [Boolean] :progress
|
320
|
+
# Show progress of parser productions
|
321
|
+
# @option options [Boolean] :debug
|
322
|
+
# Detailed debug output
|
323
|
+
# @yield [parser] `self`
|
324
|
+
# @yieldparam [LD::Patch::Parser] parser
|
325
|
+
# @return [LD::Patch::Parser] The parser instance, or result returned from block
|
326
|
+
def initialize(input = nil, options = {}, &block)
|
327
|
+
@input = case input
|
328
|
+
when IO, StringIO then input.read
|
329
|
+
else input.to_s.dup
|
330
|
+
end
|
331
|
+
@input.encode!(Encoding::UTF_8) if @input.respond_to?(:encode!)
|
332
|
+
@options = {anon_base: "b0", validate: false}.merge(options)
|
333
|
+
@errors = @options[:errors]
|
334
|
+
@options[:debug] ||= case
|
335
|
+
when options[:progress] then 2
|
336
|
+
when options[:validate] then (@errors ? nil : 1)
|
337
|
+
end
|
338
|
+
|
339
|
+
debug("base IRI") {base_uri.inspect}
|
340
|
+
debug("validate") {validate?.inspect}
|
341
|
+
|
342
|
+
@vars = {}
|
343
|
+
|
344
|
+
if block_given?
|
345
|
+
case block.arity
|
346
|
+
when 0 then instance_eval(&block)
|
347
|
+
else block.call(self)
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
##
|
353
|
+
# Returns `true` if the input string is syntactically valid.
|
354
|
+
#
|
355
|
+
# @return [Boolean]
|
356
|
+
def valid?
|
357
|
+
parse
|
358
|
+
true
|
359
|
+
rescue ParseError
|
360
|
+
false
|
361
|
+
end
|
362
|
+
|
363
|
+
# @return [String]
|
364
|
+
def to_sxp_bin
|
365
|
+
@result
|
366
|
+
end
|
367
|
+
|
368
|
+
def to_s
|
369
|
+
@result.to_sxp
|
370
|
+
end
|
371
|
+
|
372
|
+
##
|
373
|
+
# Accumulated errors found during processing
|
374
|
+
# @return [Array<String>]
|
375
|
+
attr_reader :errors
|
376
|
+
|
377
|
+
alias_method :ll1_parse, :parse
|
378
|
+
# Parse patch
|
379
|
+
#
|
380
|
+
# The result is an S-List. Productions return an array such as the following:
|
381
|
+
#
|
382
|
+
# (prefix ((: <http://example/>))
|
383
|
+
#
|
384
|
+
# @param [Symbol, #to_s] prod The starting production for the parser.
|
385
|
+
# It may be a URI from the grammar, or a symbol representing the local_name portion of the grammar URI.
|
386
|
+
# @return [SPARQL::Algebra::Operator, Array]
|
387
|
+
# @raise [ParseError] when illegal grammar detected.
|
388
|
+
def parse(prod = START)
|
389
|
+
ll1_parse(@input, prod.to_sym, @options.merge(branch: BRANCH,
|
390
|
+
first: FIRST,
|
391
|
+
follow: FOLLOW,
|
392
|
+
whitespace: WS)
|
393
|
+
) do |context, *data|
|
394
|
+
case context
|
395
|
+
when :trace
|
396
|
+
level, lineno, depth, *args = data
|
397
|
+
message = args.to_sse
|
398
|
+
d_str = depth > 100 ? ' ' * 100 + '+' : ' ' * depth
|
399
|
+
str = "[#{lineno}](#{level})#{d_str}#{message}".chop
|
400
|
+
if @errors && level == 0
|
401
|
+
@errors << str
|
402
|
+
else
|
403
|
+
case @options[:debug]
|
404
|
+
when Array
|
405
|
+
@options[:debug] << str
|
406
|
+
when TrueClass
|
407
|
+
$stderr.puts str
|
408
|
+
when Integer
|
409
|
+
$stderr.puts(str) if level <= @options[:debug]
|
410
|
+
end
|
411
|
+
end
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
# The last thing on the @prod_data stack is the result
|
416
|
+
@result = case
|
417
|
+
when !prod_data.is_a?(Hash)
|
418
|
+
prod_data
|
419
|
+
when prod_data.empty?
|
420
|
+
nil
|
421
|
+
when prod_data[:ldpatch]
|
422
|
+
prod_data[:ldpatch]
|
423
|
+
else
|
424
|
+
key = prod_data.keys.first
|
425
|
+
[key] + Array(prod_data[key]) # Creates [:key, [:triple], ...]
|
426
|
+
end
|
427
|
+
|
428
|
+
# Validate resulting expression
|
429
|
+
@result.validate! if @result && validate?
|
430
|
+
@result
|
431
|
+
rescue EBNF::LL1::Parser::Error, EBNF::LL1::Lexer::Error => e
|
432
|
+
raise LD::Patch::ParseError.new(e.message, lineno: e.lineno, token: e.token)
|
433
|
+
end
|
434
|
+
|
435
|
+
##
|
436
|
+
# Returns the Base URI defined for the parser,
|
437
|
+
# as specified or when parsing a BASE prologue element.
|
438
|
+
#
|
439
|
+
# @example
|
440
|
+
# base #=> RDF::URI('http://example.com/')
|
441
|
+
#
|
442
|
+
# @return [HRDF::URI]
|
443
|
+
def base_uri
|
444
|
+
RDF::URI(@options[:base_uri])
|
445
|
+
end
|
446
|
+
|
447
|
+
##
|
448
|
+
# Set the Base URI to use for this parser.
|
449
|
+
#
|
450
|
+
# @param [RDF::URI, #to_s] iri
|
451
|
+
#
|
452
|
+
# @example
|
453
|
+
# base_uri = RDF::URI('http://purl.org/dc/terms/')
|
454
|
+
#
|
455
|
+
# @return [RDF::URI]
|
456
|
+
def base_uri=(iri)
|
457
|
+
@options[:base_uri] = RDF::URI(iri)
|
458
|
+
end
|
459
|
+
|
460
|
+
##
|
461
|
+
# Returns the URI prefixes currently defined for this parser.
|
462
|
+
#
|
463
|
+
# @example
|
464
|
+
# prefixes[:dc] #=> RDF::URI('http://purl.org/dc/terms/')
|
465
|
+
#
|
466
|
+
# @return [Hash{Symbol => RDF::URI}]
|
467
|
+
# @since 0.3.0
|
468
|
+
def prefixes
|
469
|
+
@options[:prefixes] ||= {}
|
470
|
+
end
|
471
|
+
|
472
|
+
##
|
473
|
+
# Defines the given named URI prefix for this parser.
|
474
|
+
#
|
475
|
+
# @example Defining a URI prefix
|
476
|
+
# prefix :dc, RDF::URI('http://purl.org/dc/terms/')
|
477
|
+
#
|
478
|
+
# @example Returning a URI prefix
|
479
|
+
# prefix(:dc) #=> RDF::URI('http://purl.org/dc/terms/')
|
480
|
+
#
|
481
|
+
# @overload prefix(name, uri)
|
482
|
+
# @param [Symbol, #to_s] name
|
483
|
+
# @param [RDF::URI, #to_s] uri
|
484
|
+
#
|
485
|
+
# @overload prefix(name)
|
486
|
+
# @param [Symbol, #to_s] name
|
487
|
+
#
|
488
|
+
# @return [RDF::URI]
|
489
|
+
def prefix(name = nil, iri = nil)
|
490
|
+
name = name.to_s.empty? ? nil : (name.respond_to?(:to_sym) ? name.to_sym : name.to_s.to_sym)
|
491
|
+
iri.nil? ? prefixes[name] : prefixes[name] = iri
|
492
|
+
end
|
493
|
+
|
494
|
+
private
|
495
|
+
##
|
496
|
+
# Returns `true` if parsed statements and values should be validated.
|
497
|
+
#
|
498
|
+
# @return [Boolean] `true` or `false`
|
499
|
+
# @since 0.3.0
|
500
|
+
def resolve_iris?
|
501
|
+
@options[:resolve_iris]
|
502
|
+
end
|
503
|
+
|
504
|
+
##
|
505
|
+
# Returns `true` when resolving IRIs, otherwise BASE and PREFIX are retained in the output algebra.
|
506
|
+
#
|
507
|
+
# @return [Boolean] `true` or `false`
|
508
|
+
# @since 1.0.3
|
509
|
+
def validate?
|
510
|
+
@options[:validate]
|
511
|
+
end
|
512
|
+
|
513
|
+
##
|
514
|
+
# Return variable allocated to an ID.
|
515
|
+
# If no ID is provided, a new variable
|
516
|
+
# is allocated. Otherwise, any previous assignment will be used.
|
517
|
+
#
|
518
|
+
# The variable has a #distinguished? method applied depending on if this
|
519
|
+
# is a disinguished or non-distinguished variable. Non-distinguished
|
520
|
+
# variables are effectively the same as BNodes.
|
521
|
+
# @return [RDF::Query::Variable]
|
522
|
+
def variable(id, distinguished = true)
|
523
|
+
id = nil if id.to_s.empty?
|
524
|
+
|
525
|
+
if id
|
526
|
+
@vars[id] ||= begin
|
527
|
+
v = RDF::Query::Variable.new(id)
|
528
|
+
v.distinguished = distinguished
|
529
|
+
v
|
530
|
+
end
|
531
|
+
else
|
532
|
+
unless distinguished
|
533
|
+
# Allocate a non-distinguished variable identifier
|
534
|
+
id = @nd_var_gen
|
535
|
+
@nd_var_gen = id.succ
|
536
|
+
end
|
537
|
+
v = RDF::Query::Variable.new(id)
|
538
|
+
v.distinguished = distinguished
|
539
|
+
v
|
540
|
+
end
|
541
|
+
end
|
542
|
+
|
543
|
+
# Used for generating BNode labels
|
544
|
+
attr_accessor :nd_var_gen
|
545
|
+
|
546
|
+
# Reset the bnode cache, always generating new nodes, and start generating BNodes instead of non-distinguished variables
|
547
|
+
def clear_bnode_cache
|
548
|
+
@nd_var_gen = false
|
549
|
+
@bnode_cache = {}
|
550
|
+
end
|
551
|
+
|
552
|
+
# Generate a BNode identifier
|
553
|
+
def bnode(id = nil)
|
554
|
+
if @nd_var_gen
|
555
|
+
# Use non-distinguished variables within patterns
|
556
|
+
variable(id, false)
|
557
|
+
else
|
558
|
+
unless id
|
559
|
+
id = @options[:anon_base]
|
560
|
+
@options[:anon_base] = @options[:anon_base].succ
|
561
|
+
end
|
562
|
+
# Don't use provided ID to avoid aliasing issues when re-serializing the graph, when the bnode identifiers are re-used
|
563
|
+
(@bnode_cache ||= {})[id.to_s] ||= begin
|
564
|
+
new_bnode = RDF::Node.new
|
565
|
+
new_bnode.lexical = "_:#{id}"
|
566
|
+
new_bnode
|
567
|
+
end
|
568
|
+
end
|
569
|
+
end
|
570
|
+
|
571
|
+
# Create URIs
|
572
|
+
def iri(value)
|
573
|
+
# If we have a base URI, use that when constructing a new URI
|
574
|
+
iri = if base_uri
|
575
|
+
u = base_uri.join(value.to_s)
|
576
|
+
u.lexical = "<#{value}>" unless u.to_s == value.to_s || resolve_iris?
|
577
|
+
u
|
578
|
+
else
|
579
|
+
RDF::URI(value)
|
580
|
+
end
|
581
|
+
|
582
|
+
iri.validate! if validate? && iri.respond_to?(:validate)
|
583
|
+
#iri = RDF::URI.intern(iri) if intern?
|
584
|
+
iri
|
585
|
+
end
|
586
|
+
|
587
|
+
def ns(prefix, suffix)
|
588
|
+
error("pname", "undefined prefix #{prefix.inspect}") unless prefix(prefix)
|
589
|
+
base = prefix(prefix).to_s
|
590
|
+
suffix = suffix.to_s.sub(/^\#/, "") if base.index("#")
|
591
|
+
debug {"ns(#{prefix.inspect}): base: '#{base}', suffix: '#{suffix}'"}
|
592
|
+
iri = iri(base + suffix.to_s)
|
593
|
+
# Cause URI to be serialized as a lexical
|
594
|
+
iri.lexical = "#{prefix}:#{suffix}" unless resolve_iris?
|
595
|
+
iri
|
596
|
+
end
|
597
|
+
|
598
|
+
# Create a literal
|
599
|
+
def literal(value, options = {})
|
600
|
+
options = options.dup
|
601
|
+
# Internal representation is to not use xsd:string, although it could arguably go the other way.
|
602
|
+
options.delete(:datatype) if options[:datatype] == RDF::XSD.string
|
603
|
+
debug("literal") do
|
604
|
+
"value: #{value.inspect}, " +
|
605
|
+
"options: #{options.inspect}, " +
|
606
|
+
"validate: #{validate?.inspect}, "
|
607
|
+
end
|
608
|
+
RDF::Literal.new(value, options.merge(validate: validate?))
|
609
|
+
end
|
610
|
+
end
|
611
|
+
end
|
612
|
+
|
613
|
+
|
614
|
+
# Update RDF::Node to set lexical representation of BNode
|
615
|
+
##
|
616
|
+
# Extensions for RDF::URI
|
617
|
+
class RDF::Node
|
618
|
+
# Original lexical value of this URI to allow for round-trip serialization.
|
619
|
+
def lexical=(value); @lexical = value; end
|
620
|
+
def lexical; @lexical; end
|
621
|
+
end
|