shex 0.2.0 → 0.3.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: 3ba75ebd8c6632f82452b8cc6a19dbe69e86258d
4
- data.tar.gz: a924845f0d0ffd7caa35926b98829345ee3d427a
3
+ metadata.gz: 5d83ac4fdd795f4d27280e23fc431a42faa72dd7
4
+ data.tar.gz: 61b80075c76f018481226ed563fa7ec44fe0c35f
5
5
  SHA512:
6
- metadata.gz: 1e844351dfd63a5c158a088b6d83375e3f96382d1a56c3e7aaa8e037f4c3ae380bac4f04d1da9db3727053400563b96565d291cb7537974c920df636bf7d3614
7
- data.tar.gz: 3797b22ce44300b723770608ee42a3fd03418f0c8497e4a370fdf960f47ad324c67b07ec061ca498a8fa2d683b97a4fab7c95ced34123840e2c968120e162418
6
+ metadata.gz: 2a158d18cdfbe1c0ae951549bbafc0e8d2d312cc08d259eefe76729e192db380b60a63b0d4a525cc4c49eee8f3092b9fd82cee03fe174cf8ab334c57bb943a4f
7
+ data.tar.gz: 5078624090859f18d37daf162baadcca943533c4bd35eb3e52299c737a18b3c7ab85e1d3e0d4819f3d7a12b9c97e308e580b7d408a43d866ee1264331bb01d73
data/README.md CHANGED
@@ -19,10 +19,12 @@ This is a pure-Ruby library for working with the [Shape Expressions Language][Sh
19
19
 
20
20
  The ShEx gem implements a [ShEx][ShExSpec] Shape Expression engine.
21
21
 
22
- * `ShEx::Parser` parses ShExC formatted documents generating executable operators which can be serialized as [S-Expressions](http://en.wikipedia.org/wiki/S-expression).
22
+ * `ShEx::Parser` parses ShExC and ShExJ formatted documents generating executable operators which can be serialized as [S-Expressions](http://en.wikipedia.org/wiki/S-expression).
23
23
  * `ShEx::Algebra` executes operators against Any `RDF::Graph`, including compliant [RDF.rb][].
24
+ * [Implementation Report](file.earl.html)
24
25
 
25
- ## Example
26
+ ## Examples
27
+ ### Validating a node using ShExC
26
28
 
27
29
  require 'rubygems'
28
30
  require 'rdf/turtle'
@@ -46,6 +48,121 @@ The ShEx gem implements a [ShEx][ShExSpec] Shape Expression engine.
46
48
  }
47
49
  schema.satisfies?("http://rubygems.org/gems/shex", graph, map)
48
50
  # => true
51
+ ### Validating a node using ShExC
52
+
53
+ require 'rubygems'
54
+ require 'rdf/turtle'
55
+ require 'shex'
56
+
57
+ shexj: %({
58
+ "type": "Schema",
59
+ "prefixes": {
60
+ "doap": "http://usefulinc.com/ns/doap#",
61
+ "dc": "http://purl.org/dc/terms/"
62
+ },
63
+ "shapes": {
64
+ "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
+ {
71
+ "type": "TripleConstraint",
72
+ "predicate": "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
73
+ "valueExpr": {
74
+ "type": "NodeConstraint",
75
+ "values": ["http://usefulinc.com/ns/doap#Project"]
76
+ }
77
+ },
78
+ {
79
+ "type": "OneOf",
80
+ "expressions": [
81
+ {
82
+ "type": "EachOf",
83
+ "expressions": [
84
+ {
85
+ "type": "TripleConstraint",
86
+ "predicate": "http://usefulinc.com/ns/doap#name",
87
+ "valueExpr": {"type": "NodeConstraint", "nodeKind": "literal"}
88
+ },
89
+ {
90
+ "type": "TripleConstraint",
91
+ "predicate": "http://usefulinc.com/ns/doap#description",
92
+ "valueExpr": {"type": "NodeConstraint", "nodeKind": "literal"}
93
+ }
94
+ ]
95
+ },
96
+ {
97
+ "type": "EachOf",
98
+ "expressions": [
99
+ {
100
+ "type": "TripleConstraint",
101
+ "predicate": "http://purl.org/dc/terms/title",
102
+ "valueExpr": {"type": "NodeConstraint", "nodeKind": "literal"}
103
+ },
104
+ {
105
+ "type": "TripleConstraint",
106
+ "predicate": "http://purl.org/dc/terms/description",
107
+ "valueExpr": {"type": "NodeConstraint", "nodeKind": "literal"}
108
+ }
109
+ ]
110
+ }
111
+ ],
112
+ "min": 1, "max": "*"
113
+ },
114
+ {
115
+ "type": "TripleConstraint",
116
+ "predicate": "http://usefulinc.com/ns/doap#category",
117
+ "valueExpr": {"type": "NodeConstraint", "nodeKind": "iri"},
118
+ "min": 0, "max": "*"
119
+ },
120
+ {
121
+ "type": "TripleConstraint",
122
+ "predicate": "http://usefulinc.com/ns/doap#developer",
123
+ "valueExpr": {"type": "NodeConstraint", "nodeKind": "iri"},
124
+ "min": 1, "max": "*"
125
+ },
126
+ {
127
+ "type": "TripleConstraint",
128
+ "predicate": "http://usefulinc.com/ns/doap#implements",
129
+ "valueExpr": {
130
+ "type": "NodeConstraint",
131
+ "values": ["https://shexspec.github.io/spec/"]
132
+ }
133
+ }
134
+ ]
135
+ }
136
+ }
137
+ }
138
+ })
139
+ graph = RDF::Graph.load("etc/doap.ttl")
140
+ schema = ShEx.parse(shexj, format: :shexj)
141
+ map = {"http://rubygems.org/gems/shex" => "TestShape"}
142
+ schema.satisfies?("http://rubygems.org/gems/shex", graph, map)
143
+ # => true
144
+
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.
147
+
148
+ require 'shex'
149
+ class ShEx::Test < ShEx::Extension("http://shex.io/extensions/Test/")
150
+ # (see ShEx::Extension#initialize)
151
+ def initialize(schema: nil, logger: nil, depth: 0, **options)
152
+ ...
153
+ end
154
+
155
+ # (see ShEx::Extension#visit)
156
+ def visit(code: nil, matched: nil, expression: nil, depth: 0, **options)
157
+ ...
158
+ end
159
+ end
160
+
161
+ The `#enter` method will be called on any {ShEx::Algebra::TripleExpression} that includes a {ShEx::Algebra::SemAct} referencing the extension, while the `#exit` method will be called on exit, even if not satisfied.
162
+
163
+ The `#initialize` method is called when {ShEx::Algebra::Schema#execute} starts and `#close` called on exit, even if not satisfied.
164
+
165
+ To make sure your extension is found, make sure to require it before the shape is executed.
49
166
 
50
167
  ## Documentation
51
168
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.0
1
+ 0.3.0
@@ -8,7 +8,7 @@
8
8
  @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
9
9
 
10
10
  <http://rubygems.org/gems/shex> a doap:Project, earl:TestSubject, earl:Software ;
11
- doap:name "ShEx" ;
11
+ doap:name "ShEx.rb" ;
12
12
  doap:homepage <http://ruby-rdf.github.com/shex> ;
13
13
  doap:license <http://creativecommons.org/licenses/publicdomain/> ;
14
14
  doap:shortdesc "ShEx is a Shape Expression engine for Ruby."@en ;
@@ -26,7 +26,7 @@
26
26
  doap:maintainer <http://greggkellogg.net/foaf#me> ;
27
27
  doap:documenter <http://greggkellogg.net/foaf#me> ;
28
28
  foaf:maker <http://greggkellogg.net/foaf#me> ;
29
- dc:title "ShEx" ;
29
+ dc:title "ShEx.rb" ;
30
30
  dc:description "ShEx is an Shape Expression engine for the RDF.rb library suite."@en ;
31
31
  dc:date "2016-12-09"^^xsd:date ;
32
32
  dc:creator <http://greggkellogg.net/foaf#me> ;
@@ -6,9 +6,16 @@ module ShEx
6
6
  autoload :Algebra, 'shex/algebra'
7
7
  autoload :Meta, 'shex/meta'
8
8
  autoload :Parser, 'shex/parser'
9
+ autoload :Extension, 'shex/extensions/extension'
9
10
  autoload :Terminals, 'shex/terminals'
10
11
  autoload :VERSION, 'shex/version'
11
12
 
13
+ # Location of the ShEx JSON-LD context
14
+ CONTEXT = "https://shexspec.github.io/context.jsonld"
15
+
16
+ # Extensions defined in this gem
17
+ EXTENSIONS = %w{test}
18
+
12
19
  ##
13
20
  # Parse the given ShEx `query` string.
14
21
  #
@@ -24,10 +31,14 @@ module ShEx
24
31
  # @return (see ShEx::Parser#parse)
25
32
  # @raise (see ShEx::Parser#parse)
26
33
  def self.parse(expression, format: 'shexc', **options)
27
- case format
34
+ case format.to_s
28
35
  when 'shexc' then Parser.new(expression, options).parse
29
36
  when 'shexj'
37
+ expression = expression.read if expression.respond_to?(:read)
38
+ Algebra.from_shexj(JSON.parse expression)
30
39
  when 'sxp'
40
+ expression = expression.read if expression.respond_to?(:read)
41
+ Algebra.from_sxp(JSON.parse expression)
31
42
  else raise "Unknown expression format: #{format.inspect}"
32
43
  end
33
44
  end
@@ -85,6 +96,15 @@ module ShEx
85
96
  shex.satisfies?(focus, queryable, {focus => shape}, options)
86
97
  end
87
98
 
99
+ ##
100
+ # Alias for `ShEx::Extension.create`.
101
+ #
102
+ # @param (see ShEx::Extension#create)
103
+ # @return [Class]
104
+ def self.Extension(uri)
105
+ Extension.send(:create, uri)
106
+ end
107
+
88
108
  class Error < StandardError
89
109
  # The status code associated with this error
90
110
  attr_reader :code
@@ -167,7 +187,6 @@ module ShEx
167
187
  # ParseError includes `token` and `lineno` associated with the expression.
168
188
  #
169
189
  # @param [String, #to_s] message
170
- # @param [Hash{Symbol => Object}] options
171
190
  # @param [String] token (nil)
172
191
  # @param [Integer] lineno (nil)
173
192
  def initialize(message, token: nil, lineno: nil)
@@ -9,7 +9,6 @@ module ShEx
9
9
  module Algebra
10
10
  autoload :And, 'shex/algebra/and'
11
11
  autoload :Annotation, 'shex/algebra/annotation'
12
- autoload :Base, 'shex/algebra/base'
13
12
  autoload :EachOf, 'shex/algebra/each_of'
14
13
  autoload :Inclusion, 'shex/algebra/inclusion'
15
14
  autoload :Not, 'shex/algebra/not'
@@ -17,7 +16,6 @@ module ShEx
17
16
  autoload :OneOf, 'shex/algebra/one_of'
18
17
  autoload :Operator, 'shex/algebra/operator'
19
18
  autoload :Or, 'shex/algebra/or'
20
- autoload :Prefix, 'shex/algebra/prefix'
21
19
  autoload :Satisfiable, 'shex/algebra/satisfiable'
22
20
  autoload :Schema, 'shex/algebra/schema'
23
21
  autoload :SemAct, 'shex/algebra/semact'
@@ -29,8 +27,48 @@ module ShEx
29
27
  autoload :StemRange, 'shex/algebra/stem_range'
30
28
  autoload :TripleConstraint, 'shex/algebra/triple_constraint'
31
29
  autoload :TripleExpression, 'shex/algebra/triple_expression'
32
- autoload :UnaryShape, 'shex/algebra/unary_shape'
33
30
  autoload :Value, 'shex/algebra/value'
31
+
32
+
33
+ ##
34
+ # Creates an operator instance from a parsed ShExJ representation
35
+ #
36
+ # @example Simple TripleConstraint
37
+ # rep = JSON.parse(%({
38
+ # "type": "TripleConstraint",
39
+ # "predicate": "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
40
+ # }
41
+ # ))
42
+ # TripleConstraint.from(rep) #=> (tripleConstraint a)
43
+ # @param [Hash] operator
44
+ # @param [Hash] options ({})
45
+ # @option options [RDF::URI] :base
46
+ # @option options [Hash{String => RDF::URI}] :prefixes
47
+ # @return [Operator]
48
+ def self.from_shexj(operator, options = {})
49
+ raise ArgumentError unless operator.is_a?(Hash)
50
+ klass = case operator['type']
51
+ when 'Annotation' then Annotation
52
+ when 'EachOf' then EachOf
53
+ when 'Inclusion' then Inclusion
54
+ when 'NodeConstraint' then NodeConstraint
55
+ when 'OneOf' then OneOf
56
+ when 'Schema' then Schema
57
+ when 'SemAct' then SemAct
58
+ when 'Shape' then Shape
59
+ when 'ShapeAnd' then And
60
+ when 'ShapeNot' then Not
61
+ when 'ShapeOr' then Or
62
+ when 'ShapeRef' then ShapeRef
63
+ when 'Stem' then Stem
64
+ when 'StemRange' then StemRange
65
+ when 'TripleConstraint' then TripleConstraint
66
+ when 'Wildcard' then StemRange
67
+ else raise ArgumentError, "unknown type #{operator['type']}"
68
+ end
69
+
70
+ klass.from_shexj(operator, options)
71
+ end
34
72
  end
35
73
  end
36
74
 
@@ -6,9 +6,22 @@ module ShEx::Algebra
6
6
 
7
7
  def initialize(*args, **options)
8
8
  case
9
- when args.length <= 1
10
- raise ArgumentError, "wrong number of arguments (given #{args.length}, expected 1..)"
9
+ when args.length < 2
10
+ raise ArgumentError, "wrong number of arguments (given #{args.length}, expected 2..)"
11
11
  end
12
+
13
+ # All arguments must be Satisfiable
14
+ raise ArgumentError, "All operands must be Shape operands" unless args.all? {|o| o.is_a?(Satisfiable)}
15
+ super
16
+ end
17
+
18
+ ##
19
+ # Creates an operator instance from a parsed ShExJ representation
20
+ # @param (see Operator#from_shexj)
21
+ # @return [Operator]
22
+ def self.from_shexj(operator, options = {})
23
+ raise ArgumentError unless operator.is_a?(Hash) && operator['type'] == 'ShapeAnd'
24
+ raise ArgumentError, "missing shapeExprs in #{operator.inspect}" unless operator.has_key?('shapeExprs')
12
25
  super
13
26
  end
14
27
 
@@ -17,20 +30,28 @@ module ShEx::Algebra
17
30
  # @param (see Satisfiable#satisfies?)
18
31
  # @return (see Satisfiable#satisfies?)
19
32
  # @raise (see Satisfiable#satisfies?)
20
- def satisfies?(focus)
33
+ def satisfies?(focus, depth: 0)
21
34
  status ""
22
35
  expressions = operands.select {|o| o.is_a?(Satisfiable)}
23
36
  satisfied = []
37
+ unsatisfied = expressions.dup
24
38
 
25
39
  # Operand raises NotSatisfied, so no need to check here.
26
40
  expressions.each do |op|
27
- satisfied << op.satisfies?(focus)
41
+ satisfied << op.satisfies?(focus, depth: depth)
42
+ unsatisfied.shift
28
43
  end
29
- satisfy satisfied: satisfied
44
+ satisfy focus: focus, satisfied: satisfied, depth: depth
30
45
  rescue ShEx::NotSatisfied => e
31
46
  not_satisfied e.message,
47
+ focus: focus,
32
48
  satisfied: satisfied,
33
- unsatisfied: (expressions - satisfied)
49
+ unsatisfied: unsatisfied,
50
+ depth: depth
51
+ end
52
+
53
+ def json_type
54
+ "ShapeAnd"
34
55
  end
35
56
  end
36
57
  end
@@ -2,5 +2,24 @@ module ShEx::Algebra
2
2
  ##
3
3
  class Annotation < Operator
4
4
  NAME = :annotation
5
+
6
+ ##
7
+ # Creates an operator instance from a parsed ShExJ representation
8
+ # @param (see Operator#from_shexj)
9
+ # @return [Operator]
10
+ def self.from_shexj(operator, options = {})
11
+ raise ArgumentError unless operator.is_a?(Hash) && operator['type'] == "Annotation"
12
+ raise ArgumentError, "missing predicate in #{operator.inspect}" unless operator.has_key?('predicate')
13
+ raise ArgumentError, "missing object in #{operator.inspect}" unless operator.has_key?('object')
14
+ super
15
+ end
16
+
17
+ def to_h
18
+ {
19
+ 'type' => json_type,
20
+ 'predicate' => operands.first.to_s,
21
+ 'object' => serialize_value(operands.last)
22
+ }
23
+ end
5
24
  end
6
25
  end
@@ -4,39 +4,51 @@ module ShEx::Algebra
4
4
  include TripleExpression
5
5
  NAME = :eachOf
6
6
 
7
+ ##
8
+ # Creates an operator instance from a parsed ShExJ representation
9
+ # @param (see Operator#from_shexj)
10
+ # @return [Operator]
11
+ def self.from_shexj(operator, options = {})
12
+ raise ArgumentError unless operator.is_a?(Hash) && operator['type'] == 'EachOf'
13
+ raise ArgumentError, "missing expressions in #{operator.inspect}" unless operator.has_key?('expressions')
14
+ super
15
+ end
16
+
7
17
  ##
8
18
  # expr is an EachOf and there is some partition of T into T1, T2,… such that for every expression expr1, expr2,… in shapeExprs, matches(Tn, exprn, m)...
9
19
  #
10
- # @param [Array<RDF::Statement>] statements
11
- # @return [Array<RDF::Statement>]
12
- # @raise [ShEx::NotMatched]
13
- def matches(statements)
14
- status ""
20
+ # @param (see TripleExpression#matches)
21
+ # @return (see TripleExpression#matches)
22
+ # @raise (see TripleExpression#matches)
23
+ def matches(arcs_in, arcs_out, depth: 0)
24
+ status "", depth: depth
15
25
  results, satisfied, unsatisfied = [], [], []
16
26
  num_iters, max = 0, maximum
17
27
 
28
+ # enter semantic acts
29
+ semantic_actions.each {|op| op.enter(arcs_in: arcs_in, arcs_out: arcs_out, depth: depth + 1)}
30
+
18
31
  while num_iters < max
19
32
  begin
20
33
  matched_this_iter = []
21
34
  operands.select {|o| o.is_a?(TripleExpression)}.all? do |op|
22
35
  begin
23
- matched_op = op.matches(statements - matched_this_iter)
36
+ matched_op = op.matches(arcs_in - matched_this_iter, arcs_out - matched_this_iter, depth: depth + 1)
24
37
  satisfied << matched_op
25
38
  matched_this_iter += matched_op.matched
26
39
  rescue ShEx::NotMatched => e
27
- status "not matched: #{e.message}"
28
- op = op.dup
29
- op.unmatched = statements - matched_this_iter
30
- unsatisfied << op
40
+ status "not matched: #{e.message}", depth: depth
41
+ unsatisfied << e.expression
31
42
  raise
32
43
  end
33
44
  end
34
45
  results += matched_this_iter
35
- statements -= matched_this_iter
46
+ arcs_in -= matched_this_iter
47
+ arcs_out -= matched_this_iter
36
48
  num_iters += 1
37
- status "matched #{results.length} statements after #{num_iters} iterations"
49
+ status "matched #{results.length} statements after #{num_iters} iterations", depth: depth
38
50
  rescue ShEx::NotMatched => e
39
- status "no match after #{num_iters} iterations (ignored)"
51
+ status "no match after #{num_iters} iterations (ignored)", depth: depth
40
52
  break
41
53
  end
42
54
  end
@@ -47,15 +59,16 @@ module ShEx::Algebra
47
59
  end
48
60
 
49
61
  # Last, evaluate semantic acts
50
- semantic_actions.all? do |op|
51
- op.satisfies?(results)
52
- end unless results.empty?
62
+ semantic_actions.each {|op| op.satisfies?(nil, matched: results, depth: depth + 1)}
53
63
 
54
- satisfy matched: results, satisfied: satisfied
64
+ satisfy matched: results, satisfied: satisfied, depth: depth
55
65
  rescue ShEx::NotMatched, ShEx::NotSatisfied => e
56
66
  not_matched e.message,
57
- matched: results, unmatched: (statements - results),
58
- satisfied: satisfied, unsatisfied: unsatisfied
67
+ matched: results, unmatched: ((arcs_in + arcs_out).uniq - results),
68
+ satisfied: satisfied, unsatisfied: unsatisfied,
69
+ depth: depth
70
+ ensure
71
+ semantic_actions.each {|op| op.exit(matched: matched, depth: depth + 1)}
59
72
  end
60
73
  end
61
74
  end