shex 0.6.2 → 0.7.1

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
  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