Class: ShEx::Algebra::Schema

Inherits:
Operator show all
Defined in:
lib/shex/algebra/schema.rb

Constant Summary collapse

NAME =
:schema

Constants inherited from Operator

Operator::ARITY

Instance Attribute Summary collapse

Attributes inherited from Operator

#id, #logger, #operands, #options, #schema

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Operator

#base_uri, #closed?, #each_descendant, #eql?, #expression, #expressions, #focus, #focus=, #inspect, #iri, iri, #json_type, #matched, #matched=, #message, #message=, #not_matched, #not_satisfied, #operand, #parent, #parent=, #satisfied, #satisfied=, #satisfy, #semact?, #semantic_actions, #serialize_value, #status, #structure_error, #to_h, #to_json, #to_sxp, #to_sxp_bin, #triple_expression?, #unmatched, #unmatched=, #unsatisfied, #unsatisfied=, #value, value

Constructor Details

#initialize(*operands) ⇒ Object #initialize(*operands, options) ⇒ Object

Initializes a new operator instance.

Overloads:

  • #initialize(*operands) ⇒ Object

    Parameters:

    • operands (Array<RDF::Term>)
  • #initialize(*operands, options) ⇒ Object

    Parameters:

    • operands (Array<RDF::Term>)
    • options (Hash{Symbol => Object})

      any additional options

    Options Hash (options):

    • :memoize (Boolean) — default: false

      whether to memoize results for particular operands

    • :id (RDF::Resource)

      Identifier of the operator

Raises:

  • (TypeError)

    if any operand is invalid



28
29
30
31
32
33
34
# File 'lib/shex/algebra/schema.rb', line 28

def initialize(*operands)
  super
  each_descendant do |op|
    # Set schema everywhere
    op.schema = self
  end
end

Instance Attribute Details

#extensionsHash{String => ShEx::Extension} (readonly)

Map of Semantic Action instances

Returns:



16
17
18
# File 'lib/shex/algebra/schema.rb', line 16

def extensions
  @extensions
end

#graphRDF::Queryable

Graph to validate

Returns:

  • (RDF::Queryable)


8
9
10
# File 'lib/shex/algebra/schema.rb', line 8

def graph
  @graph
end

#mapHash{RDF::Resource => RDF::Resource} (readonly)

Map of nodes to shapes

Returns:

  • (Hash{RDF::Resource => RDF::Resource})


12
13
14
# File 'lib/shex/algebra/schema.rb', line 12

def map
  @map
end

Class Method Details

.from_shexj(operator, options = {}) ⇒ Operator

Creates an operator instance from a parsed ShExJ representation

Returns:

Raises:

  • (ArgumentError)


22
23
24
25
# File 'lib/shex/algebra/schema.rb', line 22

def self.from_shexj(operator, options = {})
  raise ArgumentError unless operator.is_a?(Hash) && operator['type'] == "Schema"
  super
end

Instance Method Details

#enter_shape(id, node) {|shape,| ... } ⇒ ShapeExpression

Indicate that a shape has been entered with a specific focus node. Any future attempt to enter the same shape with the same node raises an exception.

Parameters:

  • id (RDF::Resource)
  • node (RDF::Resource)

Yields:

  • :shape

Yield Parameters:

Returns:

  • (ShapeExpression)

    with ‘matched` and `satisfied` accessors for matched triples and sub-expressions

Raises:

  • (ShEx::NotMatched)

    with ‘expression` accessor to access `matched` and `unmatched` statements along with `satisfied` and `unsatisfied` operations.



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/shex/algebra/schema.rb', line 176

def enter_shape(id, node, &block)
  shape = shapes.detect {|s| s.id == id}
  structure_error("No shape found for #{id}") unless shape
  @shapes_entered[id] ||= {}
  if @shapes_entered[id][node]
    block.call(false)
  else
    @shapes_entered[id][node] = self
    begin
      block.call(shape)
    ensure
      @shapes_entered[id].delete(node)
    end
  end
end

#execute(graph, map, focus: [], shapeExterns: [], depth: 0, **options) ⇒ Hash{RDF::Term => Array<ShapeResult>}

Match on schema. Finds appropriate shape for node, and matches that shape.

Parameters:

  • graph (RDF::Queryable)
  • map (Hash{RDF::Term => <RDF::Resource>}, Array<Array(RDF::Term, RDF::Resource)>)

    A set of (‘term`, `resource`) pairs where `term` is a node within `graph`, and `resource` identifies a shape

  • *focus (Array<RDF::Term>)

    One or more nodes within ‘graph` for which to run the start expression.

  • shapeExterns (Array<Schema, String>) (defaults to: [])

    ([]) One or more schemas, or paths to ShEx schema resources used for finding external shapes.

  • options (Hash{Symbol => Object})

Options Hash (**options):

  • :base_uri (String) — default: for resolving focus

Returns:

  • (Hash{RDF::Term => Array<ShapeResult>})

    Returns ShapeResults, a hash of graph nodes to the results of their associated shapes

Raises:



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/shex/algebra/schema.rb', line 50

def execute(graph, map, focus: [], shapeExterns: [], depth: 0, **options)
  @graph, @shapes_entered, results = graph, {}, {}
  @external_schemas = shapeExterns
  @extensions = {}
  focus = Array(focus).map {|f| value(f, options)}

  logger = options[:logger] || @options[:logger]
  each_descendant do |op|
    # Set logging everywhere
    op.logger = logger
  end

  # Initialize Extensions
  each_descendant do |op|
    next unless op.is_a?(SemAct)
    name = op.operands.first.to_s
    if ext_class = ShEx::Extension.find(name)
      @extensions[name] ||= ext_class.new(schema: self, depth: depth, **options)
    end
  end

  # 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
  @map = case map
  when Hash
    map.inject({}) do |memo, (node, shapes)|
      gnode = graph.enum_term.detect {|t| t.node? && t.id == node.id} if node.is_a?(RDF::Node)
      node = gnode if gnode
      memo.merge(node => Array(shapes))
    end
  when Array
    map.inject({}) do |memo, (node, shape)|
      gnode = graph.enum_term.detect {|t| t.node? && t.id == node.id} if node.is_a?(RDF::Node)
      node = gnode if gnode
      (memo[node] ||= []).concat(Array(shape))
      memo
    end
  when nil then {}
  else
    structure_error "Unrecognized shape map: #{map.inspect}"
  end

  # First, evaluate semantic acts
  semantic_actions.all? do |op|
    op.satisfies?([], depth: depth + 1)
  end

  # Next run any start expression
  if !focus.empty?
    if start
      focus.each do |node|
        node = graph.enum_term.detect {|t| t.node? && t.id == node.id} if node.is_a?(RDF::Node)
        sr = ShapeResult.new(RDF::URI("http://www.w3.org/ns/shex#Start"))
        (results[node] ||= []) << sr
        begin
          sr.expression = start.satisfies?(node, depth: depth + 1)
          sr.result = true
        rescue ShEx::NotSatisfied => e
          sr.expression = e.expression
          sr.result = false
        end
      end
    else
      structure_error "Focus nodes with no start"
    end
  end

  # Match against all shapes associated with the ids for focus
  @map.each do |node, shapes|
    results[node] ||= []
    shapes.each do |id|
      enter_shape(id, node) do |shape|
        sr = ShapeResult.new(id)
        results[node] << sr
        begin
          sr.expression = shape.satisfies?(node, depth: depth + 1)
          sr.result = true
        rescue ShEx::NotSatisfied => e
          sr.expression = e.expression
          sr.result = false
        end
      end
    end
  end

  if results.values.flatten.all? {|sr| sr.result}
    status "schema satisfied", depth: depth
    results
  else
    raise ShEx::NotSatisfied.new("Graph does not conform to schema", expression: results)
  end
ensure
  # Close Semantic Action extensions
  @extensions.values.each {|ext| ext.close(schema: self, depth: depth, **options)}
end

#external_schemasArray<Schema>

Externally loaded schemas, lazily evaluated

Returns:



195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/shex/algebra/schema.rb', line 195

def external_schemas
  @external_schemas = Array(@external_schemas).map do |extern|
    schema = case extern
    when Schema then extern
    else
      status "Load extern #{extern}"
      ShEx.open(extern, logger: options[:logger])
    end
    schema.graph = graph
    schema
  end
end

#find(id) ⇒ TripleExpression, ShapeExpression

Find a ShapeExpression or TripleExpression by identifier

Parameters:

  • id (#to_s)

Returns:



218
219
220
# File 'lib/shex/algebra/schema.rb', line 218

def find(id)
  each_descendant.detect {|op| op.id == id}
end

#satisfies?(graph, map, **options) ⇒ Boolean

Match on schema. Finds appropriate shape for node, and matches that shape.

Parameters:

  • options (Hash{Symbol => Object})
  • graph (RDF::Queryable)
  • map (Hash{RDF::Term => <RDF::Resource>}, Array<Array(RDF::Term, RDF::Resource)>)

    A set of (‘term`, `resource`) pairs where `term` is a node within `graph`, and `resource` identifies a shape

  • *focus (Array<RDF::Term>)

    One or more nodes within ‘graph` for which to run the start expression.

  • shapeExterns (Array<Schema, String>)

    ([]) One or more schemas, or paths to ShEx schema resources used for finding external shapes.

  • options (Hash{Symbol => Object})

Options Hash (**options):

  • :base_uri (String)

Returns:

  • (Boolean)


152
153
154
155
156
# File 'lib/shex/algebra/schema.rb', line 152

def satisfies?(graph, map, **options)
  execute(graph, map, **options)
rescue ShEx::NotSatisfied
  false
end

#shapesArray<Operator>

Shapes as a hash

Returns:



161
162
163
164
165
166
# File 'lib/shex/algebra/schema.rb', line 161

def shapes
  @shapes ||= begin
    shapes = Array(operands.detect {|op| op.is_a?(Array) && op.first == :shapes})
    Array(shapes[1..-1])
  end
end

#startObject

Start action, if any



210
211
212
# File 'lib/shex/algebra/schema.rb', line 210

def start
  @start ||= operands.detect {|op| op.is_a?(Start)}
end

#validate!Operator

Validate shapes, in addition to other operands

Returns:

Raises:

  • (ArgumentError)

    if the value is invalid



226
227
228
229
230
231
232
233
234
235
# File 'lib/shex/algebra/schema.rb', line 226

def validate!
  shapes.each do |op|
    op.validate! if op.respond_to?(:validate!)
    if op.is_a?(RDF::Resource)
      ref = find(op)
      structure_error("Missing reference: #{op}") if ref.nil?
    end
  end
  super
end