shex 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +8 -8
- data/VERSION +1 -1
- data/etc/doap.ttl +1 -1
- data/lib/shex.rb +11 -11
- data/lib/shex/algebra.rb +43 -33
- data/lib/shex/algebra/node_constraint.rb +12 -3
- data/lib/shex/algebra/not.rb +1 -1
- data/lib/shex/algebra/operator.rb +20 -13
- data/lib/shex/algebra/schema.rb +106 -33
- data/lib/shex/algebra/shape_expression.rb +1 -1
- data/lib/shex/algebra/stem.rb +46 -2
- data/lib/shex/algebra/stem_range.rb +70 -2
- data/lib/shex/extensions/extension.rb +2 -2
- data/lib/shex/meta.rb +4282 -2334
- data/lib/shex/parser.rb +223 -72
- data/lib/shex/shex_context.rb +67 -68
- data/lib/shex/terminals.rb +36 -21
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d66489982d96aa02cd6be18354daaa81934af243
|
4
|
+
data.tar.gz: 7385a3e9933b1c81e09b7d0a126a1760de9aa4c2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d060ca866830927b3342352249337b6c06b25060677a8fe46e3a017d9c7a269749e5e1e792928eb6c4324bbaffb3bab91da1fb560d5e97ef33df57847df281e7
|
7
|
+
data.tar.gz: 7bf7a3fb6bd9d121a00ba837a5b0725cb5b2732d04dc8c959a8ee37ff3a311340dd9ebdf4496254dc450c39ee139e92800773990a133d922ffb98e5e7f602160
|
data/README.md
CHANGED
@@ -38,7 +38,7 @@ The ShEx gem implements a [ShEx][ShExSpec] Shape Expression engine.
|
|
38
38
|
(doap:name;doap:description|dc:title;dc:description)+;
|
39
39
|
doap:category*;
|
40
40
|
doap:developer IRI;
|
41
|
-
doap:implements [<
|
41
|
+
doap:implements [<http://shex.io/shex-semantics/>]
|
42
42
|
}
|
43
43
|
)
|
44
44
|
graph = RDF::Graph.load("etc/doap.ttl")
|
@@ -109,26 +109,26 @@ The ShEx gem implements a [ShEx][ShExSpec] Shape Expression engine.
|
|
109
109
|
]
|
110
110
|
}
|
111
111
|
],
|
112
|
-
"min": 1, "max":
|
112
|
+
"min": 1, "max": -1
|
113
113
|
},
|
114
114
|
{
|
115
115
|
"type": "TripleConstraint",
|
116
116
|
"predicate": "http://usefulinc.com/ns/doap#category",
|
117
117
|
"valueExpr": {"type": "NodeConstraint", "nodeKind": "iri"},
|
118
|
-
"min": 0, "max":
|
118
|
+
"min": 0, "max": -1
|
119
119
|
},
|
120
120
|
{
|
121
121
|
"type": "TripleConstraint",
|
122
122
|
"predicate": "http://usefulinc.com/ns/doap#developer",
|
123
123
|
"valueExpr": {"type": "NodeConstraint", "nodeKind": "iri"},
|
124
|
-
"min": 1, "max":
|
124
|
+
"min": 1, "max": -1
|
125
125
|
},
|
126
126
|
{
|
127
127
|
"type": "TripleConstraint",
|
128
128
|
"predicate": "http://usefulinc.com/ns/doap#implements",
|
129
129
|
"valueExpr": {
|
130
130
|
"type": "NodeConstraint",
|
131
|
-
"values": ["
|
131
|
+
"values": ["http://shex.io/shex-semantics/"]
|
132
132
|
}
|
133
133
|
}
|
134
134
|
]
|
@@ -143,7 +143,7 @@ The ShEx gem implements a [ShEx][ShExSpec] Shape Expression engine.
|
|
143
143
|
# => true
|
144
144
|
|
145
145
|
## Extensions
|
146
|
-
ShEx has an extension mechanism using [Semantic Actions](
|
146
|
+
ShEx has an extension mechanism using [Semantic Actions](http://shex.io/shex-semantics/#semantic-actions). Extensions may be implemented in Ruby ShEx by sub-classing {ShEx::Extension} and implementing {ShEx::Extension#visit} and possibly {ShEx::Extension#initialize}, {ShEx::Extension#enter}, {ShEx::Extension#exit}, and {ShEx::Extension#close}. The `#visit` method will be called as part of the `#satisfies?` operation.
|
147
147
|
|
148
148
|
require 'shex'
|
149
149
|
class ShEx::Test < ShEx::Extension("http://shex.io/extensions/Test/")
|
@@ -179,7 +179,7 @@ The result of parsing either ShExC or ShExJ is the creation of a set of executab
|
|
179
179
|
## Dependencies
|
180
180
|
|
181
181
|
* [Ruby](http://ruby-lang.org/) (>= 2.2.2)
|
182
|
-
* [RDF.rb](http://rubygems.org/gems/rdf) (
|
182
|
+
* [RDF.rb](http://rubygems.org/gems/rdf) (~> 2.2)
|
183
183
|
|
184
184
|
## Installation
|
185
185
|
|
@@ -237,7 +237,7 @@ This repository uses [Git Flow](https://github.com/nvie/gitflow) to mange develo
|
|
237
237
|
This is free and unencumbered public domain software. For more information,
|
238
238
|
see <http://unlicense.org/> or the accompanying {file:LICENSE} file.
|
239
239
|
|
240
|
-
[ShExSpec]:
|
240
|
+
[ShExSpec]: http://shex.io/shex-semantics/
|
241
241
|
[RDF]: http://www.w3.org/RDF/
|
242
242
|
[RDF.rb]: http://rubydoc.info/github/ruby-rdf/rdf
|
243
243
|
[EBNF]: http://rubygems.org/gems/ebnf
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.5.0
|
data/etc/doap.ttl
CHANGED
@@ -15,7 +15,7 @@
|
|
15
15
|
doap:description "ShEx is an Shape Expression engine for the RDF.rb library suite."@en ;
|
16
16
|
doap:created "2016-12-09"^^xsd:date ;
|
17
17
|
doap:programming-language "Ruby" ;
|
18
|
-
doap:implements <
|
18
|
+
doap:implements <http://shex.io/shex-semantics/> ;
|
19
19
|
doap:category <http://dbpedia.org/resource/Resource_Description_Framework>,
|
20
20
|
<http://dbpedia.org/resource/Ruby_(programming_language)> ;
|
21
21
|
doap:download-page <http://rubygems.org/gems/shex> ;
|
data/lib/shex.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
##
|
2
2
|
# A ShEx runtime for RDF.rb.
|
3
3
|
#
|
4
|
-
# @see
|
4
|
+
# @see http://shex.io/shex-semantics/#shexc
|
5
5
|
module ShEx
|
6
6
|
autoload :Algebra, 'shex/algebra'
|
7
7
|
autoload :Meta, 'shex/meta'
|
@@ -11,7 +11,7 @@ module ShEx
|
|
11
11
|
autoload :VERSION, 'shex/version'
|
12
12
|
|
13
13
|
# Location of the ShEx JSON-LD context
|
14
|
-
CONTEXT = "
|
14
|
+
CONTEXT = "http://www.w3.org/ns/shex.jsonld"
|
15
15
|
|
16
16
|
# Extensions defined in this gem
|
17
17
|
EXTENSIONS = %w{test}
|
@@ -71,11 +71,11 @@ module ShEx
|
|
71
71
|
# @param (see ShEx::Algebra::Schema#execute)
|
72
72
|
# @return (see ShEx::Algebra::Schema#execute)
|
73
73
|
# @raise (see ShEx::Algebra::Schema#execute)
|
74
|
-
def self.execute(expression, queryable,
|
74
|
+
def self.execute(expression, queryable, map, format: 'shexc', **options)
|
75
75
|
shex = self.parse(expression, options.merge(format: format))
|
76
76
|
queryable = queryable || RDF::Graph.new
|
77
77
|
|
78
|
-
shex.execute(
|
78
|
+
shex.execute(queryable, map, options)
|
79
79
|
end
|
80
80
|
|
81
81
|
##
|
@@ -89,11 +89,11 @@ module ShEx
|
|
89
89
|
# @param (see ShEx::Algebra::Schema#satisfies?)
|
90
90
|
# @return (see ShEx::Algebra::Schema#satisfies?)
|
91
91
|
# @raise (see ShEx::Algebra::Schema#satisfies?)
|
92
|
-
def self.satisfies?(expression, queryable,
|
92
|
+
def self.satisfies?(expression, queryable, map, format: 'shexc', **options)
|
93
93
|
shex = self.parse(expression, options.merge(format: format))
|
94
94
|
queryable = queryable || RDF::Graph.new
|
95
95
|
|
96
|
-
shex.satisfies?(
|
96
|
+
shex.satisfies?(queryable, map, options)
|
97
97
|
end
|
98
98
|
|
99
99
|
##
|
@@ -129,14 +129,14 @@ module ShEx
|
|
129
129
|
class NotSatisfied < Error
|
130
130
|
##
|
131
131
|
# The expression which was not satified
|
132
|
-
# @return [ShEx::
|
132
|
+
# @return [ShEx::Algebra::ShapeExpression]
|
133
133
|
attr_reader :expression
|
134
134
|
|
135
135
|
##
|
136
136
|
# Initializes a new parser error instance.
|
137
137
|
#
|
138
|
-
# @param [String, #to_s]
|
139
|
-
# @param [
|
138
|
+
# @param [String, #to_s] message
|
139
|
+
# @param [ShEx::Algebra::ShapeExpression] expression
|
140
140
|
def initialize(message, expression: self)
|
141
141
|
@expression = expression
|
142
142
|
super(message.to_s)
|
@@ -157,8 +157,8 @@ module ShEx
|
|
157
157
|
##
|
158
158
|
# Initializes a new parser error instance.
|
159
159
|
#
|
160
|
-
# @param [String, #to_s]
|
161
|
-
# @param [
|
160
|
+
# @param [String, #to_s] message
|
161
|
+
# @param [ShEx::Algebra::TripleExpression] expression
|
162
162
|
def initialize(message, expression: self)
|
163
163
|
@expression = expression
|
164
164
|
super(message.to_s)
|
data/lib/shex/algebra.rb
CHANGED
@@ -7,25 +7,31 @@ module ShEx
|
|
7
7
|
#
|
8
8
|
# @author [Gregg Kellogg](http://greggkellogg.net/)
|
9
9
|
module Algebra
|
10
|
-
autoload :And,
|
11
|
-
autoload :Annotation,
|
12
|
-
autoload :EachOf,
|
13
|
-
autoload :External,
|
14
|
-
autoload :
|
15
|
-
autoload :
|
16
|
-
autoload :
|
17
|
-
autoload :
|
18
|
-
autoload :
|
19
|
-
autoload :
|
20
|
-
autoload :
|
21
|
-
autoload :
|
22
|
-
autoload :
|
23
|
-
autoload :
|
24
|
-
autoload :
|
25
|
-
autoload :
|
10
|
+
autoload :And, 'shex/algebra/and'
|
11
|
+
autoload :Annotation, 'shex/algebra/annotation'
|
12
|
+
autoload :EachOf, 'shex/algebra/each_of'
|
13
|
+
autoload :External, 'shex/algebra/external'
|
14
|
+
autoload :IriStem, 'shex/algebra/stem'
|
15
|
+
autoload :IriStemRange, 'shex/algebra/stem_range'
|
16
|
+
autoload :LanguageStem, 'shex/algebra/stem'
|
17
|
+
autoload :LanguageStemRange,'shex/algebra/stem_range'
|
18
|
+
autoload :LiteralStem, 'shex/algebra/stem'
|
19
|
+
autoload :LiteralStemRange, 'shex/algebra/stem_range'
|
20
|
+
autoload :NodeConstraint, 'shex/algebra/node_constraint'
|
21
|
+
autoload :Not, 'shex/algebra/not'
|
22
|
+
autoload :OneOf, 'shex/algebra/one_of'
|
23
|
+
autoload :Operator, 'shex/algebra/operator'
|
24
|
+
autoload :Or, 'shex/algebra/or'
|
25
|
+
autoload :Schema, 'shex/algebra/schema'
|
26
|
+
autoload :SemAct, 'shex/algebra/semact'
|
27
|
+
autoload :Shape, 'shex/algebra/shape'
|
28
|
+
autoload :ShapeExpression, 'shex/algebra/shape_expression'
|
29
|
+
autoload :Start, 'shex/algebra/start'
|
30
|
+
autoload :Stem, 'shex/algebra/stem'
|
31
|
+
autoload :StemRange, 'shex/algebra/stem_range'
|
26
32
|
autoload :TripleConstraint, 'shex/algebra/triple_constraint'
|
27
33
|
autoload :TripleExpression, 'shex/algebra/triple_expression'
|
28
|
-
autoload :Value,
|
34
|
+
autoload :Value, 'shex/algebra/value'
|
29
35
|
|
30
36
|
|
31
37
|
##
|
@@ -46,22 +52,26 @@ module ShEx
|
|
46
52
|
def self.from_shexj(operator, options = {})
|
47
53
|
raise ArgumentError unless operator.is_a?(Hash)
|
48
54
|
klass = case operator['type']
|
49
|
-
when 'Annotation'
|
50
|
-
when 'EachOf'
|
51
|
-
when '
|
52
|
-
when '
|
53
|
-
when '
|
54
|
-
when '
|
55
|
-
when '
|
56
|
-
when '
|
57
|
-
when '
|
58
|
-
when '
|
59
|
-
when '
|
60
|
-
when '
|
61
|
-
when '
|
62
|
-
when '
|
63
|
-
when '
|
64
|
-
|
55
|
+
when 'Annotation' then Annotation
|
56
|
+
when 'EachOf' then EachOf
|
57
|
+
when 'IriStem' then IriStem
|
58
|
+
when 'IriStemRange' then IriStemRange
|
59
|
+
when 'LanguageStem' then LanguageStem
|
60
|
+
when 'LanguageStemRange' then LanguageStemRange
|
61
|
+
when 'LiteralStem' then LiteralStem
|
62
|
+
when 'LiteralStemRange' then LiteralStemRange
|
63
|
+
when 'NodeConstraint' then NodeConstraint
|
64
|
+
when 'OneOf' then OneOf
|
65
|
+
when 'Schema' then Schema
|
66
|
+
when 'SemAct' then SemAct
|
67
|
+
when 'Shape' then Shape
|
68
|
+
when 'ShapeAnd' then And
|
69
|
+
when 'ShapeExternal' then External
|
70
|
+
when 'ShapeNot' then Not
|
71
|
+
when 'ShapeOr' then Or
|
72
|
+
when 'TripleConstraint' then TripleConstraint
|
73
|
+
when 'Wildcard' then StemRange
|
74
|
+
else raise ArgumentError, "unknown type #{operator['type'].inspect}"
|
65
75
|
end
|
66
76
|
|
67
77
|
klass.from_shexj(operator, options)
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
1
2
|
module ShEx::Algebra
|
2
3
|
##
|
3
4
|
class NodeConstraint < Operator
|
@@ -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
@@ -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
|
@@ -267,12 +267,16 @@ module ShEx::Algebra
|
|
267
267
|
|
268
268
|
operator.each do |k, v|
|
269
269
|
case k
|
270
|
-
when /length|
|
270
|
+
when /length|clusive|digits/ then operands << [k.to_sym, RDF::Literal(v)]
|
271
271
|
when 'id' then id = iri(v, options)
|
272
|
-
when '
|
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
276
|
when 'object' then operands << value(v, options)
|
277
|
+
when 'pattern'
|
278
|
+
# Include flags as well
|
279
|
+
operands << [:pattern, RDF::Literal(v), operator['flags']].compact
|
276
280
|
when 'start'
|
277
281
|
if v.is_a?(String)
|
278
282
|
operands << Start.new(iri(v, options))
|
@@ -291,7 +295,7 @@ module ShEx::Algebra
|
|
291
295
|
end
|
292
296
|
when 'stem', 'name'
|
293
297
|
# Value may be :wildcard for stem
|
294
|
-
operands << (v.is_a?(Symbol) ? v :
|
298
|
+
operands << (v.is_a?(Symbol) ? v : value(v, options))
|
295
299
|
when 'predicate' then operands << [:predicate, iri(v, options)]
|
296
300
|
when 'extra', 'datatype'
|
297
301
|
v = [v] unless v.is_a?(Array)
|
@@ -299,7 +303,7 @@ module ShEx::Algebra
|
|
299
303
|
when 'exclusions'
|
300
304
|
v = [v] unless v.is_a?(Array)
|
301
305
|
operands << v.map do |op|
|
302
|
-
op.is_a?(Hash) ?
|
306
|
+
op.is_a?(Hash) && op.has_key?('type') ?
|
303
307
|
ShEx::Algebra.from_shexj(op, options) :
|
304
308
|
value(op, options)
|
305
309
|
end.unshift(:exclusions)
|
@@ -345,11 +349,12 @@ module ShEx::Algebra
|
|
345
349
|
when Array
|
346
350
|
# First element should be a symbol
|
347
351
|
case sym = op.first
|
348
|
-
when :datatype
|
349
|
-
:pattern then obj[op.first.to_s] = op.last.to_s
|
352
|
+
when :datatype then obj['datatype'] = op.last.to_s
|
350
353
|
when :exclusions then obj['exclusions'] = Array(op[1..-1]).map {|v| serialize_value(v)}
|
351
354
|
when :extra then (obj['extra'] ||= []).concat Array(op[1..-1]).map(&:to_s)
|
352
|
-
|
355
|
+
when :pattern
|
356
|
+
obj['pattern'] = op[1]
|
357
|
+
obj['flags'] = op[2] if op[2]
|
353
358
|
when :shapes then obj['shapes'] = Array(op[1..-1]).map {|v| v.to_h}
|
354
359
|
when :minlength,
|
355
360
|
:maxlength,
|
@@ -360,7 +365,7 @@ module ShEx::Algebra
|
|
360
365
|
:maxexclusive,
|
361
366
|
:totaldigits,
|
362
367
|
:fractiondigits then obj[op.first.to_s] = op.last.object
|
363
|
-
when :min, :max then obj[op.first.to_s] = op.last
|
368
|
+
when :min, :max then obj[op.first.to_s] = op.last == '*' ? -1 : op.last
|
364
369
|
when :predicate then obj[op.first.to_s] = op.last.to_s
|
365
370
|
when :base, :prefix
|
366
371
|
# Ignore base and prefix
|
@@ -378,7 +383,7 @@ module ShEx::Algebra
|
|
378
383
|
end
|
379
384
|
when RDF::Value
|
380
385
|
case self
|
381
|
-
when Stem, StemRange then obj['stem'] = op
|
386
|
+
when Stem, StemRange then obj['stem'] = serialize_value(op)
|
382
387
|
when SemAct then obj[op.is_a?(RDF::URI) ? 'name' : 'code'] = op.to_s
|
383
388
|
when TripleConstraint then obj['valueExpr'] = op.to_s
|
384
389
|
when Shape then obj['expression'] = op.to_s
|
@@ -517,10 +522,10 @@ module ShEx::Algebra
|
|
517
522
|
case value
|
518
523
|
when Hash
|
519
524
|
# 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'])
|
525
|
+
if value['uri'] || value['@id']
|
526
|
+
iri(value['uri'] || value['@id'], options)
|
527
|
+
elsif value['value'] || value['@value']
|
528
|
+
RDF::Literal(value['value'] || value['@value'], datatype: value['type'] || value['@type'], language: value['language'] || value['@language'])
|
524
529
|
else
|
525
530
|
ShEx::Algebra.from_shexj(value, options)
|
526
531
|
end
|
@@ -541,6 +546,8 @@ module ShEx::Algebra
|
|
541
546
|
merge(value.has_language? ? {'language' => value.language.to_s} : {})
|
542
547
|
when RDF::Resource
|
543
548
|
value.to_s
|
549
|
+
when String
|
550
|
+
{'value' => value}
|
544
551
|
else value.to_h
|
545
552
|
end
|
546
553
|
end
|
data/lib/shex/algebra/schema.rb
CHANGED
@@ -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] label
|
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
|