shex 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|