Class: SHACL::Algebra::Shape

Inherits:
Operator
  • Object
show all
Defined in:
lib/shacl/algebra/shape.rb

Direct Known Subclasses

NodeShape, PropertyShape

Constant Summary collapse

NAME =
:Shape
NODE_KIND_COMPARE =

The matrix of comparisons of different types of nodes

Returns:

  • (Hash{Class => RDF::URI})
{
  RDF::URI => [
    RDF::Vocab::SHACL.IRI,
    RDF::Vocab::SHACL.BlankNodeOrIRI,
    RDF::Vocab::SHACL.IRIOrLiteral,
  ],
  RDF::Node => [
    RDF::Vocab::SHACL.BlankNode,
    RDF::Vocab::SHACL.BlankNodeOrIRI,
    RDF::Vocab::SHACL.BlankNodeOrLiteral,
  ],
  RDF::Literal => [
    RDF::Vocab::SHACL.Literal,
    RDF::Vocab::SHACL.IRIOrLiteral,
    RDF::Vocab::SHACL.BlankNodeOrLiteral,
  ]
}

Constants inherited from Operator

Operator::BUILTIN_KEYS, Operator::PARAMETERS

Instance Attribute Summary

Attributes inherited from Operator

#graph, #options, #shapes_graph

Instance Method Summary collapse

Methods inherited from Operator

add_component, apply_op, #comment, component_params, #conforms, #deactivated?, from_expanded_value, from_json, #id, iri, #iri, #label, #not_satisfied, params, parse_path, #satisfy, to_rdf, #to_sxp_bin, #type

Instance Method Details

#builtin_class(types, node, path, value_nodes, **options) ⇒ Array<SHACL::ValidationResult>

Specifies that each value node is a SHACL instance of a given type.

Examples:

ex:ClassExampleShape
	a sh:NodeShape ;
	sh:targetNode ex:Bob, ex:Alice, ex:Carol ;
	sh:property [
		sh:path ex:address ;
		sh:class ex:PostalAddress ;
	] .

Parameters:

  • types (Array<RDF::URI>)

    The type expected for each value node.

  • node (RDF::Term)

    the focus node

  • path (RDF::URI, SPARQL::Algebra::Expression)

    (nil) the property path from the focus node to the value nodes.

  • value_nodes (Array<RDF::Term>)

Returns:



52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/shacl/algebra/shape.rb', line 52

def builtin_class(types, node, path, value_nodes, **options)
  value_nodes.map do |n|
    has_type = n.resource? && begin
      objects = graph.query({subject: n, predicate: RDF.type}).objects
      types.all? {|t| objects.include?(t)}
    end
    satisfy(focus: node, path: path,
      value: n,
      message: "is#{' not' unless has_type} of class #{type.to_sxp}",
      resultSeverity: (options.fetch(:severity) unless has_type),
      component: RDF::Vocab::SHACL.ClassConstraintComponent,
      **options)
  end.flatten.compact
end

#builtin_datatype(datatype, node, path, value_nodes, **options) ⇒ Array<SHACL::ValidationResult>

Specifies a condition to be satisfied with regards to the datatype of each value node.

Examples:

ex:DatatypeExampleShape
	a sh:NodeShape ;
	sh:targetNode ex:Alice, ex:Bob, ex:Carol ;
	sh:property [
		sh:path ex:age ;
		sh:datatype xsd:integer ;
	] .

Parameters:

  • datatype (RDF::URI)

    the expected datatype of each value node.

  • node (RDF::Term)

    the focus node

  • path (RDF::URI, SPARQL::Algebra::Expression)

    (nil) the property path from the focus node to the value nodes.

Returns:



83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/shacl/algebra/shape.rb', line 83

def builtin_datatype(datatype, node, path, value_nodes, **options)
  datatype = datatype.first if datatype.is_a?(Array)
  value_nodes.map do |n|
    has_datatype = n.literal? && n.datatype == datatype && n.valid?
    satisfy(focus: node, path: path,
      value: n,
      message: "is#{' not' unless has_datatype} a valid literal with datatype #{datatype.to_sxp}",
      resultSeverity: (options.fetch(:severity) unless has_datatype),
      component: RDF::Vocab::SHACL.DatatypeConstraintComponent,
      **options)
  end.flatten.compact
end

#builtin_disjoint(properties, node, path, value_nodes, **options) ⇒ Array<SHACL::ValidationResult>

Specifies the condition that the set of value nodes is disjoint with the set of objects of the triples that have the focus node as subject and the value of sh:disjoint as predicate.

Examples:

ex:DisjointExampleShape
	a sh:NodeShape ;
	sh:targetNode ex:USA, ex:Germany ;
	sh:property [
		sh:path ex:prefLabel ;
		sh:disjoint ex:altLabel ;
	] .

Parameters:

  • properties (Array<RDF::URI>)

    the properties of the focus node whose values must be disjoint with the value nodes.

  • node (RDF::Term)

    the focus node

  • path (RDF::URI, SPARQL::Algebra::Expression)

    (nil) the property path from the focus node to the value nodes.

  • value_nodes (Array<RDF::Term>)

Returns:



112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/shacl/algebra/shape.rb', line 112

def builtin_disjoint(properties, node, path, value_nodes, **options)
  disjoint_nodes = properties.map do |prop|
    graph.query({subject: node, predicate: prop}).objects
  end.flatten.compact
  value_nodes.map do |n|
    has_value = disjoint_nodes.include?(n)
    satisfy(focus: node, path: path,
      value: n,
      message: "is#{' not' unless has_value} disjoint with #{disjoint_nodes.to_sxp}",
      resultSeverity: (options.fetch(:severity) if has_value),
      component: RDF::Vocab::SHACL.DisjointConstraintComponent,
      **options)
  end.flatten.compact
end

#builtin_equals(property, node, path, value_nodes, **options) ⇒ Array<SHACL::ValidationResult>

Specifies the condition that the set of all value nodes is equal to the set of objects of the triples that have the focus node as subject and the value of sh:equals as predicate.

Examples:

ex:EqualExampleShape
	a sh:NodeShape ;
	sh:targetNode ex:Bob ;
	sh:property [
		sh:path ex:firstName ;
		sh:equals ex:givenName ;
	] .

Parameters:

  • property (RDF::URI)

    the property of the focus node whose values must be equal to some value node.

  • node (RDF::Term)

    the focus node

  • path (RDF::URI, SPARQL::Algebra::Expression)

    (nil) the property path from the focus node to the value nodes.

  • value_nodes (Array<RDF::Term>)

Returns:



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/shacl/algebra/shape.rb', line 143

def builtin_equals(property, node, path, value_nodes, **options)
  property = property.first if property.is_a?(Array)
  equal_nodes = graph.query({subject: node, predicate: property}).objects
  (value_nodes.map do |n|
    has_value = equal_nodes.include?(n)
    satisfy(focus: node, path: path,
      value: n,
      message: "is#{' not' unless has_value} a value in #{equal_nodes.to_sxp}",
      resultSeverity: (options.fetch(:severity) unless has_value),
      component: RDF::Vocab::SHACL.EqualsConstraintComponent,
      **options)
  end +
  equal_nodes.map do |n|
    !value_nodes.include?(n) ?
      not_satisfied(focus: node, path: path,
        value: n,
        message: "should have a value in #{value_nodes.to_sxp}",
        resultSeverity: options.fetch(:severity),
        component: RDF::Vocab::SHACL.EqualsConstraintComponent,
        **options) :
      nil
  end).flatten.compact
end

#builtin_hasValue(term, node, path, value_nodes, **options) ⇒ Array<SHACL::ValidationResult>

Specifies the condition that at least one value node is equal to the given RDF term.

Examples:

ex:StanfordGraduate
	a sh:NodeShape ;
	sh:targetNode ex:Alice ;
	sh:property [
		sh:path ex:alumniOf ;
		sh:hasValue ex:Stanford ;
	] .

Parameters:

  • term (RDF::URI)

    the term that must be a value of a value node.

  • node (RDF::Term)

    the focus node

  • path (RDF::URI, SPARQL::Algebra::Expression)

    (nil) the property path from the focus node to the value nodes.

  • value_nodes (Array<RDF::Term>)

Returns:



183
184
185
186
187
188
189
190
191
# File 'lib/shacl/algebra/shape.rb', line 183

def builtin_hasValue(term, node, path, value_nodes, **options)
  term = term.first if term.is_a?(Array)
  has_value = value_nodes.include?(term)
  [satisfy(focus: node, path: path,
    message: "is#{' not' unless has_value} the value #{term.to_sxp}",
    resultSeverity: (options.fetch(:severity) unless has_value),
    component: RDF::Vocab::SHACL.HasValueConstraintComponent,
    **options)]
end

#builtin_in(list, node, path, value_nodes, **options) ⇒ Array<SHACL::ValidationResult>

Specifies the condition that each value node is a member of a provided SHACL list.

Examples:

ex:InExampleShape
	a sh:NodeShape ;
	sh:targetNode ex:RainbowPony ;
	sh:property [
		sh:path ex:color ;
		sh:in ( ex:Pink ex:Purple ) ;
	] .

Parameters:

  • list (RDF::URI)

    the list which must contain the value nodes..

  • node (RDF::Term)

    the focus node

  • path (RDF::URI, SPARQL::Algebra::Expression)

    (nil) the property path from the focus node to the value nodes.

  • value_nodes (Array<RDF::Term>)

Returns:



209
210
211
212
213
214
215
216
217
218
219
# File 'lib/shacl/algebra/shape.rb', line 209

def builtin_in(list, node, path, value_nodes, **options)
  value_nodes.map do |n|
    has_value = list.include?(n)
    satisfy(focus: node, path: path,
      value: n,
      message: "is#{' not' unless has_value} a value in #{list.to_sxp}",
      resultSeverity: (options.fetch(:severity) unless has_value),
      component: RDF::Vocab::SHACL.InConstraintComponent,
      **options)
  end.flatten.compact
end

#builtin_languageIn(datatypes, node, path, value_nodes, **options) ⇒ Array<SHACL::ValidationResult>

The condition specified by sh:languageIn is that the allowed language tags for each value node are limited by a given list of language tags.

Examples:

ex:NewZealandLanguagesShape
	a sh:NodeShape ;
	sh:targetNode ex:Mountain, ex:Berg ;
	sh:property [
		sh:path ex:prefLabel ;
		sh:languageIn ( "en" "mi" ) ;
	] .

Parameters:

  • datatypes (Array<RDF::URI>)

    the expected datatype of each value node.

  • node (RDF::Term)

    the focus node

  • path (RDF::URI, SPARQL::Algebra::Expression)

    (nil) the property path from the focus node to the value nodes.

Returns:



236
237
238
239
240
241
242
243
244
245
246
# File 'lib/shacl/algebra/shape.rb', line 236

def builtin_languageIn(datatypes, node, path, value_nodes, **options)
  value_nodes.map do |n|
    has_language = n.literal? && datatypes.any? {|l| n.language.to_s.start_with?(l)}
    satisfy(focus: node, path: path,
      value: n,
      message: "is#{' not' unless has_language} a literal with a language in #{datatypes.to_sxp}",
      resultSeverity: (options.fetch(:severity) unless has_language),
      component: RDF::Vocab::SHACL.LanguageInConstraintComponent,
      **options)
  end.flatten.compact
end

#builtin_maxExclusive(term, node, path, value_nodes, **options) ⇒ Array<SHACL::ValidationResult>

Compares value nodes to be < than the specified value.

Examples:

ex:NumericRangeExampleShape
	a sh:NodeShape ;
	sh:targetNode ex:Bob, ex:Alice, ex:Ted ;
	sh:property [
		sh:path ex:age ;
		sh:minInclusive 0 ;
		sh:maxInclusive 150 ;
	] .

Parameters:

  • term (RDF::URI)

    the term is used to compare each value node.

  • node (RDF::Term)

    the focus node

  • path (RDF::URI, SPARQL::Algebra::Expression)

    (nil) the property path from the focus node to the value nodes.

  • value_nodes (Array<RDF::Term>)

Returns:



265
266
267
268
# File 'lib/shacl/algebra/shape.rb', line 265

def builtin_maxExclusive(term, node, path, value_nodes, **options)
  compare(:<, term, node, path, value_nodes,
          RDF::Vocab::SHACL.MaxExclusiveConstraintComponent, **options)
end

#builtin_maxInclusive(term, node, path, value_nodes, **options) ⇒ Array<SHACL::ValidationResult>

Compares value nodes to be <= than the specified value.

Examples:

ex:NumericRangeExampleShape
	a sh:NodeShape ;
	sh:targetNode ex:Bob, ex:Alice, ex:Ted ;
	sh:property [
		sh:path ex:age ;
		sh:minInclusive 0 ;
		sh:maxInclusive 150 ;
	] .

Parameters:

  • term (RDF::URI)

    the term is used to compare each value node.

  • node (RDF::Term)

    the focus node

  • path (RDF::URI, SPARQL::Algebra::Expression)

    (nil) the property path from the focus node to the value nodes.

  • value_nodes (Array<RDF::Term>)

Returns:



287
288
289
290
# File 'lib/shacl/algebra/shape.rb', line 287

def builtin_maxInclusive(term, node, path, value_nodes, **options)
  compare(:<=, term, node, path, value_nodes,
          RDF::Vocab::SHACL.MaxInclusiveConstraintComponent, **options)
end

#builtin_maxLength(term, node, path, value_nodes, **options) ⇒ Array<SHACL::ValidationResult>

Specifies the maximum string length of each value node that satisfies the condition. This can be applied to any literals and IRIs, but not to blank nodes.

Parameters:

  • term (RDF::URI)

    the term is used to compare each value node.

  • node (RDF::Term)

    the focus node

  • path (RDF::URI, SPARQL::Algebra::Expression)

    (nil) the property path from the focus node to the value nodes.

  • value_nodes (Array<RDF::Term>)

Returns:



299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/shacl/algebra/shape.rb', line 299

def builtin_maxLength(term, node, path, value_nodes, **options)
  term = term.first if term.is_a?(Array)
  value_nodes.map do |n|
    compares = !n.node? && n.to_s.length <= term.to_i
    satisfy(focus: node, path: path,
      value: n,
      message: "is#{' not' unless compares} a literal at with length <= #{term.to_sxp}",
      resultSeverity: (options.fetch(:severity) unless compares),
      component: RDF::Vocab::SHACL.MaxLengthConstraintComponent,
      **options)
  end.flatten.compact
end

#builtin_minExclusive(term, node, path, value_nodes, **options) ⇒ Array<SHACL::ValidationResult>

Compares value nodes to be > than the specified value.

Examples:

ex:NumericRangeExampleShape
	a sh:NodeShape ;
	sh:targetNode ex:Bob, ex:Alice, ex:Ted ;
	sh:property [
		sh:path ex:age ;
		sh:minInclusive 0 ;
		sh:maxInclusive 150 ;
	] .

Parameters:

  • term (RDF::URI)

    the term is used to compare each value node.

  • node (RDF::Term)

    the focus node

  • path (RDF::URI, SPARQL::Algebra::Expression)

    (nil) the property path from the focus node to the value nodes.

  • value_nodes (Array<RDF::Term>)

Returns:



329
330
331
332
# File 'lib/shacl/algebra/shape.rb', line 329

def builtin_minExclusive(term, node, path, value_nodes, **options)
  compare(:>, term, node, path, value_nodes,
          RDF::Vocab::SHACL.MinExclusiveConstraintComponent, **options)
end

#builtin_minInclusive(term, node, path, value_nodes, **options) ⇒ Array<SHACL::ValidationResult>

Compares value nodes to be >= than the specified value.

Examples:

ex:NumericRangeExampleShape
	a sh:NodeShape ;
	sh:targetNode ex:Bob, ex:Alice, ex:Ted ;
	sh:property [
		sh:path ex:age ;
		sh:minInclusive 0 ;
		sh:maxInclusive 150 ;
	] .

Parameters:

  • term (RDF::URI)

    the term is used to compare each value node.

  • node (RDF::Term)

    the focus node

  • path (RDF::URI, SPARQL::Algebra::Expression)

    (nil) the property path from the focus node to the value nodes.

  • value_nodes (Array<RDF::Term>)

Returns:



351
352
353
354
# File 'lib/shacl/algebra/shape.rb', line 351

def builtin_minInclusive(term, node, path, value_nodes, **options)
  compare(:>=, term, node, path, value_nodes,
          RDF::Vocab::SHACL.MinInclusiveConstraintComponent, **options)
end

#builtin_minLength(term, node, path, value_nodes, **options) ⇒ Array<SHACL::ValidationResult>

Specifies the minimum string length of each value node that satisfies the condition. This can be applied to any literals and IRIs, but not to blank nodes.

Parameters:

  • term (RDF::URI)

    the term is used to compare each value node.

  • node (RDF::Term)

    the focus node

  • path (RDF::URI, SPARQL::Algebra::Expression)

    (nil) the property path from the focus node to the value nodes.

  • value_nodes (Array<RDF::Term>)

Returns:



363
364
365
366
367
368
369
370
371
372
373
374
# File 'lib/shacl/algebra/shape.rb', line 363

def builtin_minLength(term, node, path, value_nodes, **options)
  term = term.first if term.is_a?(Array)
  value_nodes.map do |n|
    compares = !n.node? && n.to_s.length >= term.to_i
    satisfy(focus: node, path: path,
      value: n,
      message: "is#{' not' unless compares} a literal with length >= #{term.to_sxp}",
      resultSeverity: (options.fetch(:severity) unless compares),
      component: RDF::Vocab::SHACL.MinLengthConstraintComponent,
      **options)
  end.flatten.compact
end

#builtin_nodeKind(term, node, path, value_nodes, **options) ⇒ Array<SHACL::ValidationResult>

Specifies a condition to be satisfied by the RDF node kind of each value node.

Examples:

ex:NodeKindExampleShape
	a sh:NodeShape ;
	sh:targetObjectsOf ex:knows ;
	sh:nodeKind sh:IRI .

Parameters:

  • term (RDF::URI)

    the kind of node to match each value node.

  • node (RDF::Term)

    the focus node

  • path (RDF::URI, SPARQL::Algebra::Expression)

    (nil) the property path from the focus node to the to the value nodes.

  • value_nodes (Array<RDF::Term>)

Returns:



409
410
411
412
413
414
415
416
417
418
419
420
# File 'lib/shacl/algebra/shape.rb', line 409

def builtin_nodeKind(term, node, path, value_nodes, **options)
  term = term.first if term.is_a?(Array)
  value_nodes.map do |n|
    compares = NODE_KIND_COMPARE.fetch(n.class, []).include?(term)
    satisfy(focus: node, path: path,
      value: n,
      message: "is#{' not' unless compares} a node kind match of #{term.to_sxp}",
      resultSeverity: (options.fetch(:severity) unless compares),
      component: RDF::Vocab::SHACL.NodeKindConstraintComponent,
      **options)
  end.flatten.compact
end

#compare(method, terms, node, path, value_nodes, component, **options) ⇒ Object (protected)

Common comparison logic for lessThan, lessThanOrEqual, max/minInclusive/Exclusive



425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
# File 'lib/shacl/algebra/shape.rb', line 425

def compare(method, terms, node, path, value_nodes, component, **options)
  terms = [terms] unless terms.is_a?(Array)
  value_nodes.map do |left|
    results = terms.map do |right|
      case left
      when RDF::Literal
        unless right.literal? && (
          (left.simple? && right.simple?) ||
          (left.is_a?(RDF::Literal::Numeric) && right.is_a?(RDF::Literal::Numeric)) ||
          (left.datatype == right.datatype && left.language == right.language))
          :incomperable
        else
          left.send(method, right)
        end
      when RDF::URI
        right.uri? && left.send(method, right)
      else
        :incomperable
      end
    end

    if results.include?(:incomperable)
      not_satisfied(focus: node, path: path,
        value: left,
        message: "is incomperable with #{terms.to_sxp}",
        resultSeverity: options.fetch(:severity),
        component: component,
        **options)
    elsif results.include?(false)
      not_satisfied(focus: node, path: path,
        value: left,
        message: "is not #{method} than #{terms.to_sxp}",
        resultSeverity: options.fetch(:severity),
        component: component,
        **options)
    else
      satisfy(focus: node, path: path,
        value: left,
        message: "is #{method} than #{terms.to_sxp}",
        component: component,
        **options)
    end
  end.flatten.compact
end

#targetNodesArray<RDF::Term>

Returns the nodes matching this particular shape, based upon the shape properties: * targetNode * targetClass * targetSubjectsOf * targetObjectsOf * id – where type includes rdfs:Class

Returns:

  • (Array<RDF::Term>)


15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/shacl/algebra/shape.rb', line 15

def targetNodes
  (Array(@options[:targetNode]) +
  Array(@options[:targetClass]).map do |cls|
    graph.query({predicate: RDF.type, object: cls}).subjects
  end +
  Array(@options[:targetSubjectsOf]).map do |pred|
    graph.query({predicate: pred}).subjects
  end +
  Array(@options[:targetObjectsOf]).map do |pred|
    graph.query({predicate: pred}).objects
  end + (
    Array(type).include?(RDF::RDFS.Class) ?
      graph.query({predicate: RDF.type, object: id}).subjects :
      []
  )).flatten.uniq
end