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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a8b0ba57a643b21c61dc3ddb8a7b49323c39bc1f
4
- data.tar.gz: 82bdcc8715f967354d09e1990f801806778c39b6
3
+ metadata.gz: 3ba75ebd8c6632f82452b8cc6a19dbe69e86258d
4
+ data.tar.gz: a924845f0d0ffd7caa35926b98829345ee3d427a
5
5
  SHA512:
6
- metadata.gz: 6828f94a4e12c8d344ded59c69ec16e3c1f01990ea53110a1f21f1382eca27a2a1bdb53fa32e61e8c7095f2bdf0d74feb5c60f09e26b93b0626daad690171535
7
- data.tar.gz: 6ad0d4ae5c64c2d8076b270f0861749410a983ba2525db7fcabef7b405394af1432fcb63f08f625c75114e8bc8bd11da335e563560aed84290327a0cfb938e37
6
+ metadata.gz: 1e844351dfd63a5c158a088b6d83375e3f96382d1a56c3e7aaa8e037f4c3ae380bac4f04d1da9db3727053400563b96565d291cb7537974c920df636bf7d3614
7
+ data.tar.gz: 3797b22ce44300b723770608ee42a3fd03418f0c8497e4a370fdf960f47ad324c67b07ec061ca498a8fa2d683b97a4fab7c95ced34123840e2c968120e162418
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.2.0
@@ -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', 'sse'] format ('shexc')
21
+ # @param ['shexc', 'shexj', 'sxp'] format ('shexc')
24
22
  # @param [Hash{Symbol => Object}] options
25
- # @return [ShEx::Algebra::Schema] The executable parsed expression.
26
- # @raise [ShEx::ParseError] when a syntax error is detected
27
- # @raise [ShEx::StructureError, ArgumentError] on structural problems with schema
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 'sse'
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 ['shexc', 'shexj', 'sse'] format ('shexc')
45
- # @param [Hash{Symbol => Object}] options
46
- # any additional options (see `RDF::Reader#initialize` and `RDF::Format.for`)
47
- # @yield [ShEx::Algebra::Schema]
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 [RDF::Resource] focus
68
- # @param [RDF::Resource] shape
69
- # @param ['shexc', 'shexj', 'sse'] format ('shexc')
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; end
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
- # Initializes a new parser error instance.
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
- # @option options [String] :token (nil)
125
- # @option options [Integer] :lineno (nil)
126
- # @option options [Integer] :code (400)
127
- def initialize(message, options = {})
128
- @token = options[:token]
129
- @lineno = options[:lineno] || (@token.lineno if @token.respond_to?(:lineno))
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
@@ -31,8 +31,6 @@ module ShEx
31
31
  autoload :TripleExpression, 'shex/algebra/triple_expression'
32
32
  autoload :UnaryShape, 'shex/algebra/unary_shape'
33
33
  autoload :Value, 'shex/algebra/value'
34
-
35
- class NotMatched < ShEx::Error; end
36
34
  end
37
35
  end
38
36
 
@@ -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 [RDF::Resource] n
18
- # @return [Boolean] `true` when satisfied
19
- # @raise [ShEx::NotSatisfied] if not satisfied
20
- def satisfies?(n)
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
- operands.select {|o| o.is_a?(Satisfiable)}.each {|op| op.satisfies?(n)}
25
- status("satisfied")
26
- true
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(e.message)
29
- raise
31
+ not_satisfied e.message,
32
+ satisfied: satisfied,
33
+ unsatisfied: (expressions - satisfied)
30
34
  end
31
35
  end
32
36
  end
@@ -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>] t
10
+ # @param [Array<RDF::Statement>] statements
11
11
  # @return [Array<RDF::Statement>]
12
- # @raise NotMatched, ShEx::NotSatisfied
13
- def matches(t)
12
+ # @raise [ShEx::NotMatched]
13
+ def matches(statements)
14
14
  status ""
15
- results = []
16
- statements = t.dup
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
- matched = op.matches(statements - matched_this_iter)
25
- matched_this_iter += matched
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
- log_recover("eachOf: ignore error: #{e.message}", depth: options.fetch(:depth, 0))
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
- not_matched "Minimum Cardinality Violation: #{num_iters} < #{minimum}" if
39
- num_iters < minimum
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
- status "each of satisfied"
47
- results
48
- rescue NotMatched => e
49
- not_matched(e.message)
50
- raise
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?(n)
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?(n)
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>] t
15
+ # @param [Array<RDF::Statement>] statements
16
16
  # @return [Array<RDF::Statement>]
17
- # @raise NotMatched, ShEx::NotSatisfied
18
- def matches(t)
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
- results = expression.matches(t)
23
-
24
- # Max violations handled in Shape
25
- not_matched "Minimum Cardinality Violation: #{results.length} < #{minimum}" if
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(n, 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 [RDF::Resource] n
10
- # @return [Boolean] `true` if satisfied, `false` if it does not apply
11
- # @raise [ShEx::NotSatisfied] if not satisfied
12
- def satisfies?(n)
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?(n) &&
15
- satisfies_datatype?(n) &&
16
- satisfies_string_facet?(n) &&
17
- satisfies_numeric_facet?(n) &&
18
- satisfies_values?(n)
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
@@ -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 [RDF::Resource] n
10
- # @return [Boolean] `true` when satisfied, meaning that the operand was not satisfied
11
- # @raise [ShEx::NotSatisfied] if not satisfied, meaning that the operand was satisfied
12
- def satisfies?(n)
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
- operands.last.not_satisfies?(n)
15
- status "not satisfied"
16
- true
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
@@ -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>] t
10
+ # @param [Array<RDF::Statement>] statements
11
11
  # @return [Array<RDF::Statement]
12
- def matches(t)
13
- results = []
14
- statements = t.dup
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
- matched = op.matches(statements)
24
- results += matched
25
- statements -= matched
26
- status "matched #{t.first.to_sxp}"
27
- rescue NotMatched => e
28
- log_recover("oneOf: ignore error: #{e.message}", depth: options.fetch(:depth, 0))
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
- not_matched "Minimum Cardinality Violation: #{num_iters} < #{minimum}" if
39
- num_iters < minimum
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
- status "one of satisfied"
47
- results
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
- exception = opts.fetch(:exception, NotMatched)
94
- log_error(message, depth: options.fetch(:depth, 0), exception: exception) {"expression: #{expression.to_sxp}"}
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
- log_error(message, depth: options.fetch(:depth, 0), exception: exception) {"expression: #{expression.to_sxp}"}
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
- log_info(self.class.const_get(:NAME), message, depth: options.fetch(:depth, 0), &block)
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 SPARQL S-Expression (SSE) representation of this operator.
198
+ # Returns the binary S-Expression (SXP) representation of this operator.
132
199
  #
133
200
  # @return [Array]
134
- # @see http://openjena.org/wiki/SSE
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)]
@@ -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 [RDF::Resource] n
18
- # @return [Boolean] `true` if satisfied, `false` if it does not apply
19
- # @raise [ShEx::NotSatisfied] if not satisfied
20
- def satisfies?(n)
21
- any_not_satisfied = false
22
- operands.select {|o| o.is_a?(Satisfiable)}.any? do |op|
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?(n)
25
- status "satisfied #{n}"
26
- return true
26
+ matched_op = op.satisfies?(focus)
27
+ return satisfy satisfied: matched_op, unsatisfied: unsatisfied
27
28
  rescue ShEx::NotSatisfied => e
28
- log_recover("or: ignore error: #{e.message}", depth: options.fetch(:depth, 0))
29
- any_not_satisfied = e
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] n
10
- # @return [Boolean] `true` if satisfied, `false` if it does not apply
11
- # @raise [ShEx::NotSatisfied] if not satisfied
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?(n)
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>]
@@ -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] n
19
- # @param [RDF::Queryable] g
20
- # @param [Hash{RDF::Resource => RDF::Resource}] m
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 [Boolean] `true` if satisfied, `false` if it does not apply
24
- # @raise [ShEx::NotSatisfied] if not satisfied
25
- # FIXME: set of node/shape pairs
26
- def satisfies?(n, g, m, shapeExterns: [], **options)
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 = m.inject({}) {|memo, (k,v)| memo.merge(k.to_s => v.to_s)}
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?(n)
41
+ satisfied_schema.operands << start.satisfies?(focus)
41
42
  end
42
43
 
43
- label = @map[n.to_s]
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 n.is_a?(RDF::Node)
50
- nn = graph.enum_term.detect {|t| t.id == n.id}
51
- n = nn if nn
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?(n)
59
+ satisfied_shapes[label] = shape.satisfies?(focus)
55
60
  end
56
61
  status "schema satisfied"
57
- true
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
@@ -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 [RDF::Resource] n
10
- # @return [Boolean] `true` if satisfied
11
- # @raise [ShEx::NotSatisfied] if not satisfied
12
- def satisfies?(n)
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: n).to_a.sort_by(&:to_sxp)
19
- arcs_out = schema.graph.query(subject: n).to_a.sort_by(&:to_sxp)
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
- matched = expression ? expression.matches(neigh) : []
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 == n}
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.each do |statement|
38
- expression.triple_constraints.each do |expr|
55
+ unmatched = matchables.select do |statement|
56
+ expression.triple_constraints.any? do |expr|
39
57
  begin
40
- status "check matchable #{statement.to_sxp} against #{expr.to_sxp}"
41
- if statement.predicate == expr.predicate && expr.matches([statement])
42
- not_satisfied "Unmatched statement: #{statement.to_sxp} matched #{expr.to_sxp}"
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 if expression
50
-
51
- # There is no triple in `matchables` which matches a `TripleConstraint` in `expression`.
52
- # FIXME: Really run against every TripleConstraint?
53
-
54
- # Let `unmatchables` be the triples in `outs` which are not in `matchables`.
55
- unmatchables = outs - matchables
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.each do |statement|
59
- not_satisfied "Statement remains with predicate #{statement.predicate} not in extra" unless extra.include?(statement.predicate)
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
- true
73
- rescue NotMatched => e
74
- logger.recovering = false
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 method
14
- # @param [RDF::Resource] n
15
- # @return [Boolean] `true` if satisfied
16
- # @raise [ShEx::NotSatisfied] if not satisfied
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?(n)
18
+ def satisfies?(focus)
19
19
  status "ref #{operands.first.to_s}"
20
- referenced_shape.satisfies?(n)
21
- status "ref satisfied"
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
 
@@ -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 [RDF::Resource] n
8
- # @return [Boolean] `true` if satisfied
9
- # @raise [ShEx::NotSatisfied] if not satisfied
10
- def satisfies?(n)
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?(n)
13
- status("satisfied")
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 [Array<RDF::Statement>] t
11
- # @return [Array<RDF::Statement>]
12
- # @raise NotMatched, ShEx::NotSatisfied
13
- def matches(t)
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
- max = maximum
16
- results = t.select do |statement|
17
- if max > 0
18
- value = inverse? ? statement.subject : statement.object
15
+ results, satisfied, unsatisfied = [], [], []
16
+ num_iters, max = 0, maximum
19
17
 
20
- if statement.predicate == predicate && shape_expr_satisfies?(shape, value)
21
- status "matched #{statement.to_sxp}"
22
- max -= 1
23
- else
24
- status "no match #{statement.to_sxp}"
25
- false
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
- else
28
- false # matched enough
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
- not_matched "Minimum Cardinality Violation: #{results.length} < #{minimum}" if
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
- end
43
-
44
- def shape_expr_satisfies?(shape, value)
45
- shape.nil? || shape.satisfies?(value)
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>] t
14
- # @return [Array<RDF::Statement>]
15
- # @raise NotMatched, ShEx::NotSatisfied
16
- def matches(t)
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 {|o| o.is_a?(TripleExpression)}.map(&:triple_constraints).flatten.uniq
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
- op = operands.detect {|o| o.is_a?(Array) && o.first == :min} || [:min, 1]
32
- op[1]
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
- op = operands.detect {|o| o.is_a?(Array) && o.first == :max} || [:max, 1]
40
- op[1] == '*' ? Float::INFINITY : op[1]
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
@@ -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 SSE
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 [Array]
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
- level, lineno, depth, *args = data
645
- message = args.to_sse
646
- d_str = depth > 100 ? ' ' * 100 + '+' : ' ' * depth
647
- str = "[#{lineno}](#{level})#{d_str}#{message}".chop
648
- case @options[:debug]
649
- when Array
650
- @options[:debug] << str unless level > 2
651
- when TrueClass
652
- $stderr.puts str
653
- when Integer
654
- $stderr.puts(str) if level <= @options[:debug]
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.1.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 00:00:00.000000000 Z
11
+ date: 2016-12-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rdf