shex 0.4.0 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/AUTHORS +1 -1
- data/LICENSE +1 -1
- data/README.md +45 -30
- data/VERSION +1 -1
- data/etc/doap.ttl +15 -14
- data/lib/shex.rb +22 -21
- data/lib/shex/algebra.rb +48 -36
- data/lib/shex/algebra/and.rb +1 -1
- data/lib/shex/algebra/annotation.rb +1 -1
- data/lib/shex/algebra/each_of.rb +1 -1
- data/lib/shex/algebra/language.rb +22 -0
- data/lib/shex/algebra/node_constraint.rb +13 -4
- data/lib/shex/algebra/not.rb +2 -2
- data/lib/shex/algebra/one_of.rb +1 -1
- data/lib/shex/algebra/operator.rb +70 -44
- data/lib/shex/algebra/or.rb +1 -1
- data/lib/shex/algebra/schema.rb +107 -34
- data/lib/shex/algebra/semact.rb +9 -9
- data/lib/shex/algebra/shape.rb +1 -1
- data/lib/shex/algebra/shape_expression.rb +1 -1
- data/lib/shex/algebra/stem.rb +47 -3
- data/lib/shex/algebra/stem_range.rb +71 -3
- data/lib/shex/algebra/triple_constraint.rb +1 -1
- data/lib/shex/algebra/value.rb +1 -1
- data/lib/shex/extensions/extension.rb +2 -2
- data/lib/shex/extensions/test.rb +18 -16
- data/lib/shex/format.rb +110 -0
- data/lib/shex/meta.rb +4282 -2334
- data/lib/shex/parser.rb +242 -86
- data/lib/shex/shex_context.rb +64 -70
- data/lib/shex/terminals.rb +36 -21
- metadata +31 -30
data/lib/shex/algebra/and.rb
CHANGED
@@ -19,7 +19,7 @@ module ShEx::Algebra
|
|
19
19
|
# Creates an operator instance from a parsed ShExJ representation
|
20
20
|
# @param (see Operator#from_shexj)
|
21
21
|
# @return [Operator]
|
22
|
-
def self.from_shexj(operator, options
|
22
|
+
def self.from_shexj(operator, **options)
|
23
23
|
raise ArgumentError unless operator.is_a?(Hash) && operator['type'] == 'ShapeAnd'
|
24
24
|
raise ArgumentError, "missing shapeExprs in #{operator.inspect}" unless operator.has_key?('shapeExprs')
|
25
25
|
super
|
@@ -7,7 +7,7 @@ module ShEx::Algebra
|
|
7
7
|
# Creates an operator instance from a parsed ShExJ representation
|
8
8
|
# @param (see Operator#from_shexj)
|
9
9
|
# @return [Operator]
|
10
|
-
def self.from_shexj(operator, options
|
10
|
+
def self.from_shexj(operator, **options)
|
11
11
|
raise ArgumentError unless operator.is_a?(Hash) && operator['type'] == "Annotation"
|
12
12
|
raise ArgumentError, "missing predicate in #{operator.inspect}" unless operator.has_key?('predicate')
|
13
13
|
raise ArgumentError, "missing object in #{operator.inspect}" unless operator.has_key?('object')
|
data/lib/shex/algebra/each_of.rb
CHANGED
@@ -8,7 +8,7 @@ module ShEx::Algebra
|
|
8
8
|
# Creates an operator instance from a parsed ShExJ representation
|
9
9
|
# @param (see Operator#from_shexj)
|
10
10
|
# @return [Operator]
|
11
|
-
def self.from_shexj(operator, options
|
11
|
+
def self.from_shexj(operator, **options)
|
12
12
|
raise ArgumentError unless operator.is_a?(Hash) && operator['type'] == 'EachOf'
|
13
13
|
raise ArgumentError, "missing expressions in #{operator.inspect}" unless operator.has_key?('expressions')
|
14
14
|
super
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module ShEx::Algebra
|
2
|
+
##
|
3
|
+
class Language < Operator::Unary
|
4
|
+
NAME = :language
|
5
|
+
|
6
|
+
##
|
7
|
+
# matches any literal having a language tag that matches value
|
8
|
+
def match?(value, depth: 0)
|
9
|
+
status "", depth: depth
|
10
|
+
if case expr = operands.first
|
11
|
+
when RDF::Literal then value.language == expr.to_s.to_sym
|
12
|
+
else false
|
13
|
+
end
|
14
|
+
status "matched #{value}", depth: depth
|
15
|
+
true
|
16
|
+
else
|
17
|
+
status "not matched #{value}", depth: depth
|
18
|
+
false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
1
2
|
module ShEx::Algebra
|
2
3
|
##
|
3
4
|
class NodeConstraint < Operator
|
@@ -8,7 +9,7 @@ module ShEx::Algebra
|
|
8
9
|
# Creates an operator instance from a parsed ShExJ representation
|
9
10
|
# @param (see Operator#from_shexj)
|
10
11
|
# @return [Operator]
|
11
|
-
def self.from_shexj(operator, options
|
12
|
+
def self.from_shexj(operator, **options)
|
12
13
|
raise ArgumentError unless operator.is_a?(Hash) && operator['type'] == 'NodeConstraint'
|
13
14
|
super
|
14
15
|
end
|
@@ -58,7 +59,7 @@ module ShEx::Algebra
|
|
58
59
|
return true unless dt
|
59
60
|
|
60
61
|
not_satisfied "Node was #{value.inspect}, expected datatype #{dt}", depth: depth unless
|
61
|
-
value.is_a?(RDF::Literal) && value.datatype == RDF::URI(dt)
|
62
|
+
value.is_a?(RDF::Literal) && value.datatype == RDF::URI(dt) && value.valid?
|
62
63
|
status "right datatype: #{value}: #{dt}", depth: depth
|
63
64
|
true
|
64
65
|
end
|
@@ -68,11 +69,18 @@ module ShEx::Algebra
|
|
68
69
|
# Checks all length/minlength/maxlength/pattern facets against the string representation of the value.
|
69
70
|
# @return [Boolean] `true` if satisfied, `false` if it does not apply
|
70
71
|
# @raise [ShEx::NotSatisfied] if not satisfied
|
72
|
+
# @todo using the XPath regexp engine supports additional flags "s" and "q"
|
71
73
|
def satisfies_string_facet?(value, depth: 0)
|
72
74
|
length = op_fetch(:length)
|
73
75
|
minlength = op_fetch(:minlength)
|
74
76
|
maxlength = op_fetch(:maxlength)
|
75
|
-
|
77
|
+
pat = (operands.detect {|op| op.is_a?(Array) && op[0] == :pattern} || [])
|
78
|
+
pattern = pat[1]
|
79
|
+
|
80
|
+
flags = 0
|
81
|
+
flags |= Regexp::EXTENDED if pat[2].to_s.include?("x")
|
82
|
+
flags |= Regexp::IGNORECASE if pat[2].to_s.include?("i")
|
83
|
+
flags |= Regexp::MULTILINE if pat[2].to_s.include?("m")
|
76
84
|
|
77
85
|
return true if (length || minlength || maxlength || pattern).nil?
|
78
86
|
|
@@ -80,6 +88,7 @@ module ShEx::Algebra
|
|
80
88
|
when RDF::Node then value.id
|
81
89
|
else value.to_s
|
82
90
|
end
|
91
|
+
|
83
92
|
not_satisfied "Node #{v_s.inspect} length not #{length}", depth: depth if
|
84
93
|
length && v_s.length != length.to_i
|
85
94
|
not_satisfied"Node #{v_s.inspect} length < #{minlength}", depth: depth if
|
@@ -87,7 +96,7 @@ module ShEx::Algebra
|
|
87
96
|
not_satisfied "Node #{v_s.inspect} length > #{maxlength}", depth: depth if
|
88
97
|
maxlength && v_s.length > maxlength.to_i
|
89
98
|
not_satisfied "Node #{v_s.inspect} does not match #{pattern}", depth: depth if
|
90
|
-
pattern && !Regexp.new(pattern).match(v_s)
|
99
|
+
pattern && !Regexp.new(pattern, flags).match(v_s)
|
91
100
|
status "right string facet: #{value}", depth: depth
|
92
101
|
true
|
93
102
|
end
|
data/lib/shex/algebra/not.rb
CHANGED
@@ -8,7 +8,7 @@ module ShEx::Algebra
|
|
8
8
|
# Creates an operator instance from a parsed ShExJ representation
|
9
9
|
# @param (see Operator#from_shexj)
|
10
10
|
# @return [Operator]
|
11
|
-
def self.from_shexj(operator, options
|
11
|
+
def self.from_shexj(operator, **options)
|
12
12
|
raise ArgumentError unless operator.is_a?(Hash) && operator['type'] == 'ShapeNot'
|
13
13
|
raise ArgumentError, "missing shapeExpr in #{operator.inspect}" unless operator.has_key?('shapeExpr')
|
14
14
|
super
|
@@ -19,7 +19,7 @@ module ShEx::Algebra
|
|
19
19
|
# @param (see ShapeExpression#satisfies?)
|
20
20
|
# @return (see ShapeExpression#satisfies?)
|
21
21
|
# @raise (see ShapeExpression#satisfies?)
|
22
|
-
# @see [
|
22
|
+
# @see [http://shex.io/shex-semantics/#shape-expression-semantics]
|
23
23
|
def satisfies?(focus, depth: 0)
|
24
24
|
status ""
|
25
25
|
op = expressions.last
|
data/lib/shex/algebra/one_of.rb
CHANGED
@@ -8,7 +8,7 @@ module ShEx::Algebra
|
|
8
8
|
# Creates an operator instance from a parsed ShExJ representation
|
9
9
|
# @param (see Operator#from_shexj)
|
10
10
|
# @return [Operator]
|
11
|
-
def self.from_shexj(operator, options
|
11
|
+
def self.from_shexj(operator, **options)
|
12
12
|
raise ArgumentError unless operator.is_a?(Hash) && operator['type'] == 'OneOf'
|
13
13
|
raise ArgumentError, "missing expressions in #{operator.inspect}" unless operator.has_key?('expressions')
|
14
14
|
super
|
@@ -26,7 +26,7 @@ module ShEx::Algebra
|
|
26
26
|
# @overload initialize(*operands)
|
27
27
|
# @param [Array<RDF::Term>] operands
|
28
28
|
#
|
29
|
-
# @overload initialize(*operands, options)
|
29
|
+
# @overload initialize(*operands, **options)
|
30
30
|
# @param [Array<RDF::Term>] operands
|
31
31
|
# @param [Hash{Symbol => Object}] options
|
32
32
|
# any additional options
|
@@ -244,7 +244,7 @@ module ShEx::Algebra
|
|
244
244
|
# @return [String]
|
245
245
|
def to_sxp
|
246
246
|
begin
|
247
|
-
require 'sxp' # @see
|
247
|
+
require 'sxp' # @see https://rubygems.org/gems/sxp
|
248
248
|
rescue LoadError
|
249
249
|
abort "SPARQL::Algebra::Operator#to_sxp requires the SXP gem (hint: `gem install sxp')."
|
250
250
|
end
|
@@ -260,24 +260,29 @@ module ShEx::Algebra
|
|
260
260
|
# @option options [RDF::URI] :base
|
261
261
|
# @option options [Hash{String => RDF::URI}] :prefixes
|
262
262
|
# @return [Operator]
|
263
|
-
def self.from_shexj(operator, options
|
263
|
+
def self.from_shexj(operator, **options)
|
264
264
|
options[:context] ||= JSON::LD::Context.parse(ShEx::CONTEXT)
|
265
265
|
operands = []
|
266
266
|
id = nil
|
267
267
|
|
268
268
|
operator.each do |k, v|
|
269
269
|
case k
|
270
|
-
when /length|
|
271
|
-
when 'id' then id = iri(v, options)
|
272
|
-
when '
|
270
|
+
when /length|clusive|digits/ then operands << [k.to_sym, RDF::Literal(v)]
|
271
|
+
when 'id' then id = iri(v, **options)
|
272
|
+
when 'flags' then ; # consumed in pattern below
|
273
|
+
when 'min', 'max' then operands << [k.to_sym, (v == -1 ? '*' : v)]
|
273
274
|
when 'inverse', 'closed' then operands << k.to_sym
|
274
275
|
when 'nodeKind' then operands << v.to_sym
|
275
|
-
when 'object' then operands << value(v, options)
|
276
|
+
when 'object' then operands << value(v, **options)
|
277
|
+
when 'languageTag' then operands << v
|
278
|
+
when 'pattern'
|
279
|
+
# Include flags as well
|
280
|
+
operands << [:pattern, RDF::Literal(v), operator['flags']].compact
|
276
281
|
when 'start'
|
277
282
|
if v.is_a?(String)
|
278
|
-
operands << Start.new(iri(v, options))
|
283
|
+
operands << Start.new(iri(v, **options))
|
279
284
|
else
|
280
|
-
operands << Start.new(ShEx::Algebra.from_shexj(v, options))
|
285
|
+
operands << Start.new(ShEx::Algebra.from_shexj(v, **options))
|
281
286
|
end
|
282
287
|
when '@context' then
|
283
288
|
options[:context] = JSON::LD::Context.parse(v)
|
@@ -285,39 +290,47 @@ module ShEx::Algebra
|
|
285
290
|
when 'shapes'
|
286
291
|
operands << case v
|
287
292
|
when Array
|
288
|
-
[:shapes] + v.map {|vv| ShEx::Algebra.from_shexj(vv, options)}
|
293
|
+
[:shapes] + v.map {|vv| ShEx::Algebra.from_shexj(vv, **options)}
|
289
294
|
else
|
290
295
|
raise "Expected value of shapes #{v.inspect}"
|
291
296
|
end
|
292
297
|
when 'stem', 'name'
|
293
298
|
# Value may be :wildcard for stem
|
294
|
-
|
295
|
-
|
299
|
+
if [IriStem, IriStemRange, SemAct].include?(self)
|
300
|
+
operands << (v.is_a?(Symbol) ? v : value(v, **options))
|
301
|
+
else
|
302
|
+
operands << v
|
303
|
+
end
|
304
|
+
when 'predicate' then operands << [:predicate, iri(v, **options)]
|
296
305
|
when 'extra', 'datatype'
|
297
306
|
v = [v] unless v.is_a?(Array)
|
298
|
-
operands << (v.map {|op| iri(op, options)}).unshift(k.to_sym)
|
307
|
+
operands << (v.map {|op| iri(op, **options)}).unshift(k.to_sym)
|
299
308
|
when 'exclusions'
|
300
309
|
v = [v] unless v.is_a?(Array)
|
301
310
|
operands << v.map do |op|
|
302
|
-
op.is_a?(Hash) ?
|
303
|
-
ShEx::Algebra.from_shexj(op, options)
|
304
|
-
|
311
|
+
if op.is_a?(Hash) && op.has_key?('type')
|
312
|
+
ShEx::Algebra.from_shexj(op, **options)
|
313
|
+
elsif [IriStem, IriStemRange].include?(self)
|
314
|
+
value(op, **options)
|
315
|
+
else
|
316
|
+
RDF::Literal(op)
|
317
|
+
end
|
305
318
|
end.unshift(:exclusions)
|
306
319
|
when 'semActs', 'startActs', 'annotations'
|
307
320
|
v = [v] unless v.is_a?(Array)
|
308
|
-
operands += v.map {|op| ShEx::Algebra.from_shexj(op, options)}
|
321
|
+
operands += v.map {|op| ShEx::Algebra.from_shexj(op, **options)}
|
309
322
|
when 'expression', 'expressions', 'shapeExpr', 'shapeExprs', 'valueExpr'
|
310
323
|
v = [v] unless v.is_a?(Array)
|
311
324
|
operands += v.map do |op|
|
312
325
|
# It's a URI reference to a Shape
|
313
|
-
op.is_a?(String) ? iri(op, options) : ShEx::Algebra.from_shexj(op, options)
|
326
|
+
op.is_a?(String) ? iri(op, **options) : ShEx::Algebra.from_shexj(op, **options)
|
314
327
|
end
|
315
328
|
when 'code'
|
316
329
|
operands << v
|
317
330
|
when 'values'
|
318
331
|
v = [v] unless v.is_a?(Array)
|
319
332
|
operands += v.map do |op|
|
320
|
-
Value.new(value(op, options))
|
333
|
+
Value.new(value(op, **options))
|
321
334
|
end
|
322
335
|
end
|
323
336
|
end
|
@@ -345,11 +358,18 @@ module ShEx::Algebra
|
|
345
358
|
when Array
|
346
359
|
# First element should be a symbol
|
347
360
|
case sym = op.first
|
348
|
-
when :datatype
|
349
|
-
|
350
|
-
|
361
|
+
when :datatype then obj['datatype'] = op.last.to_s
|
362
|
+
when :exclusions
|
363
|
+
obj['exclusions'] = Array(op[1..-1]).map do |v|
|
364
|
+
case v
|
365
|
+
when Operator then v.to_h
|
366
|
+
else v.to_s
|
367
|
+
end
|
368
|
+
end
|
351
369
|
when :extra then (obj['extra'] ||= []).concat Array(op[1..-1]).map(&:to_s)
|
352
|
-
|
370
|
+
when :pattern
|
371
|
+
obj['pattern'] = op[1]
|
372
|
+
obj['flags'] = op[2] if op[2]
|
353
373
|
when :shapes then obj['shapes'] = Array(op[1..-1]).map {|v| v.to_h}
|
354
374
|
when :minlength,
|
355
375
|
:maxlength,
|
@@ -360,7 +380,7 @@ module ShEx::Algebra
|
|
360
380
|
:maxexclusive,
|
361
381
|
:totaldigits,
|
362
382
|
:fractiondigits then obj[op.first.to_s] = op.last.object
|
363
|
-
when :min, :max then obj[op.first.to_s] = op.last
|
383
|
+
when :min, :max then obj[op.first.to_s] = op.last == '*' ? -1 : op.last
|
364
384
|
when :predicate then obj[op.first.to_s] = op.last.to_s
|
365
385
|
when :base, :prefix
|
366
386
|
# Ignore base and prefix
|
@@ -378,13 +398,18 @@ module ShEx::Algebra
|
|
378
398
|
end
|
379
399
|
when RDF::Value
|
380
400
|
case self
|
381
|
-
when Stem, StemRange
|
401
|
+
when Stem, StemRange
|
402
|
+
obj['stem'] = case op
|
403
|
+
when Operator then op.to_h
|
404
|
+
else op.to_s
|
405
|
+
end
|
382
406
|
when SemAct then obj[op.is_a?(RDF::URI) ? 'name' : 'code'] = op.to_s
|
383
407
|
when TripleConstraint then obj['valueExpr'] = op.to_s
|
384
408
|
when Shape then obj['expression'] = op.to_s
|
385
409
|
when EachOf, OneOf then (obj['expressions'] ||= []) << op.to_s
|
386
410
|
when And, Or then (obj['shapeExprs'] ||= []) << op.to_s
|
387
411
|
when Not then obj['shapeExpr'] = op.to_s
|
412
|
+
when Language then obj['languageTag'] = op.to_s
|
388
413
|
else
|
389
414
|
raise "How to serialize Value #{op.inspect} to json for #{self}"
|
390
415
|
end
|
@@ -451,14 +476,14 @@ module ShEx::Algebra
|
|
451
476
|
# @option options [JSON::LD::Context] :context
|
452
477
|
# @return [RDF::Value]
|
453
478
|
def iri(value, options = @options)
|
454
|
-
self.class.iri(value, options)
|
479
|
+
self.class.iri(value, **options)
|
455
480
|
end
|
456
481
|
|
457
482
|
# Create URIs
|
458
483
|
# @param (see #iri)
|
459
484
|
# @option (see #iri)
|
460
485
|
# @return (see #iri)
|
461
|
-
def self.iri(value, options)
|
486
|
+
def self.iri(value, **options)
|
462
487
|
# If we have a base URI, use that when constructing a new URI
|
463
488
|
base_uri = options[:base_uri]
|
464
489
|
|
@@ -467,7 +492,7 @@ module ShEx::Algebra
|
|
467
492
|
# A JSON-LD node reference
|
468
493
|
v = options[:context].expand_value(value)
|
469
494
|
raise "Expected #{value.inspect} to be a JSON-LD Node Reference" unless JSON::LD::Utils.node_reference?(v)
|
470
|
-
self.iri(v['@id'], options)
|
495
|
+
self.iri(v['@id'], **options)
|
471
496
|
when RDF::URI
|
472
497
|
if base_uri && value.relative?
|
473
498
|
base_uri.join(value)
|
@@ -505,26 +530,26 @@ module ShEx::Algebra
|
|
505
530
|
# @option options [Hash{String => RDF::URI}] :prefixes
|
506
531
|
# @return [RDF::Value]
|
507
532
|
def value(value, options = @options)
|
508
|
-
self.class.value(value, options)
|
533
|
+
self.class.value(value, **options)
|
509
534
|
end
|
510
535
|
|
511
536
|
# Create Values, with "clever" matching to see if it might be a value, IRI or BNode.
|
512
537
|
# @param (see #value)
|
513
538
|
# @option (see #value)
|
514
539
|
# @return (see #value)
|
515
|
-
def self.value(value, options)
|
540
|
+
def self.value(value, **options)
|
516
541
|
# If we have a base URI, use that when constructing a new URI
|
517
542
|
case value
|
518
543
|
when Hash
|
519
544
|
# Either a value object or a node reference
|
520
|
-
if value['uri']
|
521
|
-
iri(value['uri'], options)
|
522
|
-
elsif value['value']
|
523
|
-
RDF::Literal(value['value'], datatype: value['type'], language: value['language'])
|
545
|
+
if value['uri'] || value['@id']
|
546
|
+
iri(value['uri'] || value['@id'], **options)
|
547
|
+
elsif value['value'] || value['@value']
|
548
|
+
RDF::Literal(value['value'] || value['@value'], datatype: value['type'] || value['@type'], language: value['language'] || value['@language'])
|
524
549
|
else
|
525
|
-
ShEx::Algebra.from_shexj(value, options)
|
550
|
+
ShEx::Algebra.from_shexj(value, **options)
|
526
551
|
end
|
527
|
-
else iri(value, options)
|
552
|
+
else iri(value, **options)
|
528
553
|
end
|
529
554
|
end
|
530
555
|
|
@@ -541,6 +566,8 @@ module ShEx::Algebra
|
|
541
566
|
merge(value.has_language? ? {'language' => value.language.to_s} : {})
|
542
567
|
when RDF::Resource
|
543
568
|
value.to_s
|
569
|
+
when String
|
570
|
+
{'value' => value}
|
544
571
|
else value.to_h
|
545
572
|
end
|
546
573
|
end
|
@@ -566,7 +593,6 @@ module ShEx::Algebra
|
|
566
593
|
|
567
594
|
##
|
568
595
|
# Enumerate via depth-first recursive descent over operands, yielding each operator
|
569
|
-
# @param [Integer] depth incrementeded for each depth of operator, and provided to block if Arity is 2
|
570
596
|
# @yield operator
|
571
597
|
# @yieldparam [Object] operator
|
572
598
|
# @return [Enumerator]
|
@@ -634,12 +660,12 @@ module ShEx::Algebra
|
|
634
660
|
self
|
635
661
|
end
|
636
662
|
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
end
|
663
|
+
def dup
|
664
|
+
operands = @operands.map {|o| o.dup rescue o}
|
665
|
+
self.class.new(*operands, id: @id)
|
666
|
+
end
|
642
667
|
|
668
|
+
protected
|
643
669
|
##
|
644
670
|
# A unary operator.
|
645
671
|
#
|
@@ -654,7 +680,7 @@ module ShEx::Algebra
|
|
654
680
|
# the first operand
|
655
681
|
# @param [Hash{Symbol => Object}] options
|
656
682
|
# any additional options (see {Operator#initialize})
|
657
|
-
def initialize(arg1, options
|
683
|
+
def initialize(arg1, **options)
|
658
684
|
raise ArgumentError, "wrong number of arguments (given 2, expected 1)" unless options.is_a?(Hash)
|
659
685
|
super
|
660
686
|
end
|
@@ -676,7 +702,7 @@ module ShEx::Algebra
|
|
676
702
|
# the second operand
|
677
703
|
# @param [Hash{Symbol => Object}] options
|
678
704
|
# any additional options (see {Operator#initialize})
|
679
|
-
def initialize(arg1, arg2, options
|
705
|
+
def initialize(arg1, arg2, **options)
|
680
706
|
raise ArgumentError, "wrong number of arguments (given 3, expected 2)" unless options.is_a?(Hash)
|
681
707
|
super
|
682
708
|
end
|
data/lib/shex/algebra/or.rb
CHANGED
@@ -19,7 +19,7 @@ module ShEx::Algebra
|
|
19
19
|
# Creates an operator instance from a parsed ShExJ representation
|
20
20
|
# @param (see Operator#from_shexj)
|
21
21
|
# @return [Operator]
|
22
|
-
def self.from_shexj(operator, options
|
22
|
+
def self.from_shexj(operator, **options)
|
23
23
|
raise ArgumentError unless operator.is_a?(Hash) && operator['type'] == 'ShapeOr'
|
24
24
|
raise ArgumentError, "missing shapeExprs in #{operator.inspect}" unless operator.is_a?(Hash) && operator.has_key?('shapeExprs')
|
25
25
|
super
|
data/lib/shex/algebra/schema.rb
CHANGED
@@ -19,7 +19,7 @@ module ShEx::Algebra
|
|
19
19
|
# Creates an operator instance from a parsed ShExJ representation
|
20
20
|
# @param (see Operator#from_shexj)
|
21
21
|
# @return [Operator]
|
22
|
-
def self.from_shexj(operator, options
|
22
|
+
def self.from_shexj(operator, **options)
|
23
23
|
raise ArgumentError unless operator.is_a?(Hash) && operator['type'] == "Schema"
|
24
24
|
super
|
25
25
|
end
|
@@ -36,19 +36,22 @@ module ShEx::Algebra
|
|
36
36
|
##
|
37
37
|
# Match on schema. Finds appropriate shape for node, and matches that shape.
|
38
38
|
#
|
39
|
-
# @param [RDF::Term] focus
|
40
39
|
# @param [RDF::Queryable] graph
|
41
|
-
# @param [Hash{RDF::
|
40
|
+
# @param [Hash{RDF::Term => <RDF::Resource>}, Array<Array(RDF::Term, RDF::Resource)>] map
|
41
|
+
# A set of (`term`, `resource`) pairs where `term` is a node within `graph`, and `resource` identifies a shape
|
42
|
+
# @param [Array<RDF::Term>] focus ([])
|
43
|
+
# One or more nodes within `graph` for which to run the start expression.
|
42
44
|
# @param [Array<Schema, String>] shapeExterns ([])
|
43
45
|
# One or more schemas, or paths to ShEx schema resources used for finding external shapes.
|
44
|
-
# @return [
|
46
|
+
# @return [Hash{RDF::Term => Array<ShapeResult>}] Returns _ShapeResults_, a hash of graph nodes to the results of their associated shapes
|
45
47
|
# @param [Hash{Symbol => Object}] options
|
46
48
|
# @option options [String] :base_uri (for resolving focus)
|
47
|
-
# @raise [ShEx::NotSatisfied] along with
|
48
|
-
def execute(
|
49
|
-
@graph, @shapes_entered = graph, {}
|
49
|
+
# @raise [ShEx::NotSatisfied] along with individual shape results
|
50
|
+
def execute(graph, map, focus: [], shapeExterns: [], depth: 0, **options)
|
51
|
+
@graph, @shapes_entered, results = graph, {}, {}
|
50
52
|
@external_schemas = shapeExterns
|
51
|
-
|
53
|
+
@extensions = {}
|
54
|
+
focus = Array(focus).map {|f| value(f, **options)}
|
52
55
|
|
53
56
|
logger = options[:logger] || @options[:logger]
|
54
57
|
each_descendant do |op|
|
@@ -57,7 +60,6 @@ module ShEx::Algebra
|
|
57
60
|
end
|
58
61
|
|
59
62
|
# Initialize Extensions
|
60
|
-
@extensions = {}
|
61
63
|
each_descendant do |op|
|
62
64
|
next unless op.is_a?(SemAct)
|
63
65
|
name = op.operands.first.to_s
|
@@ -67,36 +69,74 @@ module ShEx::Algebra
|
|
67
69
|
end
|
68
70
|
|
69
71
|
# If `n` is a Blank Node, we won't find it through normal matching, find an equivalent node in the graph having the same id
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
72
|
+
@map = case map
|
73
|
+
when Hash
|
74
|
+
map.inject({}) do |memo, (node, shapes)|
|
75
|
+
gnode = graph.enum_term.detect {|t| t.node? && t.id == node.id} if node.is_a?(RDF::Node)
|
76
|
+
node = gnode if gnode
|
77
|
+
memo.merge(node => Array(shapes))
|
78
|
+
end
|
79
|
+
when Array
|
80
|
+
map.inject({}) do |memo, (node, shape)|
|
81
|
+
gnode = graph.enum_term.detect {|t| t.node? && t.id == node.id} if node.is_a?(RDF::Node)
|
82
|
+
node = gnode if gnode
|
83
|
+
(memo[node] ||= []).concat(Array(shape))
|
84
|
+
memo
|
85
|
+
end
|
86
|
+
when nil then {}
|
87
|
+
else
|
88
|
+
structure_error "Unrecognized shape map: #{map.inspect}"
|
89
|
+
end
|
75
90
|
|
76
91
|
# First, evaluate semantic acts
|
77
92
|
semantic_actions.all? do |op|
|
78
93
|
op.satisfies?([], depth: depth + 1)
|
79
94
|
end
|
80
95
|
|
81
|
-
# Keep a new Schema, specifically for recording actions
|
82
|
-
satisfied_schema = Schema.new
|
83
96
|
# Next run any start expression
|
84
|
-
if
|
85
|
-
|
97
|
+
if !focus.empty?
|
98
|
+
if start
|
99
|
+
focus.each do |node|
|
100
|
+
node = graph.enum_term.detect {|t| t.node? && t.id == node.id} if node.is_a?(RDF::Node)
|
101
|
+
sr = ShapeResult.new(RDF::URI("http://www.w3.org/ns/shex#Start"))
|
102
|
+
(results[node] ||= []) << sr
|
103
|
+
begin
|
104
|
+
sr.expression = start.satisfies?(node, depth: depth + 1)
|
105
|
+
sr.result = true
|
106
|
+
rescue ShEx::NotSatisfied => e
|
107
|
+
sr.expression = e.expression
|
108
|
+
sr.result = false
|
109
|
+
end
|
110
|
+
end
|
111
|
+
else
|
112
|
+
structure_error "Focus nodes with no start"
|
113
|
+
end
|
86
114
|
end
|
87
115
|
|
88
|
-
# Add shape result(s)
|
89
|
-
satisfied_shapes = {}
|
90
|
-
satisfied_schema.operands << [:shapes, satisfied_shapes] unless shapes.empty?
|
91
|
-
|
92
116
|
# Match against all shapes associated with the ids for focus
|
93
|
-
|
94
|
-
|
95
|
-
|
117
|
+
@map.each do |node, shapes|
|
118
|
+
results[node] ||= []
|
119
|
+
shapes.each do |id|
|
120
|
+
enter_shape(id, node) do |shape|
|
121
|
+
sr = ShapeResult.new(id)
|
122
|
+
results[node] << sr
|
123
|
+
begin
|
124
|
+
sr.expression = shape.satisfies?(node, depth: depth + 1)
|
125
|
+
sr.result = true
|
126
|
+
rescue ShEx::NotSatisfied => e
|
127
|
+
sr.expression = e.expression
|
128
|
+
sr.result = false
|
129
|
+
end
|
130
|
+
end
|
96
131
|
end
|
97
132
|
end
|
98
|
-
|
99
|
-
|
133
|
+
|
134
|
+
if results.values.flatten.all? {|sr| sr.result}
|
135
|
+
status "schema satisfied", depth: depth
|
136
|
+
results
|
137
|
+
else
|
138
|
+
raise ShEx::NotSatisfied.new("Graph does not conform to schema", expression: results)
|
139
|
+
end
|
100
140
|
ensure
|
101
141
|
# Close Semantic Action extensions
|
102
142
|
@extensions.values.each {|ext| ext.close(schema: self, depth: depth, **options)}
|
@@ -105,16 +145,12 @@ module ShEx::Algebra
|
|
105
145
|
##
|
106
146
|
# Match on schema. Finds appropriate shape for node, and matches that shape.
|
107
147
|
#
|
108
|
-
# @param
|
109
|
-
# @param [RDF::Queryable] graph
|
110
|
-
# @param [Hash{RDF::Resource => RDF::Resource}] map
|
111
|
-
# @param [Array<Schema, String>] shapeExterns ([])
|
112
|
-
# One or more schemas, or paths to ShEx schema resources used for finding external shapes.
|
148
|
+
# @param (see ShEx::Algebra::Schema#execute)
|
113
149
|
# @param [Hash{Symbol => Object}] options
|
114
150
|
# @option options [String] :base_uri
|
115
151
|
# @return [Boolean]
|
116
|
-
def satisfies?(
|
117
|
-
execute(
|
152
|
+
def satisfies?(graph, map, **options)
|
153
|
+
execute(graph, map, **options)
|
118
154
|
rescue ShEx::NotSatisfied
|
119
155
|
false
|
120
156
|
end
|
@@ -198,4 +234,41 @@ module ShEx::Algebra
|
|
198
234
|
super
|
199
235
|
end
|
200
236
|
end
|
237
|
+
|
238
|
+
# A shape result
|
239
|
+
class ShapeResult
|
240
|
+
# The label of the shape within the schema, or a URI indicating a start shape
|
241
|
+
# @return [RDF::Resource]
|
242
|
+
attr_reader :shape
|
243
|
+
|
244
|
+
# Does the node conform to the shape
|
245
|
+
# @return [Boolean]
|
246
|
+
attr_accessor :result
|
247
|
+
|
248
|
+
# The annotated {Operator} indicating processing results
|
249
|
+
# @return [ShEx::Algebra::Operator]
|
250
|
+
attr_accessor :expression
|
251
|
+
|
252
|
+
# Holds the result of processing a shape
|
253
|
+
# @param [RDF::Resource] shape
|
254
|
+
# @return [ShapeResult]
|
255
|
+
def initialize(shape)
|
256
|
+
@shape = shape
|
257
|
+
end
|
258
|
+
|
259
|
+
# The SXP of {#expression}
|
260
|
+
# @return [String]
|
261
|
+
def reason
|
262
|
+
SXP::Generator.string(expression.to_sxp_bin)
|
263
|
+
end
|
264
|
+
|
265
|
+
##
|
266
|
+
# Returns the binary S-Expression (SXP) representation of this result.
|
267
|
+
#
|
268
|
+
# @return [Array]
|
269
|
+
# @see https://en.wikipedia.org/wiki/S-expression
|
270
|
+
def to_sxp_bin
|
271
|
+
[:ShapeResult, shape, result, expression].map(&:to_sxp_bin)
|
272
|
+
end
|
273
|
+
end
|
201
274
|
end
|