shex 0.6.2 → 0.7.1

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
  SHA256:
3
- metadata.gz: 0d91d9cf7282456c7a61fba4c17609eda22851232c5bfe7be75518c0bb1ea354
4
- data.tar.gz: b465b287f74cd1efaa8788ad80a9a0e4ee78e89d6a8ea061445de526f8b78edf
3
+ metadata.gz: f23772b66ae1d2d65f82803ebc661051084b62bab305781945c3d36a1c9ff1c1
4
+ data.tar.gz: c5cba5902f5c6b2dd0bdf446ec9625dc9baa94682f5375fb3c3b89d1bd76cc91
5
5
  SHA512:
6
- metadata.gz: 695c099ae0fb5899086f50296293610c3f6f1a82a361ed5da953fd1495cc38a538a26048fcdf8b329a6313e7e81abd1e102883523ad12c3b5e360cdcee0686df
7
- data.tar.gz: 7dd2fe93e6cf93b1afca58d09d79ea318d71314759e0b790b524fe58fdee7bd5954d2950ea67cee69b1953b25bc612d54a9a6bcb0b7ef74c0061555f61f68700
6
+ metadata.gz: a9ad30899338103d06b8b2d89070990c81215e3bfe0ab0d649bcbb3290e5981fe7aadf15e6456358786fc4f5f68c8a28aafd44d6e440f42130f1778622ebd0dc
7
+ data.tar.gz: 7107749beac51786c8778a1f2ea8a733caf49d8b01d323d0437eecc5d17e873bc1ad083fbc146133cde250dd8705a25105c5f7522afc0eb2773f50ef996b86f5
data/README.md CHANGED
@@ -18,34 +18,38 @@ This is a pure-Ruby library for working with the [Shape Expressions Language][Sh
18
18
 
19
19
  The ShEx gem implements a [ShEx][ShExSpec] Shape Expression engine version 2.0.
20
20
 
21
- * `ShEx::Parser` parses ShExC and ShExJ formatted documents generating executable operators which can be serialized as [S-Expressions](https://en.wikipedia.org/wiki/S-expression).
21
+ * `ShEx::Parser` parses ShExC and ShExJ formatted documents generating executable operators which can be serialized as [S-Expressions][].
22
22
  * `ShEx::Algebra` executes operators against Any `RDF::Graph`, including compliant [RDF.rb][].
23
23
  * [Implementation Report](file.earl.html)
24
24
 
25
25
  ## Examples
26
26
  ### Validating a node using ShExC
27
27
 
28
- require 'rubygems'
29
28
  require 'rdf/turtle'
30
29
  require 'shex'
31
30
 
32
- shexc: %(
31
+ shexc = %(
33
32
  PREFIX doap: <http://usefulinc.com/ns/doap#>
34
33
  PREFIX dc: <http://purl.org/dc/terms/>
35
- <TestShape> EXTRA a {
36
- a doap:Project;
37
- (doap:name;doap:description|dc:title;dc:description)+;
38
- doap:category*;
39
- doap:developer IRI;
40
- doap:implements [<http://shex.io/shex-semantics/>]
34
+ PREFIX ex: <http://example.com/>
35
+
36
+ ex:TestShape EXTRA a {
37
+ a [doap:Project];
38
+ ( doap:name Literal;
39
+ doap:description Literal
40
+ | dc:title Literal;
41
+ dc:description Literal)+;
42
+ doap:category IRI*;
43
+ doap:developer IRI+;
44
+ doap:implements [<http://shex.io/shex-semantics/>]
41
45
  }
42
46
  )
43
47
  graph = RDF::Graph.load("etc/doap.ttl")
44
48
  schema = ShEx.parse(shexc)
45
49
  map = {
46
- "https://rubygems.org/gems/shex" => "TestShape"
50
+ RDF::URI("https://rubygems.org/gems/shex") => RDF::URI("http://example.com/TestShape")
47
51
  }
48
- schema.satisfies?("https://rubygems.org/gems/shex", graph, map)
52
+ schema.satisfies?(graph, map)
49
53
  # => true
50
54
  ### Validating a node using ShExJ
51
55
 
@@ -53,92 +57,99 @@ The ShEx gem implements a [ShEx][ShExSpec] Shape Expression engine version 2.0.
53
57
  require 'rdf/turtle'
54
58
  require 'shex'
55
59
 
56
- shexj: %({
60
+ shexj = %({
61
+ "@context": "http://www.w3.org/ns/shex.jsonld",
57
62
  "type": "Schema",
58
- "prefixes": {
59
- "doap": "http://usefulinc.com/ns/doap#",
60
- "dc": "http://purl.org/dc/terms/"
61
- },
62
- "shapes": {
63
- "TestShape": {
64
- "type": "Shape",
65
- "extra": ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"],
66
- "expression": {
67
- "type": "EachOf",
68
- "expressions": [
69
- {
70
- "type": "TripleConstraint",
71
- "predicate": "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
72
- "valueExpr": {
73
- "type": "NodeConstraint",
74
- "values": ["http://usefulinc.com/ns/doap#Project"]
75
- }
76
- },
77
- {
78
- "type": "OneOf",
79
- "expressions": [
80
- {
81
- "type": "EachOf",
82
- "expressions": [
83
- {
84
- "type": "TripleConstraint",
85
- "predicate": "http://usefulinc.com/ns/doap#name",
86
- "valueExpr": {"type": "NodeConstraint", "nodeKind": "literal"}
87
- },
88
- {
89
- "type": "TripleConstraint",
90
- "predicate": "http://usefulinc.com/ns/doap#description",
91
- "valueExpr": {"type": "NodeConstraint", "nodeKind": "literal"}
92
- }
93
- ]
94
- },
95
- {
96
- "type": "EachOf",
97
- "expressions": [
98
- {
99
- "type": "TripleConstraint",
100
- "predicate": "http://purl.org/dc/terms/title",
101
- "valueExpr": {"type": "NodeConstraint", "nodeKind": "literal"}
102
- },
103
- {
104
- "type": "TripleConstraint",
105
- "predicate": "http://purl.org/dc/terms/description",
106
- "valueExpr": {"type": "NodeConstraint", "nodeKind": "literal"}
107
- }
108
- ]
63
+ "shapes": [{
64
+ "id": "http://example.com/TestShape",
65
+ "type": "Shape",
66
+ "extra": ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"],
67
+ "expression": {
68
+ "type": "EachOf",
69
+ "expressions": [{
70
+ "type": "TripleConstraint",
71
+ "predicate": "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
72
+ "valueExpr": {
73
+ "type": "NodeConstraint",
74
+ "values": ["http://usefulinc.com/ns/doap#Project"]
75
+ }
76
+ }, {
77
+ "type": "OneOf",
78
+ "expressions": [{
79
+ "type": "EachOf",
80
+ "expressions": [{
81
+ "type": "TripleConstraint",
82
+ "predicate": "http://usefulinc.com/ns/doap#name",
83
+ "valueExpr": {
84
+ "type": "NodeConstraint",
85
+ "nodeKind": "literal"
109
86
  }
110
- ],
111
- "min": 1, "max": -1
112
- },
113
- {
114
- "type": "TripleConstraint",
115
- "predicate": "http://usefulinc.com/ns/doap#category",
116
- "valueExpr": {"type": "NodeConstraint", "nodeKind": "iri"},
117
- "min": 0, "max": -1
87
+ }, {
88
+ "type": "TripleConstraint",
89
+ "predicate": "http://usefulinc.com/ns/doap#description",
90
+ "valueExpr": {
91
+ "type": "NodeConstraint",
92
+ "nodeKind": "literal"
93
+ }
94
+ }]
95
+ }, {
96
+ "type": "EachOf",
97
+ "expressions": [{
98
+ "type": "TripleConstraint",
99
+ "predicate": "http://purl.org/dc/terms/title",
100
+ "valueExpr": {
101
+ "type": "NodeConstraint",
102
+ "nodeKind": "literal"
103
+ }
104
+ }, {
105
+ "type": "TripleConstraint",
106
+ "predicate": "http://purl.org/dc/terms/description",
107
+ "valueExpr": {
108
+ "type": "NodeConstraint",
109
+ "nodeKind": "literal"
110
+ }
111
+ }]
112
+ }],
113
+ "min": 1,
114
+ "max": -1
115
+ }, {
116
+ "type": "TripleConstraint",
117
+ "predicate": "http://usefulinc.com/ns/doap#category",
118
+ "valueExpr": {
119
+ "type": "NodeConstraint",
120
+ "nodeKind": "iri"
118
121
  },
119
- {
120
- "type": "TripleConstraint",
121
- "predicate": "http://usefulinc.com/ns/doap#developer",
122
- "valueExpr": {"type": "NodeConstraint", "nodeKind": "iri"},
123
- "min": 1, "max": -1
122
+ "min": 0,
123
+ "max": -1
124
+ }, {
125
+ "type": "TripleConstraint",
126
+ "predicate": "http://usefulinc.com/ns/doap#developer",
127
+ "valueExpr": {
128
+ "type": "NodeConstraint",
129
+ "nodeKind": "iri"
124
130
  },
125
- {
126
- "type": "TripleConstraint",
127
- "predicate": "http://usefulinc.com/ns/doap#implements",
128
- "valueExpr": {
129
- "type": "NodeConstraint",
130
- "values": ["http://shex.io/shex-semantics/"]
131
- }
131
+ "min": 1,
132
+ "max": -1
133
+ }, {
134
+ "type": "TripleConstraint",
135
+ "predicate": "http://usefulinc.com/ns/doap#implements",
136
+ "valueExpr": {
137
+ "type": "NodeConstraint",
138
+ "values": [
139
+ "http://shex.io/shex-semantics/"
140
+ ]
132
141
  }
133
- ]
134
- }
142
+ }
143
+ ]
135
144
  }
136
145
  }
137
- })
146
+ ]})
138
147
  graph = RDF::Graph.load("etc/doap.ttl")
139
148
  schema = ShEx.parse(shexj, format: :shexj)
140
- map = {"https://rubygems.org/gems/shex" => "TestShape"}
141
- schema.satisfies?("https://rubygems.org/gems/shex", graph, map)
149
+ map = {
150
+ RDF::URI("https://rubygems.org/gems/shex") => RDF::URI("http://example.com/TestShape")
151
+ }
152
+ schema.satisfies?(graph, map)
142
153
  # => true
143
154
 
144
155
  ## Extensions
@@ -179,20 +190,18 @@ Example usage:
179
190
 
180
191
  ## Documentation
181
192
 
182
- <https://rubydoc.info/github/ruby-rdf/shex>
193
+ <https://ruby-rdf.github.io/shex>
183
194
 
184
195
 
185
196
  ## Implementation Notes
186
- The ShExC parser uses the [EBNF][] gem to generate first, follow and branch tables, and uses the `Parser` and `Lexer` modules to implement the ShExC parser.
187
-
188
- The parser takes branch and follow tables generated from the [ShEx Grammar](file.shex.html) described in the [specification][ShExSpec]. Branch and Follow tables are specified in the generated {ShEx::Meta}.
197
+ The ShExC parser uses the [EBNF][] gem to generate a [PEG][] parser.
189
198
 
190
- The result of parsing either ShExC or ShExJ is the creation of a set of executable {ShEx::Algebra} Operators which are directly executed to perform shape validation.
199
+ The parser uses the executable [S-Expressions][] generated from the EBNF ShExC grammar to create a set of executable {ShEx::Algebra} Operators which are directly executed to perform shape validation.
191
200
 
192
201
  ## Dependencies
193
202
 
194
- * [Ruby](https://ruby-lang.org/) (>= 2.4)
195
- * [RDF.rb](https://rubygems.org/gems/rdf) (~> 3.1)
203
+ * [Ruby](https://ruby-lang.org/) (>= 2.6)
204
+ * [RDF.rb](https://rubygems.org/gems/rdf) (~> 3.2)
196
205
  * [SPARQL gem](https://rubygems.org/gems/sparql) (~> 3.1)
197
206
 
198
207
  ## Installation
@@ -215,7 +224,7 @@ follows:
215
224
 
216
225
  ## Resources
217
226
 
218
- * <https://rubydoc.info/github/ruby-rdf/shex>
227
+ * <https://ruby-rdf.github.io/shex>
219
228
  * <https://github.com/ruby-rdf/shex>
220
229
  * <https://rubygems.org/gems/shex>
221
230
 
@@ -255,8 +264,10 @@ see <https://unlicense.org/> or the accompanying {file:LICENSE} file.
255
264
 
256
265
  [ShExSpec]: http://shex.io/shex-semantics-20170713/
257
266
  [RDF]: https://www.w3.org/RDF/
258
- [RDF.rb]: https://rubydoc.info/github/ruby-rdf/rdf
267
+ [RDF.rb]: https://ruby-rdf.github.io/rdf
259
268
  [EBNF]: https://rubygems.org/gems/ebnf
260
269
  [YARD]: https://yardoc.org/
261
270
  [YARD-GS]: https://rubydoc.info/docs/yard/file/docs/GettingStarted.md
262
271
  [PDD]: https://unlicense.org/#unlicensing-contributions
272
+ [PEG]: https://en.wikipedia.org/wiki/Parsing_expression_grammar "Parsing Expression Grammar"
273
+ [S-Expression]: https://en.wikipedia.org/wiki/S-expression
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.6.2
1
+ 0.7.1
data/etc/doap.ttl CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  <https://rubygems.org/gems/shex> a doap:Project, earl:TestSubject, earl:Software ;
12
12
  doap:name "ShEx" ;
13
- doap:homepage <https://ruby-rdf.github.com/shex> ;
13
+ doap:homepage <https://ruby-rdf.github.io/shex> ;
14
14
  doap:license <https://unlicense.org/1.0/> ;
15
15
  doap:shortdesc "ShEx is a Shape Expression engine for Ruby RDF.rb."@en ;
16
16
  doap:description "ShEx is an Shape Expression engine for the Ruby RDF.rb library suite."@en ;
@@ -61,22 +61,13 @@ module ShEx::Algebra
61
61
  end
62
62
 
63
63
  ##
64
- # expressions must be ShapeExpressions
64
+ # expressions must be ShapeExpressions or references to ShapeExpressions
65
65
  #
66
66
  # @return [Operator] `self`
67
67
  # @raise [ShEx::StructureError] if the value is invalid
68
68
  def validate!
69
- expressions.each do |op|
70
- case op
71
- when ShapeExpression
72
- when RDF::Resource
73
- ref = schema.find(op)
74
- ref.is_a?(ShapeExpression) ||
75
- structure_error("#{json_type} must reference a ShapeExpression: #{ref}")
76
- else
77
- structure_error("#{json_type} must reference a ShapeExpression: #{ref}")
78
- end
79
- end
69
+ validate_expressions!
70
+ validate_self_references!
80
71
  super
81
72
  end
82
73
 
@@ -73,22 +73,12 @@ module ShEx::Algebra
73
73
  end
74
74
 
75
75
  ##
76
- # expressions must be TripleExpressions
76
+ # expressions must be TripleExpressions or references to TripleExpressions
77
77
  #
78
78
  # @return [Operator] `self`
79
79
  # @raise [ShEx::StructureError] if the value is invalid
80
80
  def validate!
81
- expressions.each do |op|
82
- case op
83
- when TripleExpression
84
- when RDF::Resource
85
- ref = schema.find(op)
86
- ref.is_a?(TripleExpression) ||
87
- structure_error("#{json_type} must reference a TripleExpression: #{ref}")
88
- else
89
- structure_error("#{json_type} must reference a TripleExpression: #{ref}")
90
- end
91
- end
81
+ validate_expressions!
92
82
  super
93
83
  end
94
84
  end
@@ -0,0 +1,6 @@
1
+ module ShEx::Algebra
2
+ ##
3
+ class Import < Operator::Unary
4
+ NAME = :import
5
+ end
6
+ end
@@ -45,20 +45,13 @@ module ShEx::Algebra
45
45
  end
46
46
 
47
47
  ##
48
- # expression must be a ShapeExpression
48
+ # expressions must be ShapeExpressions or references to ShapeExpressions and must not reference itself recursively.
49
49
  #
50
50
  # @return [Operator] `self`
51
51
  # @raise [ShEx::StructureError] if the value is invalid
52
52
  def validate!
53
- case expression
54
- when ShapeExpression
55
- when RDF::Resource
56
- ref = schema.find(expression)
57
- ref.is_a?(ShapeExpression) ||
58
- structure_error("#{json_type} must reference a ShapeExpression: #{ref}")
59
- else
60
- structure_error("#{json_type} must reference a ShapeExpression: #{ref}")
61
- end
53
+ validate_expressions!
54
+ validate_self_references!
62
55
  super
63
56
  end
64
57
 
@@ -65,22 +65,12 @@ module ShEx::Algebra
65
65
  end
66
66
 
67
67
  ##
68
- # expressions must be TripleExpressions
68
+ # expressions must be TripleExpressions or references to TripleExpressions
69
69
  #
70
70
  # @return [Operator] `self`
71
71
  # @raise [ShEx::StructureError] if the value is invalid
72
72
  def validate!
73
- expressions.each do |op|
74
- case op
75
- when TripleExpression
76
- when RDF::Resource
77
- ref = schema.find(op)
78
- ref.is_a?(TripleExpression) ||
79
- structure_error("#{json_type} must reference a TripleExpression: #{ref}")
80
- else
81
- structure_error("#{json_type} must reference a TripleExpression: #{ref}")
82
- end
83
- end
73
+ validate_expressions!
84
74
  super
85
75
  end
86
76
  end
@@ -35,8 +35,8 @@ module ShEx::Algebra
35
35
  # @option options [RDF::Resource] :id
36
36
  # Identifier of the operator
37
37
  # @raise [TypeError] if any operand is invalid
38
- def initialize(*operands)
39
- @options = operands.last.is_a?(Hash) ? operands.pop.dup : {}
38
+ def initialize(*operands, **options)
39
+ @options = options.dup
40
40
  @operands = operands.map! do |operand|
41
41
  case operand
42
42
  when Array
@@ -221,12 +221,19 @@ module ShEx::Algebra
221
221
  end
222
222
 
223
223
  ##
224
- # The optional TripleExpression for this Shape.
225
- # @return [TripleExpression]
224
+ # The first expression from {#expressions}.
225
+ # @return [RDF::Resource, Operand]
226
226
  def expression
227
227
  expressions.first
228
228
  end
229
229
 
230
+ ##
231
+ # References are all operands which are RDF::Resource
232
+ # @return [RDF::Resource, Operand]
233
+ def references
234
+ @references = operands.select {|op| op.is_a?(RDF::Resource)}
235
+ end
236
+
230
237
  ##
231
238
  # Returns the binary S-Expression (SXP) representation of this operator.
232
239
  #
@@ -242,15 +249,10 @@ module ShEx::Algebra
242
249
  # Returns an S-Expression (SXP) representation of this operator
243
250
  #
244
251
  # @return [String]
245
- def to_sxp
246
- begin
247
- require 'sxp' # @see https://rubygems.org/gems/sxp
248
- rescue LoadError
249
- abort "SPARQL::Algebra::Operator#to_sxp requires the SXP gem (hint: `gem install sxp')."
250
- end
252
+ def to_sxp(**options)
251
253
  require 'sparql/algebra/sxp_extensions'
252
254
 
253
- to_sxp_bin.to_sxp
255
+ to_sxp_bin.to_sxp(**options)
254
256
  end
255
257
 
256
258
  ##
@@ -432,6 +434,8 @@ module ShEx::Algebra
432
434
  case self
433
435
  when And, Or
434
436
  (obj['shapeExprs'] ||= []) << op.to_h
437
+ when Not
438
+ obj['shapeExpr'] = op.to_h
435
439
  else
436
440
  obj['valueExpr'] = op.to_h
437
441
  end
@@ -593,22 +597,23 @@ module ShEx::Algebra
593
597
 
594
598
  ##
595
599
  # Enumerate via depth-first recursive descent over operands, yielding each operator
600
+ # @param [Boolean] include_self
596
601
  # @yield operator
597
602
  # @yieldparam [Object] operator
598
603
  # @return [Enumerator]
599
- def each_descendant(&block)
604
+ def each_descendant(include_self = false, &block)
600
605
  if block_given?
601
606
 
602
- block.call(self)
607
+ block.call(self) if include_self
603
608
 
604
609
  operands.each do |operand|
605
610
  case operand
606
611
  when Array
607
612
  operand.each do |op|
608
- op.each_descendant(&block) if op.respond_to?(:each_descendant)
613
+ op.each_descendant(true, &block) if op.respond_to?(:each_descendant)
609
614
  end
610
615
  else
611
- operand.each_descendant(&block) if operand.respond_to?(:each_descendant)
616
+ operand.each_descendant(true, &block) if operand.respond_to?(:each_descendant)
612
617
  end
613
618
  end
614
619
  end
@@ -631,6 +636,14 @@ module ShEx::Algebra
631
636
  @options[:parent]= operator
632
637
  end
633
638
 
639
+ ##
640
+ # Find a ShapeExpression or TripleExpression by identifier
641
+ # @param [#to_s] id
642
+ # @return [TripleExpression, ShapeExpression]
643
+ def find(id)
644
+ each_descendant(false).detect {|op| op.id == id}
645
+ end
646
+
634
647
  ##
635
648
  # Ancestors of this Operator
636
649
  # @return [Array<Operator>]
@@ -67,22 +67,13 @@ module ShEx::Algebra
67
67
  end
68
68
 
69
69
  ##
70
- # expressions must be ShapeExpressions
70
+ # expressions must be ShapeExpressions or references to ShapeExpressions
71
71
  #
72
72
  # @return [Operator] `self`
73
73
  # @raise [ShEx::StructureError] if the value is invalid
74
74
  def validate!
75
- expressions.each do |op|
76
- case op
77
- when ShapeExpression
78
- when RDF::Resource
79
- ref = schema.find(op)
80
- ref.is_a?(ShapeExpression) ||
81
- structure_error("#{json_type} must reference a ShapeExpression: #{ref}")
82
- else
83
- structure_error("#{json_type} must reference a ShapeExpression: #{ref}")
84
- end
85
- end
75
+ validate_expressions!
76
+ validate_self_references!
86
77
  super
87
78
  end
88
79
 
@@ -25,8 +25,9 @@ module ShEx::Algebra
25
25
  end
26
26
 
27
27
  # (see Operator#initialize)
28
- def initialize(*operands)
28
+ def initialize(*operands, **options)
29
29
  super
30
+ schema = self
30
31
  each_descendant do |op|
31
32
  # Set schema everywhere
32
33
  op.schema = self
@@ -211,14 +212,6 @@ module ShEx::Algebra
211
212
  @start ||= operands.detect {|op| op.is_a?(Start)}
212
213
  end
213
214
 
214
- ##
215
- # Find a ShapeExpression or TripleExpression by identifier
216
- # @param [#to_s] id
217
- # @return [TripleExpression, ShapeExpression]
218
- def find(id)
219
- each_descendant.detect {|op| op.id == id}
220
- end
221
-
222
215
  ##
223
216
  # Validate shapes, in addition to other operands
224
217
  # @return [Operator] `self`
@@ -106,7 +106,7 @@ module ShEx::Algebra
106
106
  end
107
107
 
108
108
  ##
109
- # expression must be a TripleExpression
109
+ # expression must be a TripleExpression and must not reference itself recursively.
110
110
  #
111
111
  # @return [Operator] `self`
112
112
  # @raise [ShEx::StructureError] if the value is invalid
@@ -118,8 +118,10 @@ module ShEx::Algebra
118
118
  ref.is_a?(TripleExpression) ||
119
119
  structure_error("#{json_type} must reference a TripleExpression: #{ref}")
120
120
  else
121
- structure_error("#{json_type} must reference a TripleExpression: #{ref}")
121
+ structure_error("#{json_type} must be a TripleExpression or reference: #{expression.to_sxp}")
122
122
  end
123
+ # FIXME: this runs afoul of otherwise legitamate self-references, through a TripleExpression.
124
+ #!validate_self_references!
123
125
  super
124
126
  end
125
127
 
@@ -15,5 +15,34 @@ module ShEx::Algebra
15
15
  def satisfies?(focus, depth: 0, **options)
16
16
  raise NotImplementedError, "#satisfies? Not implemented in #{self.class}"
17
17
  end
18
+
19
+ ##
20
+ # expressions must be ShapeExpressions or references.
21
+ #
22
+ # @raise [ShEx::StructureError] if the value is invalid
23
+ def validate_expressions!
24
+ expressions.each do |op|
25
+ case op
26
+ when ShapeExpression
27
+ when RDF::Resource
28
+ ref = schema.find(op)
29
+ ref.is_a?(ShapeExpression) ||
30
+ structure_error("#{json_type} must reference a ShapeExpression: #{ref}")
31
+ else
32
+ structure_error("#{json_type} must be a ShapeExpression or reference: #{op.to_sxp}")
33
+ end
34
+ end
35
+ end
36
+
37
+ ##
38
+ # An Operator with a label must contain a reference to itself.
39
+ #
40
+ # @raise [ShEx::StructureError] if the shape is invalid
41
+ def validate_self_references!
42
+ return # FIXME: needs to stop at a TripleConstraint
43
+ each_descendant do |op|
44
+ structure_error("#{json_type} must not reference itself (#{id}): #{op.to_sxp}") if op.references.include?(id)
45
+ end
46
+ end
18
47
  end
19
48
  end
@@ -30,20 +30,12 @@ module ShEx::Algebra
30
30
  end
31
31
 
32
32
  ##
33
- # expression must be a ShapeExpression
33
+ # expressions must be ShapeExpressions or references to ShapeExpressions
34
34
  #
35
35
  # @return [Operator] `self`
36
36
  # @raise [ShEx::StructureError] if the value is invalid
37
37
  def validate!
38
- case expression
39
- when ShapeExpression
40
- when RDF::Resource
41
- ref = schema.find(expression)
42
- ref.is_a?(ShapeExpression) ||
43
- structure_error("#{json_type} must reference a ShapeExpression: #{ref}")
44
- else
45
- structure_error("#{json_type} must reference a ShapeExpression: #{ref}")
46
- end
38
+ validate_expressions!
47
39
  super
48
40
  end
49
41
  end
@@ -66,8 +66,12 @@ module ShEx::Algebra
66
66
  NAME = :languageStem
67
67
 
68
68
  # (see Stem#match?)
69
+ # If the operand is empty, than any language will do,
70
+ # otherwise, it matches the substring up to that first '-', if any.
69
71
  def match?(value, depth: 0)
70
- if value.literal? && value.language.to_s.start_with?(operands.first)
72
+ if value.literal? &&
73
+ value.language? &&
74
+ (operands.first.to_s.empty? || value.language.to_s.match?(%r(^#{operands.first}((-.*)?)$)))
71
75
  status "matched #{value}", depth: depth
72
76
  true
73
77
  else