shex 0.4.0 → 0.6.1
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 +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
|