shex 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 96423cdff8bd77f2573ab32cd954d5217b2dfe20
4
- data.tar.gz: 510d72ce00605e864b37af848f39c54f19de96b8
3
+ metadata.gz: d66489982d96aa02cd6be18354daaa81934af243
4
+ data.tar.gz: 7385a3e9933b1c81e09b7d0a126a1760de9aa4c2
5
5
  SHA512:
6
- metadata.gz: 7af06418271fa283bf833b837b546a10870af1e6f60fea90099d8a75d8dc4097f1fce6c7800b0874b50efcddbafe97abe5e30888c51f1d97f62e46298b2c5e86
7
- data.tar.gz: 5c9a7e9c9f7373a64a4c0722edefb6d2c9c20e5a9aaad8920f539641ddca97d5499dec2d3c2f1aa719122405b381a2d52bf536f4bcabb71ea41702eafeb4aabc
6
+ metadata.gz: d060ca866830927b3342352249337b6c06b25060677a8fe46e3a017d9c7a269749e5e1e792928eb6c4324bbaffb3bab91da1fb560d5e97ef33df57847df281e7
7
+ data.tar.gz: 7bf7a3fb6bd9d121a00ba837a5b0725cb5b2732d04dc8c959a8ee37ff3a311340dd9ebdf4496254dc450c39ee139e92800773990a133d922ffb98e5e7f602160
data/README.md CHANGED
@@ -38,7 +38,7 @@ The ShEx gem implements a [ShEx][ShExSpec] Shape Expression engine.
38
38
  (doap:name;doap:description|dc:title;dc:description)+;
39
39
  doap:category*;
40
40
  doap:developer IRI;
41
- doap:implements [<https://shexspec.github.io/spec/>]
41
+ doap:implements [<http://shex.io/shex-semantics/>]
42
42
  }
43
43
  )
44
44
  graph = RDF::Graph.load("etc/doap.ttl")
@@ -109,26 +109,26 @@ The ShEx gem implements a [ShEx][ShExSpec] Shape Expression engine.
109
109
  ]
110
110
  }
111
111
  ],
112
- "min": 1, "max": "*"
112
+ "min": 1, "max": -1
113
113
  },
114
114
  {
115
115
  "type": "TripleConstraint",
116
116
  "predicate": "http://usefulinc.com/ns/doap#category",
117
117
  "valueExpr": {"type": "NodeConstraint", "nodeKind": "iri"},
118
- "min": 0, "max": "*"
118
+ "min": 0, "max": -1
119
119
  },
120
120
  {
121
121
  "type": "TripleConstraint",
122
122
  "predicate": "http://usefulinc.com/ns/doap#developer",
123
123
  "valueExpr": {"type": "NodeConstraint", "nodeKind": "iri"},
124
- "min": 1, "max": "*"
124
+ "min": 1, "max": -1
125
125
  },
126
126
  {
127
127
  "type": "TripleConstraint",
128
128
  "predicate": "http://usefulinc.com/ns/doap#implements",
129
129
  "valueExpr": {
130
130
  "type": "NodeConstraint",
131
- "values": ["https://shexspec.github.io/spec/"]
131
+ "values": ["http://shex.io/shex-semantics/"]
132
132
  }
133
133
  }
134
134
  ]
@@ -143,7 +143,7 @@ The ShEx gem implements a [ShEx][ShExSpec] Shape Expression engine.
143
143
  # => true
144
144
 
145
145
  ## Extensions
146
- ShEx has an extension mechanism using [Semantic Actions](https://shexspec.github.io/spec/#semantic-actions). Extensions may be implemented in Ruby ShEx by sub-classing {ShEx::Extension} and implementing {ShEx::Extension#visit} and possibly {ShEx::Extension#initialize}, {ShEx::Extension#enter}, {ShEx::Extension#exit}, and {ShEx::Extension#close}. The `#visit` method will be called as part of the `#satisfies?` operation.
146
+ ShEx has an extension mechanism using [Semantic Actions](http://shex.io/shex-semantics/#semantic-actions). Extensions may be implemented in Ruby ShEx by sub-classing {ShEx::Extension} and implementing {ShEx::Extension#visit} and possibly {ShEx::Extension#initialize}, {ShEx::Extension#enter}, {ShEx::Extension#exit}, and {ShEx::Extension#close}. The `#visit` method will be called as part of the `#satisfies?` operation.
147
147
 
148
148
  require 'shex'
149
149
  class ShEx::Test < ShEx::Extension("http://shex.io/extensions/Test/")
@@ -179,7 +179,7 @@ The result of parsing either ShExC or ShExJ is the creation of a set of executab
179
179
  ## Dependencies
180
180
 
181
181
  * [Ruby](http://ruby-lang.org/) (>= 2.2.2)
182
- * [RDF.rb](http://rubygems.org/gems/rdf) (>= 2.2)
182
+ * [RDF.rb](http://rubygems.org/gems/rdf) (~> 2.2)
183
183
 
184
184
  ## Installation
185
185
 
@@ -237,7 +237,7 @@ This repository uses [Git Flow](https://github.com/nvie/gitflow) to mange develo
237
237
  This is free and unencumbered public domain software. For more information,
238
238
  see <http://unlicense.org/> or the accompanying {file:LICENSE} file.
239
239
 
240
- [ShExSpec]: https://shexspec.github.io/spec/
240
+ [ShExSpec]: http://shex.io/shex-semantics/
241
241
  [RDF]: http://www.w3.org/RDF/
242
242
  [RDF.rb]: http://rubydoc.info/github/ruby-rdf/rdf
243
243
  [EBNF]: http://rubygems.org/gems/ebnf
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.0
1
+ 0.5.0
@@ -15,7 +15,7 @@
15
15
  doap:description "ShEx is an Shape Expression engine for the RDF.rb library suite."@en ;
16
16
  doap:created "2016-12-09"^^xsd:date ;
17
17
  doap:programming-language "Ruby" ;
18
- doap:implements <https://shexspec.github.io/spec/> ;
18
+ doap:implements <http://shex.io/shex-semantics/> ;
19
19
  doap:category <http://dbpedia.org/resource/Resource_Description_Framework>,
20
20
  <http://dbpedia.org/resource/Ruby_(programming_language)> ;
21
21
  doap:download-page <http://rubygems.org/gems/shex> ;
@@ -1,7 +1,7 @@
1
1
  ##
2
2
  # A ShEx runtime for RDF.rb.
3
3
  #
4
- # @see https://shexspec.github.io/spec/#shexc
4
+ # @see http://shex.io/shex-semantics/#shexc
5
5
  module ShEx
6
6
  autoload :Algebra, 'shex/algebra'
7
7
  autoload :Meta, 'shex/meta'
@@ -11,7 +11,7 @@ module ShEx
11
11
  autoload :VERSION, 'shex/version'
12
12
 
13
13
  # Location of the ShEx JSON-LD context
14
- CONTEXT = "https://shexspec.github.io/context.jsonld"
14
+ CONTEXT = "http://www.w3.org/ns/shex.jsonld"
15
15
 
16
16
  # Extensions defined in this gem
17
17
  EXTENSIONS = %w{test}
@@ -71,11 +71,11 @@ module ShEx
71
71
  # @param (see ShEx::Algebra::Schema#execute)
72
72
  # @return (see ShEx::Algebra::Schema#execute)
73
73
  # @raise (see ShEx::Algebra::Schema#execute)
74
- def self.execute(expression, queryable, focus, shape, format: 'shexc', **options)
74
+ def self.execute(expression, queryable, map, format: 'shexc', **options)
75
75
  shex = self.parse(expression, options.merge(format: format))
76
76
  queryable = queryable || RDF::Graph.new
77
77
 
78
- shex.execute(focus, queryable, {focus => shape}, options)
78
+ shex.execute(queryable, map, options)
79
79
  end
80
80
 
81
81
  ##
@@ -89,11 +89,11 @@ module ShEx
89
89
  # @param (see ShEx::Algebra::Schema#satisfies?)
90
90
  # @return (see ShEx::Algebra::Schema#satisfies?)
91
91
  # @raise (see ShEx::Algebra::Schema#satisfies?)
92
- def self.satisfies?(expression, queryable, focus, shape, format: 'shexc', **options)
92
+ def self.satisfies?(expression, queryable, map, format: 'shexc', **options)
93
93
  shex = self.parse(expression, options.merge(format: format))
94
94
  queryable = queryable || RDF::Graph.new
95
95
 
96
- shex.satisfies?(focus, queryable, {focus => shape}, options)
96
+ shex.satisfies?(queryable, map, options)
97
97
  end
98
98
 
99
99
  ##
@@ -129,14 +129,14 @@ module ShEx
129
129
  class NotSatisfied < Error
130
130
  ##
131
131
  # The expression which was not satified
132
- # @return [ShEx::Satisfiable]
132
+ # @return [ShEx::Algebra::ShapeExpression]
133
133
  attr_reader :expression
134
134
 
135
135
  ##
136
136
  # Initializes a new parser error instance.
137
137
  #
138
- # @param [String, #to_s] message
139
- # @param [Satisfiable] expression (self)
138
+ # @param [String, #to_s] message
139
+ # @param [ShEx::Algebra::ShapeExpression] expression
140
140
  def initialize(message, expression: self)
141
141
  @expression = expression
142
142
  super(message.to_s)
@@ -157,8 +157,8 @@ module ShEx
157
157
  ##
158
158
  # Initializes a new parser error instance.
159
159
  #
160
- # @param [String, #to_s] message
161
- # @param [Satisfiable] expression (self)
160
+ # @param [String, #to_s] message
161
+ # @param [ShEx::Algebra::TripleExpression] expression
162
162
  def initialize(message, expression: self)
163
163
  @expression = expression
164
164
  super(message.to_s)
@@ -7,25 +7,31 @@ module ShEx
7
7
  #
8
8
  # @author [Gregg Kellogg](http://greggkellogg.net/)
9
9
  module Algebra
10
- autoload :And, 'shex/algebra/and'
11
- autoload :Annotation, 'shex/algebra/annotation'
12
- autoload :EachOf, 'shex/algebra/each_of'
13
- autoload :External, 'shex/algebra/external'
14
- autoload :Not, 'shex/algebra/not'
15
- autoload :NodeConstraint, 'shex/algebra/node_constraint'
16
- autoload :OneOf, 'shex/algebra/one_of'
17
- autoload :Operator, 'shex/algebra/operator'
18
- autoload :Or, 'shex/algebra/or'
19
- autoload :Schema, 'shex/algebra/schema'
20
- autoload :SemAct, 'shex/algebra/semact'
21
- autoload :ShapeExpression, 'shex/algebra/shape_expression'
22
- autoload :Shape, 'shex/algebra/shape'
23
- autoload :Start, 'shex/algebra/start'
24
- autoload :Stem, 'shex/algebra/stem'
25
- autoload :StemRange, 'shex/algebra/stem_range'
10
+ autoload :And, 'shex/algebra/and'
11
+ autoload :Annotation, 'shex/algebra/annotation'
12
+ autoload :EachOf, 'shex/algebra/each_of'
13
+ autoload :External, 'shex/algebra/external'
14
+ autoload :IriStem, 'shex/algebra/stem'
15
+ autoload :IriStemRange, 'shex/algebra/stem_range'
16
+ autoload :LanguageStem, 'shex/algebra/stem'
17
+ autoload :LanguageStemRange,'shex/algebra/stem_range'
18
+ autoload :LiteralStem, 'shex/algebra/stem'
19
+ autoload :LiteralStemRange, 'shex/algebra/stem_range'
20
+ autoload :NodeConstraint, 'shex/algebra/node_constraint'
21
+ autoload :Not, 'shex/algebra/not'
22
+ autoload :OneOf, 'shex/algebra/one_of'
23
+ autoload :Operator, 'shex/algebra/operator'
24
+ autoload :Or, 'shex/algebra/or'
25
+ autoload :Schema, 'shex/algebra/schema'
26
+ autoload :SemAct, 'shex/algebra/semact'
27
+ autoload :Shape, 'shex/algebra/shape'
28
+ autoload :ShapeExpression, 'shex/algebra/shape_expression'
29
+ autoload :Start, 'shex/algebra/start'
30
+ autoload :Stem, 'shex/algebra/stem'
31
+ autoload :StemRange, 'shex/algebra/stem_range'
26
32
  autoload :TripleConstraint, 'shex/algebra/triple_constraint'
27
33
  autoload :TripleExpression, 'shex/algebra/triple_expression'
28
- autoload :Value, 'shex/algebra/value'
34
+ autoload :Value, 'shex/algebra/value'
29
35
 
30
36
 
31
37
  ##
@@ -46,22 +52,26 @@ module ShEx
46
52
  def self.from_shexj(operator, options = {})
47
53
  raise ArgumentError unless operator.is_a?(Hash)
48
54
  klass = case operator['type']
49
- when 'Annotation' then Annotation
50
- when 'EachOf' then EachOf
51
- when 'NodeConstraint' then NodeConstraint
52
- when 'OneOf' then OneOf
53
- when 'Schema' then Schema
54
- when 'SemAct' then SemAct
55
- when 'Shape' then Shape
56
- when 'ShapeAnd' then And
57
- when 'ShapeExternal' then External
58
- when 'ShapeNot' then Not
59
- when 'ShapeOr' then Or
60
- when 'Stem' then Stem
61
- when 'StemRange' then StemRange
62
- when 'TripleConstraint' then TripleConstraint
63
- when 'Wildcard' then StemRange
64
- else raise ArgumentError, "unknown type #{operator['type']}"
55
+ when 'Annotation' then Annotation
56
+ when 'EachOf' then EachOf
57
+ when 'IriStem' then IriStem
58
+ when 'IriStemRange' then IriStemRange
59
+ when 'LanguageStem' then LanguageStem
60
+ when 'LanguageStemRange' then LanguageStemRange
61
+ when 'LiteralStem' then LiteralStem
62
+ when 'LiteralStemRange' then LiteralStemRange
63
+ when 'NodeConstraint' then NodeConstraint
64
+ when 'OneOf' then OneOf
65
+ when 'Schema' then Schema
66
+ when 'SemAct' then SemAct
67
+ when 'Shape' then Shape
68
+ when 'ShapeAnd' then And
69
+ when 'ShapeExternal' then External
70
+ when 'ShapeNot' then Not
71
+ when 'ShapeOr' then Or
72
+ when 'TripleConstraint' then TripleConstraint
73
+ when 'Wildcard' then StemRange
74
+ else raise ArgumentError, "unknown type #{operator['type'].inspect}"
65
75
  end
66
76
 
67
77
  klass.from_shexj(operator, options)
@@ -1,3 +1,4 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  module ShEx::Algebra
2
3
  ##
3
4
  class NodeConstraint < Operator
@@ -58,7 +59,7 @@ module ShEx::Algebra
58
59
  return true unless dt
59
60
 
60
61
  not_satisfied "Node was #{value.inspect}, expected datatype #{dt}", depth: depth unless
61
- value.is_a?(RDF::Literal) && value.datatype == RDF::URI(dt)
62
+ value.is_a?(RDF::Literal) && value.datatype == RDF::URI(dt) && value.valid?
62
63
  status "right datatype: #{value}: #{dt}", depth: depth
63
64
  true
64
65
  end
@@ -68,11 +69,18 @@ module ShEx::Algebra
68
69
  # Checks all length/minlength/maxlength/pattern facets against the string representation of the value.
69
70
  # @return [Boolean] `true` if satisfied, `false` if it does not apply
70
71
  # @raise [ShEx::NotSatisfied] if not satisfied
72
+ # @todo using the XPath regexp engine supports additional flags "s" and "q"
71
73
  def satisfies_string_facet?(value, depth: 0)
72
74
  length = op_fetch(:length)
73
75
  minlength = op_fetch(:minlength)
74
76
  maxlength = op_fetch(:maxlength)
75
- pattern = op_fetch(:pattern)
77
+ pat = (operands.detect {|op| op.is_a?(Array) && op[0] == :pattern} || [])
78
+ pattern = pat[1]
79
+
80
+ flags = 0
81
+ flags |= Regexp::EXTENDED if pat[2].to_s.include?("x")
82
+ flags |= Regexp::IGNORECASE if pat[2].to_s.include?("i")
83
+ flags |= Regexp::MULTILINE if pat[2].to_s.include?("m")
76
84
 
77
85
  return true if (length || minlength || maxlength || pattern).nil?
78
86
 
@@ -80,6 +88,7 @@ module ShEx::Algebra
80
88
  when RDF::Node then value.id
81
89
  else value.to_s
82
90
  end
91
+
83
92
  not_satisfied "Node #{v_s.inspect} length not #{length}", depth: depth if
84
93
  length && v_s.length != length.to_i
85
94
  not_satisfied"Node #{v_s.inspect} length < #{minlength}", depth: depth if
@@ -87,7 +96,7 @@ module ShEx::Algebra
87
96
  not_satisfied "Node #{v_s.inspect} length > #{maxlength}", depth: depth if
88
97
  maxlength && v_s.length > maxlength.to_i
89
98
  not_satisfied "Node #{v_s.inspect} does not match #{pattern}", depth: depth if
90
- pattern && !Regexp.new(pattern).match(v_s)
99
+ pattern && !Regexp.new(pattern, flags).match(v_s)
91
100
  status "right string facet: #{value}", depth: depth
92
101
  true
93
102
  end
@@ -19,7 +19,7 @@ module ShEx::Algebra
19
19
  # @param (see ShapeExpression#satisfies?)
20
20
  # @return (see ShapeExpression#satisfies?)
21
21
  # @raise (see ShapeExpression#satisfies?)
22
- # @see [https://shexspec.github.io/spec/#shape-expression-semantics]
22
+ # @see [http://shex.io/shex-semantics/#shape-expression-semantics]
23
23
  def satisfies?(focus, depth: 0)
24
24
  status ""
25
25
  op = expressions.last
@@ -267,12 +267,16 @@ module ShEx::Algebra
267
267
 
268
268
  operator.each do |k, v|
269
269
  case k
270
- when /length|pattern|clusive|digits/ then operands << [k.to_sym, RDF::Literal(v)]
270
+ when /length|clusive|digits/ then operands << [k.to_sym, RDF::Literal(v)]
271
271
  when 'id' then id = iri(v, options)
272
- when 'min', 'max' then operands << [k.to_sym, v]
272
+ when 'flags' then ; # consumed in pattern below
273
+ when 'min', 'max' then operands << [k.to_sym, (v == -1 ? '*' : v)]
273
274
  when 'inverse', 'closed' then operands << k.to_sym
274
275
  when 'nodeKind' then operands << v.to_sym
275
276
  when 'object' then operands << value(v, options)
277
+ when 'pattern'
278
+ # Include flags as well
279
+ operands << [:pattern, RDF::Literal(v), operator['flags']].compact
276
280
  when 'start'
277
281
  if v.is_a?(String)
278
282
  operands << Start.new(iri(v, options))
@@ -291,7 +295,7 @@ module ShEx::Algebra
291
295
  end
292
296
  when 'stem', 'name'
293
297
  # Value may be :wildcard for stem
294
- operands << (v.is_a?(Symbol) ? v : iri(v, options))
298
+ operands << (v.is_a?(Symbol) ? v : value(v, options))
295
299
  when 'predicate' then operands << [:predicate, iri(v, options)]
296
300
  when 'extra', 'datatype'
297
301
  v = [v] unless v.is_a?(Array)
@@ -299,7 +303,7 @@ module ShEx::Algebra
299
303
  when 'exclusions'
300
304
  v = [v] unless v.is_a?(Array)
301
305
  operands << v.map do |op|
302
- op.is_a?(Hash) ?
306
+ op.is_a?(Hash) && op.has_key?('type') ?
303
307
  ShEx::Algebra.from_shexj(op, options) :
304
308
  value(op, options)
305
309
  end.unshift(:exclusions)
@@ -345,11 +349,12 @@ module ShEx::Algebra
345
349
  when Array
346
350
  # First element should be a symbol
347
351
  case sym = op.first
348
- when :datatype,
349
- :pattern then obj[op.first.to_s] = op.last.to_s
352
+ when :datatype then obj['datatype'] = op.last.to_s
350
353
  when :exclusions then obj['exclusions'] = Array(op[1..-1]).map {|v| serialize_value(v)}
351
354
  when :extra then (obj['extra'] ||= []).concat Array(op[1..-1]).map(&:to_s)
352
- # FIXME Shapes should be an array, not a hash
355
+ when :pattern
356
+ obj['pattern'] = op[1]
357
+ obj['flags'] = op[2] if op[2]
353
358
  when :shapes then obj['shapes'] = Array(op[1..-1]).map {|v| v.to_h}
354
359
  when :minlength,
355
360
  :maxlength,
@@ -360,7 +365,7 @@ module ShEx::Algebra
360
365
  :maxexclusive,
361
366
  :totaldigits,
362
367
  :fractiondigits then obj[op.first.to_s] = op.last.object
363
- when :min, :max then obj[op.first.to_s] = op.last
368
+ when :min, :max then obj[op.first.to_s] = op.last == '*' ? -1 : op.last
364
369
  when :predicate then obj[op.first.to_s] = op.last.to_s
365
370
  when :base, :prefix
366
371
  # Ignore base and prefix
@@ -378,7 +383,7 @@ module ShEx::Algebra
378
383
  end
379
384
  when RDF::Value
380
385
  case self
381
- when Stem, StemRange then obj['stem'] = op.to_s
386
+ when Stem, StemRange then obj['stem'] = serialize_value(op)
382
387
  when SemAct then obj[op.is_a?(RDF::URI) ? 'name' : 'code'] = op.to_s
383
388
  when TripleConstraint then obj['valueExpr'] = op.to_s
384
389
  when Shape then obj['expression'] = op.to_s
@@ -517,10 +522,10 @@ module ShEx::Algebra
517
522
  case value
518
523
  when Hash
519
524
  # Either a value object or a node reference
520
- if value['uri']
521
- iri(value['uri'], options)
522
- elsif value['value']
523
- RDF::Literal(value['value'], datatype: value['type'], language: value['language'])
525
+ if value['uri'] || value['@id']
526
+ iri(value['uri'] || value['@id'], options)
527
+ elsif value['value'] || value['@value']
528
+ RDF::Literal(value['value'] || value['@value'], datatype: value['type'] || value['@type'], language: value['language'] || value['@language'])
524
529
  else
525
530
  ShEx::Algebra.from_shexj(value, options)
526
531
  end
@@ -541,6 +546,8 @@ module ShEx::Algebra
541
546
  merge(value.has_language? ? {'language' => value.language.to_s} : {})
542
547
  when RDF::Resource
543
548
  value.to_s
549
+ when String
550
+ {'value' => value}
544
551
  else value.to_h
545
552
  end
546
553
  end
@@ -36,19 +36,22 @@ module ShEx::Algebra
36
36
  ##
37
37
  # Match on schema. Finds appropriate shape for node, and matches that shape.
38
38
  #
39
- # @param [RDF::Term] focus
40
39
  # @param [RDF::Queryable] graph
41
- # @param [Hash{RDF::Resource => RDF::Resource}] map
40
+ # @param [Hash{RDF::Term => <RDF::Resource>}, Array<Array(RDF::Term, RDF::Resource)>] map
41
+ # A set of (`term`, `resource`) pairs where `term` is a node within `graph`, and `resource` identifies a shape
42
+ # @param [Array<RDF::Term>] *focus
43
+ # One or more nodes within `graph` for which to run the start expression.
42
44
  # @param [Array<Schema, String>] shapeExterns ([])
43
45
  # One or more schemas, or paths to ShEx schema resources used for finding external shapes.
44
- # @return [Operand] Returns operand graph annotated with satisfied and unsatisfied operations.
46
+ # @return [Hash{RDF::Term => Array<ShapeResult>}] Returns _ShapeResults_, a hash of graph nodes to the results of their associated shapes
45
47
  # @param [Hash{Symbol => Object}] options
46
48
  # @option options [String] :base_uri (for resolving focus)
47
- # @raise [ShEx::NotSatisfied] along with operand graph described for return
48
- def execute(focus, graph, map, shapeExterns: [], depth: 0, **options)
49
- @graph, @shapes_entered = graph, {}
49
+ # @raise [ShEx::NotSatisfied] along with individual shape results
50
+ def execute(graph, map, focus: [], shapeExterns: [], depth: 0, **options)
51
+ @graph, @shapes_entered, results = graph, {}, {}
50
52
  @external_schemas = shapeExterns
51
- focus = value(focus, options)
53
+ @extensions = {}
54
+ focus = Array(focus).map {|f| value(f, options)}
52
55
 
53
56
  logger = options[:logger] || @options[:logger]
54
57
  each_descendant do |op|
@@ -57,7 +60,6 @@ module ShEx::Algebra
57
60
  end
58
61
 
59
62
  # Initialize Extensions
60
- @extensions = {}
61
63
  each_descendant do |op|
62
64
  next unless op.is_a?(SemAct)
63
65
  name = op.operands.first.to_s
@@ -67,36 +69,74 @@ module ShEx::Algebra
67
69
  end
68
70
 
69
71
  # If `n` is a Blank Node, we won't find it through normal matching, find an equivalent node in the graph having the same id
70
- graph_focus = graph.enum_term.detect {|t| t.node? && t.id == focus.id} if focus.is_a?(RDF::Node)
71
- graph_focus ||= focus
72
-
73
- # Make sure they're URIs
74
- @map = (map || {}).inject({}) {|memo, (k,v)| memo.merge(value(k) => iri(v))}
72
+ @map = case map
73
+ when Hash
74
+ map.inject({}) do |memo, (node, shapes)|
75
+ gnode = graph.enum_term.detect {|t| t.node? && t.id == node.id} if node.is_a?(RDF::Node)
76
+ node = gnode if gnode
77
+ memo.merge(node => Array(shapes))
78
+ end
79
+ when Array
80
+ map.inject({}) do |memo, (node, shape)|
81
+ gnode = graph.enum_term.detect {|t| t.node? && t.id == node.id} if node.is_a?(RDF::Node)
82
+ node = gnode if gnode
83
+ (memo[node] ||= []).concat(Array(shape))
84
+ memo
85
+ end
86
+ when nil then {}
87
+ else
88
+ structure_error "Unrecognized shape map: #{map.inspect}"
89
+ end
75
90
 
76
91
  # First, evaluate semantic acts
77
92
  semantic_actions.all? do |op|
78
93
  op.satisfies?([], depth: depth + 1)
79
94
  end
80
95
 
81
- # Keep a new Schema, specifically for recording actions
82
- satisfied_schema = Schema.new
83
96
  # Next run any start expression
84
- if start
85
- satisfied_schema.operands << start.satisfies?(focus, depth: depth + 1)
97
+ if !focus.empty?
98
+ if start
99
+ focus.each do |node|
100
+ node = graph.enum_term.detect {|t| t.node? && t.id == node.id} if node.is_a?(RDF::Node)
101
+ sr = ShapeResult.new(RDF::URI("http://www.w3.org/ns/shex#Start"))
102
+ (results[node] ||= []) << sr
103
+ begin
104
+ sr.expression = start.satisfies?(node, depth: depth + 1)
105
+ sr.result = true
106
+ rescue ShEx::NotSatisfied => e
107
+ sr.expression = e.expression
108
+ sr.result = false
109
+ end
110
+ end
111
+ else
112
+ structure_error "Focus nodes with no start"
113
+ end
86
114
  end
87
115
 
88
- # Add shape result(s)
89
- satisfied_shapes = {}
90
- satisfied_schema.operands << [:shapes, satisfied_shapes] unless shapes.empty?
91
-
92
116
  # Match against all shapes associated with the ids for focus
93
- Array(@map[focus]).each do |id|
94
- enter_shape(id, focus) do |shape|
95
- satisfied_shapes[id] = shape.satisfies?(graph_focus, depth: depth + 1)
117
+ @map.each do |node, shapes|
118
+ results[node] ||= []
119
+ shapes.each do |id|
120
+ enter_shape(id, node) do |shape|
121
+ sr = ShapeResult.new(id)
122
+ results[node] << sr
123
+ begin
124
+ sr.expression = shape.satisfies?(node, depth: depth + 1)
125
+ sr.result = true
126
+ rescue ShEx::NotSatisfied => e
127
+ sr.expression = e.expression
128
+ sr.result = false
129
+ end
130
+ end
96
131
  end
97
132
  end
98
- status "schema satisfied", depth: depth
99
- satisfied_schema
133
+
134
+ if results.values.flatten.all? {|sr| sr.result}
135
+ status "schema satisfied", depth: depth
136
+ results
137
+ else
138
+ raise ShEx::NotSatisfied.new("Graph does not conform to schema", expression: results)
139
+ end
100
140
  ensure
101
141
  # Close Semantic Action extensions
102
142
  @extensions.values.each {|ext| ext.close(schema: self, depth: depth, **options)}
@@ -105,16 +145,12 @@ module ShEx::Algebra
105
145
  ##
106
146
  # Match on schema. Finds appropriate shape for node, and matches that shape.
107
147
  #
108
- # @param [RDF::Resource] focus
109
- # @param [RDF::Queryable] graph
110
- # @param [Hash{RDF::Resource => RDF::Resource}] map
111
- # @param [Array<Schema, String>] shapeExterns ([])
112
- # One or more schemas, or paths to ShEx schema resources used for finding external shapes.
148
+ # @param (see ShEx::Algebra::Schema#execute)
113
149
  # @param [Hash{Symbol => Object}] options
114
150
  # @option options [String] :base_uri
115
151
  # @return [Boolean]
116
- def satisfies?(focus, graph, map, shapeExterns: [], **options)
117
- execute(focus, graph, map, options.merge(shapeExterns: shapeExterns))
152
+ def satisfies?(graph, map, **options)
153
+ execute(graph, map, **options)
118
154
  rescue ShEx::NotSatisfied
119
155
  false
120
156
  end
@@ -198,4 +234,41 @@ module ShEx::Algebra
198
234
  super
199
235
  end
200
236
  end
237
+
238
+ # A shape result
239
+ class ShapeResult
240
+ # The label of the shape within the schema, or a URI indicating a start shape
241
+ # @return [RDF::Resource]
242
+ attr_reader :shape
243
+
244
+ # Does the node conform to the shape
245
+ # @return [Boolean]
246
+ attr_accessor :result
247
+
248
+ # The annotated {Operator} indicating processing results
249
+ # @return [ShEx::Algebra::Operator]
250
+ attr_accessor :expression
251
+
252
+ # Holds the result of processing a shape
253
+ # @param [RDF::Resource] label
254
+ # @return [ShapeResult]
255
+ def initialize(shape)
256
+ @shape = shape
257
+ end
258
+
259
+ # The SXP of {#expression}
260
+ # @return [String]
261
+ def reason
262
+ SXP::Generator.string(expression.to_sxp_bin)
263
+ end
264
+
265
+ ##
266
+ # Returns the binary S-Expression (SXP) representation of this result.
267
+ #
268
+ # @return [Array]
269
+ # @see https://en.wikipedia.org/wiki/S-expression
270
+ def to_sxp_bin
271
+ [:ShapeResult, shape, result, expression].map(&:to_sxp_bin)
272
+ end
273
+ end
201
274
  end