shex 0.4.0 → 0.5.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: 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