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?, #dup, #each_descendant, #eql?, #expression, #expressions, #find, #focus, #focus=, #inspect, #iri, iri, #json_type, #matched, #matched=, #message, #message=, #not_matched, #not_satisfied, #operand, #parent, #parent=, #references, #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
35
# File 'lib/shex/algebra/schema.rb', line 28

def initialize(*operands, **options)
  super
  schema = self
  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.



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

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>) (defaults to: [])

    ([]) 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:



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
144
# File 'lib/shex/algebra/schema.rb', line 51

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:



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

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

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


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

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

#shapesArray<Operator>

Shapes as a hash

Returns:



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

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



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

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



219
220
221
222
223
224
225
226
227
228
# File 'lib/shex/algebra/schema.rb', line 219

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