shex 0.1.0 → 0.2.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 +4 -4
- data/VERSION +1 -1
- data/lib/shex.rb +79 -33
- data/lib/shex/algebra.rb +0 -2
- data/lib/shex/algebra/and.rb +13 -9
- data/lib/shex/algebra/each_of.rb +26 -18
- data/lib/shex/algebra/external.rb +2 -2
- data/lib/shex/algebra/inclusion.rb +7 -10
- data/lib/shex/algebra/node_constraint.rb +11 -10
- data/lib/shex/algebra/not.rb +11 -7
- data/lib/shex/algebra/one_of.rb +22 -16
- data/lib/shex/algebra/operator.rb +78 -11
- data/lib/shex/algebra/or.rb +18 -12
- data/lib/shex/algebra/satisfiable.rb +4 -22
- data/lib/shex/algebra/schema.rb +68 -19
- data/lib/shex/algebra/shape.rb +51 -33
- data/lib/shex/algebra/shape_ref.rb +8 -9
- data/lib/shex/algebra/start.rb +8 -8
- data/lib/shex/algebra/triple_constraint.rb +33 -27
- data/lib/shex/algebra/triple_expression.rb +27 -11
- data/lib/shex/parser.rb +18 -18
- 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: 3ba75ebd8c6632f82452b8cc6a19dbe69e86258d
|
4
|
+
data.tar.gz: a924845f0d0ffd7caa35926b98829345ee3d427a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1e844351dfd63a5c158a088b6d83375e3f96382d1a56c3e7aaa8e037f4c3ae380bac4f04d1da9db3727053400563b96565d291cb7537974c920df636bf7d3614
|
7
|
+
data.tar.gz: 3797b22ce44300b723770608ee42a3fd03418f0c8497e4a370fdf960f47ad324c67b07ec061ca498a8fa2d683b97a4fab7c95ced34123840e2c968120e162418
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/lib/shex.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'sparql/extensions'
|
2
|
-
|
3
1
|
##
|
4
2
|
# A ShEx runtime for RDF.rb.
|
5
3
|
#
|
@@ -20,16 +18,16 @@ module ShEx
|
|
20
18
|
# ).parse
|
21
19
|
#
|
22
20
|
# @param [IO, StringIO, String, #to_s] expression (ShExC or ShExJ)
|
23
|
-
# @param ['shexc', 'shexj', '
|
21
|
+
# @param ['shexc', 'shexj', 'sxp'] format ('shexc')
|
24
22
|
# @param [Hash{Symbol => Object}] options
|
25
|
-
# @
|
26
|
-
# @
|
27
|
-
# @raise
|
23
|
+
# @option (see ShEx::Parser#initialize)
|
24
|
+
# @return (see ShEx::Parser#parse)
|
25
|
+
# @raise (see ShEx::Parser#parse)
|
28
26
|
def self.parse(expression, format: 'shexc', **options)
|
29
27
|
case format
|
30
28
|
when 'shexc' then Parser.new(expression, options).parse
|
31
29
|
when 'shexj'
|
32
|
-
when '
|
30
|
+
when 'sxp'
|
33
31
|
else raise "Unknown expression format: #{format.inspect}"
|
34
32
|
end
|
35
33
|
end
|
@@ -41,15 +39,10 @@ module ShEx
|
|
41
39
|
# schema = ShEx.parse('foo.shex').parse
|
42
40
|
#
|
43
41
|
# @param [String, #to_s] filename
|
44
|
-
# @param
|
45
|
-
# @
|
46
|
-
#
|
47
|
-
# @
|
48
|
-
# @yieldparam [RDF::Reader] reader
|
49
|
-
# @yieldreturn [void] ignored
|
50
|
-
# @return [ShEx::Algebra::Schema] The executable parsed expression.
|
51
|
-
# @raise [ShEx::ParseError] when a syntax error is detected
|
52
|
-
# @raise [ShEx::StructureError, ArgumentError] on structural problems with schema
|
42
|
+
# @param (see parse)
|
43
|
+
# @option (see ShEx::Parser#initialize)
|
44
|
+
# @return (see ShEx::Parser#parse)
|
45
|
+
# @raise (see ShEx::Parser#parse)
|
53
46
|
def self.open(filename, format: 'shexc', **options, &block)
|
54
47
|
RDF::Util::File.open_file(filename, options) do |file|
|
55
48
|
self.parse(file, options.merge(format: format))
|
@@ -64,18 +57,31 @@ module ShEx
|
|
64
57
|
# ShEx.execute('etc/doap.shex', graph, "http://rubygems.org/gems/shex", "")
|
65
58
|
#
|
66
59
|
# @param [IO, StringIO, String, #to_s] expression (ShExC or ShExJ)
|
67
|
-
# @param
|
68
|
-
# @
|
69
|
-
# @
|
70
|
-
# @param [Hash{Symbol => Object}] options
|
71
|
-
# @return [Boolean] `true` if satisfied, `false` if it does not apply
|
72
|
-
# @raise [ShEx::NotSatisfied] if not satisfied
|
73
|
-
# @raise [ShEx::ParseError] when a syntax error is detected
|
74
|
-
# @raise [ShEx::StructureError, ArgumentError] on structural problems with schema
|
60
|
+
# @param (see ShEx::Algebra::Schema#execute)
|
61
|
+
# @return (see ShEx::Algebra::Schema#execute)
|
62
|
+
# @raise (see ShEx::Algebra::Schema#execute)
|
75
63
|
def self.execute(expression, queryable, focus, shape, format: 'shexc', **options)
|
76
64
|
shex = self.parse(expression, options.merge(format: format))
|
77
65
|
queryable = queryable || RDF::Graph.new
|
78
66
|
|
67
|
+
shex.execute(focus, queryable, {focus => shape}, options)
|
68
|
+
end
|
69
|
+
|
70
|
+
##
|
71
|
+
# Parse and validate the given ShEx `expression` string against `queriable`.
|
72
|
+
#
|
73
|
+
# @example executing a ShExC schema
|
74
|
+
# graph = RDF::Graph.load("etc/doap.ttl")
|
75
|
+
# ShEx.execute('etc/doap.shex', graph, "http://rubygems.org/gems/shex", "")
|
76
|
+
#
|
77
|
+
# @param [IO, StringIO, String, #to_s] expression (ShExC or ShExJ)
|
78
|
+
# @param (see ShEx::Algebra::Schema#satisfies?)
|
79
|
+
# @return (see ShEx::Algebra::Schema#satisfies?)
|
80
|
+
# @raise (see ShEx::Algebra::Schema#satisfies?)
|
81
|
+
def self.satisfies?(expression, queryable, focus, shape, format: 'shexc', **options)
|
82
|
+
shex = self.parse(expression, options.merge(format: format))
|
83
|
+
queryable = queryable || RDF::Graph.new
|
84
|
+
|
79
85
|
shex.satisfies?(focus, queryable, {focus => shape}, options)
|
80
86
|
end
|
81
87
|
|
@@ -100,7 +106,48 @@ module ShEx
|
|
100
106
|
class StructureError < Error; end
|
101
107
|
|
102
108
|
# Shape expectation not satisfied
|
103
|
-
class NotSatisfied < Error
|
109
|
+
class NotSatisfied < Error
|
110
|
+
##
|
111
|
+
# The expression which was not satified
|
112
|
+
# @return [ShEx::Satisfiable]
|
113
|
+
attr_reader :expression
|
114
|
+
|
115
|
+
##
|
116
|
+
# Initializes a new parser error instance.
|
117
|
+
#
|
118
|
+
# @param [String, #to_s] message
|
119
|
+
# @param [Satisfiable] expression (self)
|
120
|
+
def initialize(message, expression: self)
|
121
|
+
@expression = expression
|
122
|
+
super(message.to_s)
|
123
|
+
end
|
124
|
+
|
125
|
+
def inspect
|
126
|
+
super + (expression ? SXP::Generator.string(expression.to_sxp_bin) : '')
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# TripleExpression did not match
|
131
|
+
class NotMatched < ShEx::Error
|
132
|
+
##
|
133
|
+
# The expression which was not satified
|
134
|
+
# @return [ShEx::Algebra::TripleExpression]
|
135
|
+
attr_reader :expression
|
136
|
+
|
137
|
+
##
|
138
|
+
# Initializes a new parser error instance.
|
139
|
+
#
|
140
|
+
# @param [String, #to_s] message
|
141
|
+
# @param [Satisfiable] expression (self)
|
142
|
+
def initialize(message, expression: self)
|
143
|
+
@expression = expression
|
144
|
+
super(message.to_s)
|
145
|
+
end
|
146
|
+
|
147
|
+
def inspect
|
148
|
+
super + (expression ? SXP::Generator.string(expression.to_sxp_bin) : '')
|
149
|
+
end
|
150
|
+
end
|
104
151
|
|
105
152
|
# Indicates bad syntax found in LD Patch document
|
106
153
|
class ParseError < Error
|
@@ -117,17 +164,16 @@ module ShEx
|
|
117
164
|
attr_reader :lineno
|
118
165
|
|
119
166
|
##
|
120
|
-
#
|
167
|
+
# ParseError includes `token` and `lineno` associated with the expression.
|
121
168
|
#
|
122
169
|
# @param [String, #to_s] message
|
123
170
|
# @param [Hash{Symbol => Object}] options
|
124
|
-
# @
|
125
|
-
# @
|
126
|
-
|
127
|
-
|
128
|
-
@
|
129
|
-
|
130
|
-
super(message.to_s, code: options.fetch(:code, 400))
|
171
|
+
# @param [String] token (nil)
|
172
|
+
# @param [Integer] lineno (nil)
|
173
|
+
def initialize(message, token: nil, lineno: nil)
|
174
|
+
@token = token
|
175
|
+
@lineno = lineno || (@token.lineno if @token.respond_to?(:lineno))
|
176
|
+
super(message.to_s)
|
131
177
|
end
|
132
178
|
end
|
133
179
|
end
|
data/lib/shex/algebra.rb
CHANGED
data/lib/shex/algebra/and.rb
CHANGED
@@ -14,19 +14,23 @@ module ShEx::Algebra
|
|
14
14
|
|
15
15
|
#
|
16
16
|
# S is a ShapeAnd and for every shape expression se2 in shapeExprs, satisfies(n, se2, G, m).
|
17
|
-
# @param
|
18
|
-
# @return
|
19
|
-
# @raise
|
20
|
-
def satisfies?(
|
17
|
+
# @param (see Satisfiable#satisfies?)
|
18
|
+
# @return (see Satisfiable#satisfies?)
|
19
|
+
# @raise (see Satisfiable#satisfies?)
|
20
|
+
def satisfies?(focus)
|
21
21
|
status ""
|
22
|
+
expressions = operands.select {|o| o.is_a?(Satisfiable)}
|
23
|
+
satisfied = []
|
22
24
|
|
23
25
|
# Operand raises NotSatisfied, so no need to check here.
|
24
|
-
|
25
|
-
|
26
|
-
|
26
|
+
expressions.each do |op|
|
27
|
+
satisfied << op.satisfies?(focus)
|
28
|
+
end
|
29
|
+
satisfy satisfied: satisfied
|
27
30
|
rescue ShEx::NotSatisfied => e
|
28
|
-
not_satisfied
|
29
|
-
|
31
|
+
not_satisfied e.message,
|
32
|
+
satisfied: satisfied,
|
33
|
+
unsatisfied: (expressions - satisfied)
|
30
34
|
end
|
31
35
|
end
|
32
36
|
end
|
data/lib/shex/algebra/each_of.rb
CHANGED
@@ -7,47 +7,55 @@ module ShEx::Algebra
|
|
7
7
|
##
|
8
8
|
# expr is an EachOf and there is some partition of T into T1, T2,… such that for every expression expr1, expr2,… in shapeExprs, matches(Tn, exprn, m)...
|
9
9
|
#
|
10
|
-
# @param [Array<RDF::Statement>]
|
10
|
+
# @param [Array<RDF::Statement>] statements
|
11
11
|
# @return [Array<RDF::Statement>]
|
12
|
-
# @raise
|
13
|
-
def matches(
|
12
|
+
# @raise [ShEx::NotMatched]
|
13
|
+
def matches(statements)
|
14
14
|
status ""
|
15
|
-
results = []
|
16
|
-
|
17
|
-
num_iters = 0
|
18
|
-
max = maximum
|
15
|
+
results, satisfied, unsatisfied = [], [], []
|
16
|
+
num_iters, max = 0, maximum
|
19
17
|
|
20
18
|
while num_iters < max
|
21
19
|
begin
|
22
20
|
matched_this_iter = []
|
23
21
|
operands.select {|o| o.is_a?(TripleExpression)}.all? do |op|
|
24
|
-
|
25
|
-
|
22
|
+
begin
|
23
|
+
matched_op = op.matches(statements - matched_this_iter)
|
24
|
+
satisfied << matched_op
|
25
|
+
matched_this_iter += matched_op.matched
|
26
|
+
rescue ShEx::NotMatched => e
|
27
|
+
status "not matched: #{e.message}"
|
28
|
+
op = op.dup
|
29
|
+
op.unmatched = statements - matched_this_iter
|
30
|
+
unsatisfied << op
|
31
|
+
raise
|
32
|
+
end
|
26
33
|
end
|
27
34
|
results += matched_this_iter
|
28
35
|
statements -= matched_this_iter
|
29
36
|
num_iters += 1
|
30
37
|
status "matched #{results.length} statements after #{num_iters} iterations"
|
31
|
-
rescue NotMatched => e
|
32
|
-
|
38
|
+
rescue ShEx::NotMatched => e
|
39
|
+
status "no match after #{num_iters} iterations (ignored)"
|
33
40
|
break
|
34
41
|
end
|
35
42
|
end
|
36
43
|
|
37
44
|
# Max violations handled in Shape
|
38
|
-
|
39
|
-
|
45
|
+
if num_iters < minimum
|
46
|
+
raise ShEx::NotMatched, "Minimum Cardinality Violation: #{results.length} < #{minimum}"
|
47
|
+
end
|
40
48
|
|
41
49
|
# Last, evaluate semantic acts
|
42
50
|
semantic_actions.all? do |op|
|
43
51
|
op.satisfies?(results)
|
44
52
|
end unless results.empty?
|
45
53
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
54
|
+
satisfy matched: results, satisfied: satisfied
|
55
|
+
rescue ShEx::NotMatched, ShEx::NotSatisfied => e
|
56
|
+
not_matched e.message,
|
57
|
+
matched: results, unmatched: (statements - results),
|
58
|
+
satisfied: satisfied, unsatisfied: unsatisfied
|
51
59
|
end
|
52
60
|
end
|
53
61
|
end
|
@@ -6,7 +6,7 @@ module ShEx::Algebra
|
|
6
6
|
|
7
7
|
#
|
8
8
|
# S is a ShapeRef and the Schema's shapes maps reference to a shape expression se2 and satisfies(n, se2, G, m).
|
9
|
-
def satisfies?(
|
9
|
+
def satisfies?(focus)
|
10
10
|
extern_shape = nil
|
11
11
|
|
12
12
|
# Find the label for this external
|
@@ -18,7 +18,7 @@ module ShEx::Algebra
|
|
18
18
|
end
|
19
19
|
|
20
20
|
not_satisfied("External not configured for this shape") unless extern_shape
|
21
|
-
extern_shape.satisfies?(
|
21
|
+
extern_shape.satisfies?(focus)
|
22
22
|
end
|
23
23
|
end
|
24
24
|
end
|
@@ -12,20 +12,17 @@ module ShEx::Algebra
|
|
12
12
|
##
|
13
13
|
# In this case, we accept an array of statements, and match based on cardinality.
|
14
14
|
#
|
15
|
-
# @param [Array<RDF::Statement>]
|
15
|
+
# @param [Array<RDF::Statement>] statements
|
16
16
|
# @return [Array<RDF::Statement>]
|
17
|
-
# @raise
|
18
|
-
def matches(
|
17
|
+
# @raise [ShEx::NotMatched]
|
18
|
+
def matches(statements)
|
19
19
|
status "referenced_shape: #{operands.first}"
|
20
20
|
expression = referenced_shape.triple_expressions.first
|
21
21
|
max = maximum
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
not_matched
|
26
|
-
results.length < minimum
|
27
|
-
|
28
|
-
results
|
22
|
+
matched_expression = expression.matches(statements)
|
23
|
+
satisfy matched: matched_expression.matched
|
24
|
+
rescue ShEx::NotMatched => e
|
25
|
+
not_matched e.message, unsatisfied: e.expression
|
29
26
|
end
|
30
27
|
|
31
28
|
##
|
@@ -5,17 +5,18 @@ module ShEx::Algebra
|
|
5
5
|
NAME = :nodeConstraint
|
6
6
|
|
7
7
|
#
|
8
|
-
# S is a NodeConstraint and satisfies2(
|
9
|
-
# @param
|
10
|
-
# @return
|
11
|
-
# @raise
|
12
|
-
def satisfies?(
|
8
|
+
# S is a NodeConstraint and satisfies2(focus, se) as described below in Node Constraints. Note that testing if a node satisfies a node constraint does not require a graph or shapeMap.
|
9
|
+
# @param (see Satisfiable#satisfies?)
|
10
|
+
# @return (see Satisfiable#satisfies?)
|
11
|
+
# @raise (see Satisfiable#satisfies?)
|
12
|
+
def satisfies?(focus)
|
13
13
|
status ""
|
14
|
-
satisfies_node_kind?(
|
15
|
-
satisfies_datatype?(
|
16
|
-
satisfies_string_facet?(
|
17
|
-
satisfies_numeric_facet?(
|
18
|
-
satisfies_values?(
|
14
|
+
satisfies_node_kind?(focus) &&
|
15
|
+
satisfies_datatype?(focus) &&
|
16
|
+
satisfies_string_facet?(focus) &&
|
17
|
+
satisfies_numeric_facet?(focus) &&
|
18
|
+
satisfies_values?(focus) &&
|
19
|
+
satisfy
|
19
20
|
end
|
20
21
|
|
21
22
|
private
|
data/lib/shex/algebra/not.rb
CHANGED
@@ -6,14 +6,18 @@ module ShEx::Algebra
|
|
6
6
|
|
7
7
|
#
|
8
8
|
# S is a ShapeNot and for the shape expression se2 at shapeExpr, notSatisfies(n, se2, G, m).
|
9
|
-
# @param
|
10
|
-
# @return
|
11
|
-
# @raise
|
12
|
-
|
9
|
+
# @param (see Satisfiable#satisfies?)
|
10
|
+
# @return (see Satisfiable#satisfies?)
|
11
|
+
# @raise (see Satisfiable#satisfies?)
|
12
|
+
# @see [https://shexspec.github.io/spec/#shape-expression-semantics]
|
13
|
+
def satisfies?(focus)
|
13
14
|
status ""
|
14
|
-
|
15
|
-
|
16
|
-
|
15
|
+
satisfied_op = begin
|
16
|
+
operands.first.satisfies?(focus)
|
17
|
+
rescue ShEx::NotSatisfied => e
|
18
|
+
return satisfy satisfied: e.expression.unsatisfied
|
19
|
+
end
|
20
|
+
not_satisfied "Expression should not have matched", unsatisfied: satisfied_op
|
17
21
|
end
|
18
22
|
end
|
19
23
|
end
|
data/lib/shex/algebra/one_of.rb
CHANGED
@@ -7,25 +7,27 @@ module ShEx::Algebra
|
|
7
7
|
##
|
8
8
|
# `expr` is a OneOf and there is some shape expression `se2` in shapeExprs such that a `matches(T, se2, m)`...
|
9
9
|
#
|
10
|
-
# @param [Array<RDF::Statement>]
|
10
|
+
# @param [Array<RDF::Statement>] statements
|
11
11
|
# @return [Array<RDF::Statement]
|
12
|
-
def matches(
|
13
|
-
results = []
|
14
|
-
|
15
|
-
num_iters = 0
|
16
|
-
max = maximum
|
12
|
+
def matches(statements)
|
13
|
+
results, satisfied, unsatisfied = [], [], []
|
14
|
+
num_iters, max = 0, maximum
|
17
15
|
|
18
16
|
# OneOf is greedy, and consumes triples from every sub-expression, although only one is requred it succeed. Cardinality is somewhat complicated, as if two expressions match, this works for either a cardinality of one or two. Or two passes with just one match on each pass.
|
19
17
|
status ""
|
20
18
|
while num_iters < max
|
21
19
|
matched_something = operands.select {|o| o.is_a?(TripleExpression)}.any? do |op|
|
22
20
|
begin
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
21
|
+
matched_op = op.matches(statements)
|
22
|
+
satisfied << matched_op
|
23
|
+
results += matched_op.matched
|
24
|
+
statements -= matched_op.matched
|
25
|
+
status "matched #{matched_op.matched.to_sxp}"
|
26
|
+
rescue ShEx::NotMatched => e
|
27
|
+
status "not matched: #{e.message}"
|
28
|
+
op = op.dup
|
29
|
+
op.unmatched = statements - results
|
30
|
+
unsatisfied << op
|
29
31
|
false
|
30
32
|
end
|
31
33
|
end
|
@@ -35,16 +37,20 @@ module ShEx::Algebra
|
|
35
37
|
end
|
36
38
|
|
37
39
|
# Max violations handled in Shape
|
38
|
-
|
39
|
-
|
40
|
+
if num_iters < minimum
|
41
|
+
raise ShEx::NotMatched, "Minimum Cardinality Violation: #{results.length} < #{minimum}"
|
42
|
+
end
|
40
43
|
|
41
44
|
# Last, evaluate semantic acts
|
42
45
|
semantic_actions.all? do |op|
|
43
46
|
op.satisfies?(results)
|
44
47
|
end unless results.empty?
|
45
48
|
|
46
|
-
|
47
|
-
|
49
|
+
satisfy matched: results, satisfied: satisfied, unsatisfied: unsatisfied
|
50
|
+
rescue ShEx::NotMatched, ShEx::NotSatisfied => e
|
51
|
+
not_matched e.message,
|
52
|
+
matched: results, unmatched: (statements - results),
|
53
|
+
satisfied: satisfied, unsatisfied: unsatisfied
|
48
54
|
end
|
49
55
|
end
|
50
56
|
end
|
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'sparql/algebra'
|
2
|
-
require 'sparql/extensions'
|
3
2
|
|
4
3
|
module ShEx::Algebra
|
5
4
|
|
@@ -86,18 +85,86 @@ module ShEx::Algebra
|
|
86
85
|
# Does this operator a SemAct?
|
87
86
|
def semact?; false; end
|
88
87
|
|
88
|
+
##
|
89
|
+
# On a result instance, the statements that matched this expression.
|
90
|
+
# @return [Array<Statement>]
|
91
|
+
def matched
|
92
|
+
Array((operands.detect {|op| op.is_a?(Array) && op[0] == :matched} || [:matched])[1..-1])
|
93
|
+
end
|
94
|
+
def matched=(statements)
|
95
|
+
operands.delete_if {|op| op.is_a?(Array) && op[0] == :matched}
|
96
|
+
operands << statements.unshift(:matched) unless (statements || []).empty?
|
97
|
+
end
|
98
|
+
|
99
|
+
##
|
100
|
+
# On a result instance, the statements that did not match this expression (failure only).
|
101
|
+
# @return [Array<Statement>]
|
102
|
+
def unmatched
|
103
|
+
Array((operands.detect {|op| op.is_a?(Array) && op[0] == :unmatched} || [:unmatched])[1..-1])
|
104
|
+
end
|
105
|
+
def unmatched=(statements)
|
106
|
+
operands.delete_if {|op| op.is_a?(Array) && op[0] == :unmatched}
|
107
|
+
operands << statements.unshift(:unmatched) unless (statements || []).empty?
|
108
|
+
end
|
109
|
+
|
110
|
+
##
|
111
|
+
# On a result instance, the sub-expressions which were matched.
|
112
|
+
# @return [Array<Operator>]
|
113
|
+
def satisfied
|
114
|
+
Array((operands.detect {|op| op.is_a?(Array) && op[0] == :satisfied} || [:satisfied])[1..-1])
|
115
|
+
end
|
116
|
+
def satisfied=(ops)
|
117
|
+
operands.delete_if {|op| op.is_a?(Array) && op[0] == :satisfied}
|
118
|
+
operands << ops.unshift(:satisfied) unless (ops || []).empty?
|
119
|
+
end
|
120
|
+
|
121
|
+
##
|
122
|
+
# On a result instance, the sub-satisfieables which were not satisfied. (failure only).
|
123
|
+
# @return [Array<Operator>]
|
124
|
+
def unsatisfied
|
125
|
+
Array((operands.detect {|op| op.is_a?(Array) && op[0] == :unsatisfied} || [:unsatisfied])[1..-1])
|
126
|
+
end
|
127
|
+
def unsatisfied=(ops)
|
128
|
+
operands.delete_if {|op| op.is_a?(Array) && op[0] == :unsatisfied}
|
129
|
+
operands << ops.unshift(:unsatisfied) unless (ops || []).empty?
|
130
|
+
end
|
131
|
+
|
132
|
+
##
|
133
|
+
# Duplication this operand, and add `matched`, `unmatched`, `satisfied`, and `unsatisfied` operands for accessing downstream.
|
134
|
+
#
|
135
|
+
# @return [Operand]
|
136
|
+
def satisfy(matched: nil, unmatched: nil, satisfied: nil, unsatisfied: nil)
|
137
|
+
log_debug(self.class.const_get(:NAME), "satisfied", depth: options.fetch(:depth, 0))
|
138
|
+
expression = self.dup
|
139
|
+
expression.matched = Array(matched) if matched
|
140
|
+
expression.unmatched = Array(unmatched) if unmatched
|
141
|
+
expression.satisfied = Array(satisfied) if satisfied
|
142
|
+
expression.unsatisfied = Array(unsatisfied) if unsatisfied
|
143
|
+
expression
|
144
|
+
end
|
145
|
+
|
89
146
|
##
|
90
147
|
# Exception handling
|
91
|
-
def not_matched(message, **opts)
|
92
|
-
expression = opts.fetch(:expression, self)
|
93
|
-
|
94
|
-
|
148
|
+
def not_matched(message, matched: nil, unmatched: nil, satisfied: nil, unsatisfied: nil, **opts, &block)
|
149
|
+
expression = opts.fetch(:expression, self).satisfy(
|
150
|
+
matched: matched,
|
151
|
+
unmatched: unmatched,
|
152
|
+
satisfied: satisfied,
|
153
|
+
unsatisfied: unsatisfied)
|
154
|
+
exception = opts.fetch(:exception, ShEx::NotMatched)
|
155
|
+
status(message) {(block_given? ? block.call : "") + "expression: #{expression.to_sxp}"}
|
156
|
+
raise exception.new(message, expression: expression)
|
95
157
|
end
|
96
158
|
|
97
|
-
def not_satisfied(message, **opts)
|
98
|
-
expression = opts.fetch(:expression, self)
|
159
|
+
def not_satisfied(message, matched: nil, unmatched: nil, satisfied: nil, unsatisfied: nil, **opts)
|
160
|
+
expression = opts.fetch(:expression, self).satisfy(
|
161
|
+
matched: matched,
|
162
|
+
unmatched: unmatched,
|
163
|
+
satisfied: satisfied,
|
164
|
+
unsatisfied: unsatisfied)
|
99
165
|
exception = opts.fetch(:exception, ShEx::NotSatisfied)
|
100
|
-
|
166
|
+
status(message) {(block_given? ? block.call : "") + "expression: #{expression.to_sxp}"}
|
167
|
+
raise exception.new(message, expression: expression)
|
101
168
|
end
|
102
169
|
|
103
170
|
def structure_error(message, **opts)
|
@@ -107,7 +174,7 @@ module ShEx::Algebra
|
|
107
174
|
end
|
108
175
|
|
109
176
|
def status(message, &block)
|
110
|
-
|
177
|
+
log_debug(self.class.const_get(:NAME), message, depth: options.fetch(:depth, 0), &block)
|
111
178
|
true
|
112
179
|
end
|
113
180
|
|
@@ -128,10 +195,10 @@ module ShEx::Algebra
|
|
128
195
|
end
|
129
196
|
|
130
197
|
##
|
131
|
-
# Returns the
|
198
|
+
# Returns the binary S-Expression (SXP) representation of this operator.
|
132
199
|
#
|
133
200
|
# @return [Array]
|
134
|
-
# @see
|
201
|
+
# @see https://en.wikipedia.org/wiki/S-expression
|
135
202
|
def to_sxp_bin
|
136
203
|
operator = [self.class.const_get(:NAME)].flatten.first
|
137
204
|
[operator, *(operands || []).map(&:to_sxp_bin)]
|
data/lib/shex/algebra/or.rb
CHANGED
@@ -14,24 +14,30 @@ module ShEx::Algebra
|
|
14
14
|
|
15
15
|
#
|
16
16
|
# S is a ShapeOr and there is some shape expression se2 in shapeExprs such that satisfies(n, se2, G, m).
|
17
|
-
# @param
|
18
|
-
# @return
|
19
|
-
# @raise
|
20
|
-
def satisfies?(
|
21
|
-
|
22
|
-
operands.select {|o| o.is_a?(Satisfiable)}
|
17
|
+
# @param (see Satisfiable#satisfies?)
|
18
|
+
# @return (see Satisfiable#satisfies?)
|
19
|
+
# @raise (see Satisfiable#satisfies?)
|
20
|
+
def satisfies?(focus)
|
21
|
+
status ""
|
22
|
+
expressions = operands.select {|o| o.is_a?(Satisfiable)}
|
23
|
+
unsatisfied = []
|
24
|
+
expressions.any? do |op|
|
23
25
|
begin
|
24
|
-
op.satisfies?(
|
25
|
-
|
26
|
-
return true
|
26
|
+
matched_op = op.satisfies?(focus)
|
27
|
+
return satisfy satisfied: matched_op, unsatisfied: unsatisfied
|
27
28
|
rescue ShEx::NotSatisfied => e
|
28
|
-
|
29
|
-
|
29
|
+
status "unsatisfied #{focus}"
|
30
|
+
op = op.dup
|
31
|
+
op.satisfied = e.expression.satisfied
|
32
|
+
op.unsatisfied = e.expression.unsatisfied
|
33
|
+
unsatisfied << op
|
34
|
+
status("unsatisfied: #{e.message}")
|
30
35
|
false
|
31
36
|
end
|
32
37
|
end
|
33
38
|
|
34
|
-
not_satisfied "Expected some expression to be satisfied"
|
39
|
+
not_satisfied "Expected some expression to be satisfied",
|
40
|
+
unsatisfied: unsatisfied
|
35
41
|
end
|
36
42
|
end
|
37
43
|
end
|
@@ -1,36 +1,18 @@
|
|
1
1
|
require 'sparql/algebra'
|
2
|
-
require 'sparql/extensions'
|
3
2
|
|
4
3
|
module ShEx::Algebra
|
5
4
|
# Implements `satisfies?` and `not_satisfies?`
|
6
5
|
module Satisfiable
|
7
6
|
##
|
8
7
|
# Satisfies method
|
9
|
-
# @param [RDF::Resource]
|
10
|
-
# @return [
|
11
|
-
# @raise [ShEx::
|
8
|
+
# @param [RDF::Resource] focus
|
9
|
+
# @return [TripleExpression] with `matched` and `satisfied` accessors for matched triples and sub-expressions
|
10
|
+
# @raise [ShEx::NotMatched] with `expression` accessor to access `matched` and `unmatched` statements along with `satisfied` and `unsatisfied` operations.
|
12
11
|
# @see [https://shexspec.github.io/spec/#shape-expression-semantics]
|
13
|
-
def satisfies?(
|
12
|
+
def satisfies?(focus)
|
14
13
|
raise NotImplementedError, "#satisfies? Not implemented in #{self.class}"
|
15
14
|
end
|
16
15
|
|
17
|
-
##
|
18
|
-
# Satisfies method
|
19
|
-
# @param [RDF::Resource] n
|
20
|
-
# @return [Boolean] `true` if not satisfied, `false` if it does not apply
|
21
|
-
# @raise [ShEx::NotSatisfied] if satisfied
|
22
|
-
# @see [https://shexspec.github.io/spec/#shape-expression-semantics]
|
23
|
-
def not_satisfies?(n)
|
24
|
-
begin
|
25
|
-
satisfies?(n)
|
26
|
-
rescue ShEx::NotSatisfied => e
|
27
|
-
log_recover(self.class.const_get(:NAME), "ignore error: #{e.message}", depth: options.fetch(:depth, 0))
|
28
|
-
return true # Expected it to not satisfy
|
29
|
-
end
|
30
|
-
not_satisfied "Expression should not have matched"
|
31
|
-
end
|
32
|
-
alias_method :notSatisfies?, :not_satisfies?
|
33
|
-
|
34
16
|
##
|
35
17
|
# Included TripleExpressions
|
36
18
|
# @return [Array<TripleExpressions>]
|
data/lib/shex/algebra/schema.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
module ShEx::Algebra
|
2
2
|
##
|
3
3
|
class Schema < Operator
|
4
|
-
include Satisfiable
|
5
4
|
NAME = :schema
|
6
5
|
|
7
6
|
# Graph to validate
|
@@ -15,46 +14,69 @@ module ShEx::Algebra
|
|
15
14
|
##
|
16
15
|
# Match on schema. Finds appropriate shape for node, and matches that shape.
|
17
16
|
#
|
18
|
-
# @param [RDF::Resource]
|
19
|
-
# @param [RDF::Queryable]
|
20
|
-
# @param [Hash{RDF::Resource => RDF::Resource}]
|
17
|
+
# @param [RDF::Resource] focus
|
18
|
+
# @param [RDF::Queryable] graph
|
19
|
+
# @param [Hash{RDF::Resource => RDF::Resource}] map
|
21
20
|
# @param [Array<Schema, String>] shapeExterns ([])
|
22
21
|
# One or more schemas, or paths to ShEx schema resources used for finding external shapes.
|
23
|
-
# @return [
|
24
|
-
# @raise [ShEx::NotSatisfied]
|
25
|
-
|
26
|
-
|
27
|
-
@graph = g
|
22
|
+
# @return [Operand] Returns operand graph annotated with satisfied and unsatisfied operations.
|
23
|
+
# @raise [ShEx::NotSatisfied] along with operand graph described for return
|
24
|
+
def execute(focus, graph, map, shapeExterns: [], **options)
|
25
|
+
@graph = graph
|
28
26
|
@external_schemas = shapeExterns
|
27
|
+
focus = iri(focus)
|
29
28
|
# Make sure they're URIs
|
30
|
-
@map =
|
29
|
+
@map = (map || {}).inject({}) {|memo, (k,v)| memo.merge(iri(k).to_s => iri(v).to_s)}
|
31
30
|
|
32
31
|
# First, evaluate semantic acts
|
33
32
|
semantic_actions.all? do |op|
|
34
33
|
op.satisfies?([])
|
35
34
|
end
|
36
35
|
|
36
|
+
# Keep a new Schema, specifically for recording actions
|
37
|
+
satisfied_schema = Schema.new
|
37
38
|
# Next run any start expression
|
38
39
|
if start
|
39
40
|
status("start") {"expression: #{start.to_sxp}"}
|
40
|
-
start.satisfies?(
|
41
|
+
satisfied_schema.operands << start.satisfies?(focus)
|
41
42
|
end
|
42
43
|
|
43
|
-
|
44
|
+
# Add shape result(s)
|
45
|
+
satisfied_shapes = {}
|
46
|
+
satisfied_schema.operands << [:shapes, satisfied_shapes] unless shapes.empty?
|
47
|
+
|
48
|
+
label = @map[focus.to_s]
|
44
49
|
if label && !label.empty?
|
45
50
|
shape = shapes[label]
|
46
51
|
structure_error("No shape found for #{label}") unless shape
|
47
52
|
|
48
53
|
# If `n` is a Blank Node, we won't find it through normal matching, find an equivalent node in the graph having the same label
|
49
|
-
if
|
50
|
-
|
51
|
-
|
54
|
+
if focus.is_a?(RDF::Node)
|
55
|
+
n = graph.enum_term.detect {|t| t.id == focus.id}
|
56
|
+
focus = n if n
|
52
57
|
end
|
53
58
|
|
54
|
-
shape.satisfies?(
|
59
|
+
satisfied_shapes[label] = shape.satisfies?(focus)
|
55
60
|
end
|
56
61
|
status "schema satisfied"
|
57
|
-
|
62
|
+
satisfied_schema
|
63
|
+
end
|
64
|
+
|
65
|
+
##
|
66
|
+
# Match on schema. Finds appropriate shape for node, and matches that shape.
|
67
|
+
#
|
68
|
+
# @param [RDF::Resource] focus
|
69
|
+
# @param [RDF::Queryable] graph
|
70
|
+
# @param [Hash{RDF::Resource => RDF::Resource}] map
|
71
|
+
# @param [Array<Schema, String>] shapeExterns ([])
|
72
|
+
# One or more schemas, or paths to ShEx schema resources used for finding external shapes.
|
73
|
+
# @param [Hash{Symbol => Object}] options
|
74
|
+
# @option options [String] :base_uri
|
75
|
+
# @return [Boolean]
|
76
|
+
def satisfies?(focus, graph, map, shapeExterns: [], **options)
|
77
|
+
execute(focus, graph, map, options.merge(shapeExterns: shapeExterns))
|
78
|
+
rescue ShEx::NotSatisfied
|
79
|
+
false
|
58
80
|
end
|
59
81
|
|
60
82
|
##
|
@@ -62,8 +84,7 @@ module ShEx::Algebra
|
|
62
84
|
# @return [Hash{RDF::Resource => Operator}]
|
63
85
|
def shapes
|
64
86
|
@shapes ||= begin
|
65
|
-
shapes = operands.
|
66
|
-
detect {|op| op.is_a?(Array) && op.first == :shapes}
|
87
|
+
shapes = operands.detect {|op| op.is_a?(Array) && op.first == :shapes}
|
67
88
|
shapes = shapes ? shapes.last : {}
|
68
89
|
shapes.inject({}) do |memo, (label, operand)|
|
69
90
|
memo.merge(label.to_s => operand)
|
@@ -107,6 +128,34 @@ module ShEx::Algebra
|
|
107
128
|
enum_for(:each_descendant)
|
108
129
|
end
|
109
130
|
|
131
|
+
##
|
132
|
+
# Returns the Base URI defined for the parser,
|
133
|
+
# as specified or when parsing a BASE prologue element.
|
134
|
+
#
|
135
|
+
# @example
|
136
|
+
# base #=> RDF::URI('http://example.com/')
|
137
|
+
#
|
138
|
+
# @return [HRDF::URI]
|
139
|
+
def base_uri
|
140
|
+
RDF::URI(@options[:base_uri]) if @options[:base_uri]
|
141
|
+
end
|
142
|
+
|
143
|
+
# Create URIs
|
144
|
+
def iri(value)
|
145
|
+
# If we have a base URI, use that when constructing a new URI
|
146
|
+
case value
|
147
|
+
when RDF::Value then value
|
148
|
+
when /^_:/ then RDF::Node(value[2..-1].to_s)
|
149
|
+
else
|
150
|
+
value = RDF::URI(value)
|
151
|
+
if base_uri && value.relative?
|
152
|
+
base_uri.join(value)
|
153
|
+
else
|
154
|
+
value
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
110
159
|
##
|
111
160
|
# Start action, if any
|
112
161
|
def start
|
data/lib/shex/algebra/shape.rb
CHANGED
@@ -4,59 +4,77 @@ module ShEx::Algebra
|
|
4
4
|
include Satisfiable
|
5
5
|
NAME = :shape
|
6
6
|
|
7
|
-
|
7
|
+
##
|
8
|
+
# Let `outs` be the `arcsOut` in `remainder`: `outs = remainder ∩ arcsOut(G, n)`.
|
9
|
+
# @return [Array<RDF::Statement>]
|
10
|
+
attr_accessor :outs
|
11
|
+
|
12
|
+
##
|
13
|
+
# Let `matchables` be the triples in `outs` whose predicate appears in a {TripleConstraint} in `expression`. If `expression` is absent, `matchables = Ø` (the empty set).
|
14
|
+
# @return [Array<RDF::Statement>]
|
15
|
+
attr_accessor :matchables
|
16
|
+
|
17
|
+
##
|
18
|
+
# Let `unmatchables` be the triples in `outs` which are not in `matchables`. `matchables ∪ unmatchables = outs.`
|
19
|
+
# @return [Array<RDF::Statement>]
|
20
|
+
attr_accessor :unmatchables
|
21
|
+
|
8
22
|
# The `satisfies` semantics for a `Shape` depend on a matches function defined below. For a node `n`, shape `S`, graph `G`, and shapeMap `m`, `satisfies(n, S, G, m)`.
|
9
|
-
# @param
|
10
|
-
# @return
|
11
|
-
# @raise
|
12
|
-
def satisfies?(
|
23
|
+
# @param (see Satisfiable#satisfies?)
|
24
|
+
# @return (see Satisfiable#satisfies?)
|
25
|
+
# @raise (see Satisfiable#satisfies?)
|
26
|
+
def satisfies?(focus)
|
13
27
|
expression = operands.detect {|op| op.is_a?(TripleExpression)}
|
14
28
|
|
15
29
|
# neigh(G, n) is the neighbourhood of the node n in the graph G.
|
16
30
|
#
|
17
31
|
# neigh(G, n) = arcsOut(G, n) ∪ arcsIn(G, n)
|
18
|
-
arcs_in = schema.graph.query(object:
|
19
|
-
arcs_out = schema.graph.query(subject:
|
32
|
+
arcs_in = schema.graph.query(object: focus).to_a.sort_by(&:to_sxp)
|
33
|
+
arcs_out = schema.graph.query(subject: focus).to_a.sort_by(&:to_sxp)
|
20
34
|
neigh = (arcs_in + arcs_out).uniq
|
21
35
|
|
22
36
|
# `matched` is the subset of statements which match `expression`.
|
23
37
|
status("arcsIn: #{arcs_in.count}, arcsOut: #{arcs_out.count}")
|
24
|
-
|
38
|
+
matched_expression = expression.matches(neigh) if expression
|
39
|
+
matched = Array(matched_expression && matched_expression.matched)
|
25
40
|
|
26
41
|
# `remainder` is the set of unmatched statements
|
27
42
|
remainder = neigh - matched
|
28
43
|
|
29
44
|
# Let `outs` be the `arcsOut` in `remainder`: `outs = remainder ∩ arcsOut(G, n)`.
|
30
|
-
outs = remainder.select {|s| s.subject ==
|
45
|
+
@outs = remainder.select {|s| s.subject == focus}
|
31
46
|
|
32
47
|
# Let `matchables` be the triples in `outs` whose predicate appears in a `TripleConstraint` in `expression`. If `expression` is absent, `matchables = Ø` (the empty set).
|
33
48
|
predicates = expression ? expression.triple_constraints.map(&:predicate).uniq : []
|
34
|
-
matchables = outs.select {|s| predicates.include?(s.predicate)}
|
49
|
+
@matchables = outs.select {|s| predicates.include?(s.predicate)}
|
50
|
+
|
51
|
+
# Let `unmatchables` be the triples in `outs` which are not in `matchables`.
|
52
|
+
@unmatchables = outs - matchables
|
35
53
|
|
36
54
|
# No matchable can be matched by any TripleConstraint in expression
|
37
|
-
matchables.
|
38
|
-
expression.triple_constraints.
|
55
|
+
unmatched = matchables.select do |statement|
|
56
|
+
expression.triple_constraints.any? do |expr|
|
39
57
|
begin
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
end
|
44
|
-
rescue NotMatched
|
45
|
-
logger.recovering = false
|
46
|
-
# Expected not to match
|
58
|
+
statement.predicate == expr.predicate && expr.matches([statement])
|
59
|
+
rescue ShEx::NotMatched
|
60
|
+
false # Expected not to match
|
47
61
|
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
62
|
+
end if expression
|
63
|
+
end
|
64
|
+
unless unmatched.empty?
|
65
|
+
not_satisfied "Statements remain matching TripleConstraints",
|
66
|
+
matched: matched,
|
67
|
+
unmatched: unmatched,
|
68
|
+
satisfied: expression
|
69
|
+
end
|
56
70
|
|
57
71
|
# There is no triple in matchables whose predicate does not appear in extra.
|
58
|
-
matchables.
|
59
|
-
|
72
|
+
unmatched = matchables.reject {|st| extra.include?(st.predicate)}
|
73
|
+
unless unmatched.empty?
|
74
|
+
not_satisfied "Statements remains with predicate #{unmatched.map(&:predicate).compact.join(',')} not in extra",
|
75
|
+
matched: matched,
|
76
|
+
unmatched: unmatched,
|
77
|
+
satisfied: expression
|
60
78
|
end
|
61
79
|
|
62
80
|
# closed is false or unmatchables is empty.
|
@@ -69,10 +87,10 @@ module ShEx::Algebra
|
|
69
87
|
op.satisfies?(matched)
|
70
88
|
end unless matched.empty?
|
71
89
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
not_satisfied e.message
|
90
|
+
# FIXME: also record matchables, outs and others?
|
91
|
+
satisfy matched: matched
|
92
|
+
rescue ShEx::NotMatched => e
|
93
|
+
not_satisfied e.message, unsatisfied: e.expression
|
76
94
|
end
|
77
95
|
|
78
96
|
##
|
@@ -10,18 +10,17 @@ module ShEx::Algebra
|
|
10
10
|
end
|
11
11
|
|
12
12
|
##
|
13
|
-
# Satisfies
|
14
|
-
# @param
|
15
|
-
# @return
|
16
|
-
# @raise
|
13
|
+
# Satisfies referenced shape.
|
14
|
+
# @param (see Satisfiable#satisfies?)
|
15
|
+
# @return (see Satisfiable#satisfies?)
|
16
|
+
# @raise (see Satisfiable#satisfies?)
|
17
17
|
# @see [https://shexspec.github.io/spec/#shape-expression-semantics]
|
18
|
-
def satisfies?(
|
18
|
+
def satisfies?(focus)
|
19
19
|
status "ref #{operands.first.to_s}"
|
20
|
-
referenced_shape.satisfies?(
|
21
|
-
|
22
|
-
true
|
20
|
+
matched_shape = referenced_shape.satisfies?(focus)
|
21
|
+
satisfy satisfied: matched_shape
|
23
22
|
rescue ShEx::NotSatisfied => e
|
24
|
-
not_satisfied e.message
|
23
|
+
not_satisfied e.message, unsatisfied: e.expression
|
25
24
|
raise
|
26
25
|
end
|
27
26
|
|
data/lib/shex/algebra/start.rb
CHANGED
@@ -1,19 +1,19 @@
|
|
1
1
|
module ShEx::Algebra
|
2
2
|
##
|
3
3
|
class Start < Operator::Unary
|
4
|
+
include Satisfiable
|
4
5
|
NAME = :start
|
5
6
|
|
6
7
|
#
|
7
|
-
# @param
|
8
|
-
# @return
|
9
|
-
# @raise
|
10
|
-
def satisfies?(
|
8
|
+
# @param (see Satisfiable#satisfies?)
|
9
|
+
# @return (see Satisfiable#satisfies?)
|
10
|
+
# @raise (see Satisfiable#satisfies?)
|
11
|
+
def satisfies?(focus)
|
11
12
|
status ""
|
12
|
-
operands.first.satisfies?(
|
13
|
-
|
14
|
-
true
|
13
|
+
matched_op = operands.first.satisfies?(focus)
|
14
|
+
satisfy satisfied: matched_op
|
15
15
|
rescue ShEx::NotSatisfied => e
|
16
|
-
not_satisfied e.message
|
16
|
+
not_satisfied e.message, unsatisfied: e.expression
|
17
17
|
raise
|
18
18
|
end
|
19
19
|
end
|
@@ -7,46 +7,52 @@ module ShEx::Algebra
|
|
7
7
|
##
|
8
8
|
# In this case, we accept an array of statements, and match based on cardinality.
|
9
9
|
#
|
10
|
-
# @param
|
11
|
-
# @return
|
12
|
-
# @raise
|
13
|
-
def matches(
|
10
|
+
# @param (see TripleExpression#matches)
|
11
|
+
# @return (see TripleExpression#matches)
|
12
|
+
# @raise (see TripleExpression#matches)
|
13
|
+
def matches(statements)
|
14
14
|
status "predicate #{predicate}"
|
15
|
-
|
16
|
-
|
17
|
-
if max > 0
|
18
|
-
value = inverse? ? statement.subject : statement.object
|
15
|
+
results, satisfied, unsatisfied = [], [], []
|
16
|
+
num_iters, max = 0, maximum
|
19
17
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
18
|
+
statements.select {|st| st.predicate == predicate}.each do |statement|
|
19
|
+
break if num_iters == max # matched enough
|
20
|
+
|
21
|
+
value = inverse? ? statement.subject : statement.object
|
22
|
+
|
23
|
+
begin
|
24
|
+
shape && (matched_shape = shape.satisfies?(value))
|
25
|
+
status "matched #{statement.to_sxp}"
|
26
|
+
if matched_shape
|
27
|
+
matched_shape.matched = [statement]
|
28
|
+
statement = statement.dup.extend(ReferencedStatement)
|
29
|
+
statement.referenced = matched_shape
|
30
|
+
satisfied << matched_shape
|
26
31
|
end
|
27
|
-
|
28
|
-
|
32
|
+
results << statement
|
33
|
+
num_iters += 1
|
34
|
+
rescue ShEx::NotSatisfied => e
|
35
|
+
status "not satisfied: #{e.message}"
|
36
|
+
statement = statement.dup.extend(ReferencedStatement)
|
37
|
+
unmatched << statement.referenced = shape
|
29
38
|
end
|
30
39
|
end
|
31
40
|
|
32
41
|
# Max violations handled in Shape
|
33
|
-
|
34
|
-
results.length < minimum
|
42
|
+
if results.length < minimum
|
43
|
+
raise ShEx::NotMatched, "Minimum Cardinality Violation: #{results.length} < #{minimum}"
|
44
|
+
end
|
35
45
|
|
36
46
|
# Last, evaluate semantic acts
|
37
47
|
semantic_actions.all? do |op|
|
38
48
|
op.satisfies?(results)
|
39
49
|
end unless results.empty?
|
40
50
|
|
41
|
-
results
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
rescue ShEx::NotSatisfied => e
|
47
|
-
status "ignore error: #{e.message}"
|
48
|
-
logger.recovering = false
|
49
|
-
false
|
51
|
+
satisfy matched: results, satisfied: satisfied, unsatisfied: unsatisfied
|
52
|
+
rescue ShEx::NotMatched, ShEx::NotSatisfied => e
|
53
|
+
not_matched e.message,
|
54
|
+
matched: results, unmatched: (statements - results),
|
55
|
+
satisfied: satisfied, unsatisfied: unsatisfied
|
50
56
|
end
|
51
57
|
|
52
58
|
def predicate
|
@@ -1,19 +1,17 @@
|
|
1
1
|
require 'sparql/algebra'
|
2
|
-
require 'sparql/extensions'
|
3
2
|
|
4
3
|
module ShEx::Algebra
|
5
4
|
# Implements `neigh`, `arcs_out`, `args_in` and `matches`
|
6
5
|
module TripleExpression
|
7
|
-
|
8
6
|
##
|
9
7
|
# `matches`: asserts that a triple expression is matched by a set of triples that come from the neighbourhood of a node in an RDF graph. The expression `matches(T, expr, m)` indicates that a set of triples `T` can satisfy these rules...
|
10
8
|
#
|
11
9
|
# Behavior should be overridden in subclasses, which end by calling this through `super`.
|
12
10
|
#
|
13
|
-
# @param [Array<RDF::Statement>]
|
14
|
-
# @return [
|
15
|
-
# @raise NotMatched
|
16
|
-
def matches(
|
11
|
+
# @param [Array<RDF::Statement>] statements
|
12
|
+
# @return [TripleExpression] with `matched` accessor for matched triples
|
13
|
+
# @raise [ShEx::NotMatched] with `expression` accessor to access `matched` and `unmatched` statements along with `satisfied` and `unsatisfied` operations.
|
14
|
+
def matches(statements)
|
17
15
|
raise NotImplementedError, "#matches Not implemented in #{self.class}"
|
18
16
|
end
|
19
17
|
|
@@ -21,26 +19,44 @@ module ShEx::Algebra
|
|
21
19
|
# Included TripleConstraints
|
22
20
|
# @return [Array<TripleConstraints>]
|
23
21
|
def triple_constraints
|
24
|
-
operands.select
|
22
|
+
@triple_contraints ||= operands.select do |o|
|
23
|
+
o.is_a?(TripleExpression)
|
24
|
+
end.
|
25
|
+
map(&:triple_constraints).
|
26
|
+
flatten.
|
27
|
+
uniq
|
25
28
|
end
|
26
29
|
|
27
30
|
##
|
28
31
|
# Minimum constraint (defaults to 1)
|
29
32
|
# @return [Integer]
|
30
33
|
def minimum
|
31
|
-
|
32
|
-
|
34
|
+
@minimum ||= begin
|
35
|
+
op = operands.detect {|o| o.is_a?(Array) && o.first == :min} || [:min, 1]
|
36
|
+
op[1]
|
37
|
+
end
|
33
38
|
end
|
34
39
|
|
35
40
|
##
|
36
41
|
# Maximum constraint (defaults to 1)
|
37
42
|
# @return [Integer, Float::INFINITY]
|
38
43
|
def maximum
|
39
|
-
|
40
|
-
|
44
|
+
@maximum ||= begin
|
45
|
+
op = operands.detect {|o| o.is_a?(Array) && o.first == :max} || [:max, 1]
|
46
|
+
op[1] == '*' ? Float::INFINITY : op[1]
|
47
|
+
end
|
41
48
|
end
|
42
49
|
|
43
50
|
# This operator includes TripleExpression
|
44
51
|
def triple_expression?; true; end
|
45
52
|
end
|
53
|
+
|
54
|
+
module ReferencedStatement
|
55
|
+
# @return [ShEx::Algebra::Satisfiable] referenced operand which satisfied some of this statement
|
56
|
+
attr_accessor :referenced
|
57
|
+
|
58
|
+
def to_sxp_bin
|
59
|
+
referenced ? super + [referenced.to_sxp_bin] : super
|
60
|
+
end
|
61
|
+
end
|
46
62
|
end
|
data/lib/shex/parser.rb
CHANGED
@@ -12,6 +12,7 @@ module ShEx
|
|
12
12
|
include ShEx::Meta
|
13
13
|
include ShEx::Terminals
|
14
14
|
include EBNF::LL1::Parser
|
15
|
+
include RDF::Util::Logger
|
15
16
|
|
16
17
|
##
|
17
18
|
# Any additional options for the parser.
|
@@ -568,7 +569,7 @@ module ShEx
|
|
568
569
|
# @option options [#to_s] :anon_base ("b0")
|
569
570
|
# Basis for generating anonymous Nodes
|
570
571
|
# @option options [Boolean] :resolve_iris (false)
|
571
|
-
# Resolve prefix and relative IRIs, otherwise, when serializing the parsed
|
572
|
+
# Resolve prefix and relative IRIs, otherwise, when serializing the parsed SXP
|
572
573
|
# as S-Expressions, use the original prefixed and relative URIs along with `base` and `prefix`
|
573
574
|
# definitions.
|
574
575
|
# @option options [Boolean] :validate (false)
|
@@ -591,10 +592,6 @@ module ShEx
|
|
591
592
|
end
|
592
593
|
@input.encode!(Encoding::UTF_8) if @input.respond_to?(:encode!)
|
593
594
|
@options = {anon_base: "b0", validate: false}.merge(options)
|
594
|
-
@options[:debug] ||= case
|
595
|
-
when options[:progress] then 2
|
596
|
-
when options[:validate] then 1
|
597
|
-
end
|
598
595
|
|
599
596
|
debug("base IRI") {base_uri.inspect}
|
600
597
|
debug("validate") {validate?.inspect}
|
@@ -630,7 +627,9 @@ module ShEx
|
|
630
627
|
#
|
631
628
|
# @param [Symbol, #to_s] prod The starting production for the parser.
|
632
629
|
# It may be a URI from the grammar, or a symbol representing the local_name portion of the grammar URI.
|
633
|
-
# @return [
|
630
|
+
# @return [ShEx::Algebra::Schema] The executable parsed expression.
|
631
|
+
# @raise [ShEx::ParseError] when a syntax error is detected
|
632
|
+
# @raise [ShEx::StructureError, ArgumentError] on structural problems with schema
|
634
633
|
# @see http://www.w3.org/TR/sparql11-query/#sparqlAlgebra
|
635
634
|
# @see http://axel.deri.ie/sparqltutorial/ESWC2007_SPARQL_Tutorial_unit2b.pdf
|
636
635
|
def parse(prod = START)
|
@@ -641,17 +640,18 @@ module ShEx
|
|
641
640
|
) do |context, *data|
|
642
641
|
case context
|
643
642
|
when :trace
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
643
|
+
if options[:logger]
|
644
|
+
level, lineno, depth, *args = data
|
645
|
+
case level
|
646
|
+
when 0
|
647
|
+
log_error(*args, depth: depth, lineno: lineno)
|
648
|
+
when 1
|
649
|
+
log_warning(*args, depth: depth, lineno: lineno)
|
650
|
+
when 2
|
651
|
+
log_info(*args, depth: depth, lineno: lineno)
|
652
|
+
else
|
653
|
+
log_debug(*args, depth: depth, lineno: lineno)
|
654
|
+
end
|
655
655
|
end
|
656
656
|
end
|
657
657
|
end
|
@@ -735,7 +735,7 @@ module ShEx
|
|
735
735
|
#
|
736
736
|
# @return [HRDF::URI]
|
737
737
|
def base_uri
|
738
|
-
RDF::URI(@options[:base_uri])
|
738
|
+
RDF::URI(@options[:base_uri]) if @options[:base_uri]
|
739
739
|
end
|
740
740
|
|
741
741
|
##
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shex
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gregg Kellogg
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-12-
|
11
|
+
date: 2016-12-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rdf
|