Class: SHACL::Algebra::Operator Abstract

Inherits:
SPARQL::Algebra::Operator
  • Object
show all
Extended by:
JSON::LD::Utils
Includes:
RDF::Util::Logger
Defined in:
lib/shacl/algebra/operator.rb

Overview

This class is abstract.

The SHACL operator.

Direct Known Subclasses

And, Not, Or, QualifiedValueShape, Shape, Xone

Constant Summary collapse

ALL_KEYS =

All keys associated with shapes which are set in options

Returns:

  • (Array<Symbol>)
%i(
  id type label name comment description deactivated severity
  order group defaultValue path
  targetNode targetClass targetSubjectsOf targetObjectsOf
  class datatype nodeKind
  minCount maxCount
  minExclusive minInclusive maxExclusive maxInclusive
  minLength maxLength
  pattern flags languageIn uniqueLang
  qualifiedValueShapesDisjoint qualifiedMinCount qualifiedMaxCount
  equals disjoint lessThan lessThanOrEquals
  closed ignoredProperties hasValue in
).freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#graphObject

Graph against which shapes are validaed



36
37
38
# File 'lib/shacl/algebra/operator.rb', line 36

def graph
  @graph
end

#optionsObject

Initialization options



33
34
35
# File 'lib/shacl/algebra/operator.rb', line 33

def options
  @options
end

Class Method Details

.apply_op(op, values) ⇒ Object

Recursively apply operand to sucessive values until the argument count which is expected is achieved



235
236
237
238
239
240
# File 'lib/shacl/algebra/operator.rb', line 235

def apply_op(op, values)
  if values.length > op.arity
    values = values.first, apply_op(op, values[1..-1])
  end
  op.new(*values)
end

.from_expanded_value(item, **options) ⇒ RDF::Term

Interpret a JSON-LD expanded value

Parameters:

  • item (Hash)

Returns:

  • (RDF::Term)


160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/shacl/algebra/operator.rb', line 160

def from_expanded_value(item, **options)
  if item['@value']
    value, datatype = item.fetch('@value'), item.fetch('type', nil)
    case value
    when TrueClass, FalseClass
      value = value.to_s
      datatype ||= RDF::XSD.boolean.to_s
    when Numeric
      # Don't serialize as double if there are no fractional bits
      as_double = value.ceil != value || value >= 1e21 || datatype == RDF::XSD.double
      lit = if as_double
        RDF::Literal::Double.new(value, canonicalize: true)
      else
        RDF::Literal.new(value.numerator, canonicalize: true)
      end

      datatype ||= lit.datatype
      value = lit.to_s.sub("E+", "E")
    else
      datatype ||= item.has_key?('@language') ? RDF.langString : RDF::XSD.string
    end
    datatype = iri(datatype) if datatype
    language = item.fetch('@language', nil) if datatype == RDF.langString
    RDF::Literal.new(value, datatype: datatype, language: language)
  elsif item['id']
    self.iri(item['id'], **options)
  else
    RDF::Node.new
  end
end

.from_json(operator, **options) ⇒ Operator

Creates an operator instance from a parsed SHACL representation

Parameters:

  • operator (Hash)
  • options (Hash)

    ({})

Options Hash (**options):

  • :prefixes (Hash{String => RDF::URI})

Returns:



46
47
48
49
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
# File 'lib/shacl/algebra/operator.rb', line 46

def from_json(operator, **options)
  operands = []
  node_opts = options.dup
  operator.each do |k, v|
    next if v.nil?
    case k
    # List properties
    when 'and'
      elements = as_array(v).map {|vv| SHACL::Algebra.from_json(vv, **options)}
      operands << And.new(*elements, **options.dup)
    when 'class'              then node_opts[:class] = as_array(v).map {|vv| iri(vv, **options)} if v
    when 'datatype'           then node_opts[:datatype] = iri(v, **options)
    when 'disjoint'           then node_opts[:disjoint] = as_array(v).map {|vv| iri(vv, **options)} if v
    when 'equals'             then node_opts[:equals] = iri(v, **options)
    when 'id'                 then node_opts[:id] = iri(v, vocab: false, **options)
    when 'ignoredProperties'  then node_opts[:ignoredProperties] = as_array(v).map {|vv| iri(vv, **options)} if v
    when 'lessThan'           then node_opts[:lessThan] = iri(v, **options)
    when 'lessThanOrEquals'   then node_opts[:lessThanOrEquals] = iri(v, **options)
    when 'node'
      operands.push(*as_array(v).map {|vv| NodeShape.from_json(vv, **options)})
    when 'nodeKind'           then node_opts[:nodeKind] = iri(v, **options)
    when 'not'
      elements = as_array(v).map {|vv| SHACL::Algebra.from_json(vv, **options)}
      operands << Not.new(*elements, **options.dup)
    when 'or'
      elements = as_array(v).map {|vv| SHACL::Algebra.from_json(vv, **options)}
      operands << Or.new(*elements, **options.dup)
    when 'path'               then node_opts[:path] = parse_path(v, **options)
    when 'property'
      operands.push(*as_array(v).map {|vv| PropertyShape.from_json(vv, **options)})
    when 'qualifiedValueShape'
      elements = as_array(v).map {|vv| SHACL::Algebra.from_json(vv, **options)}
      operands << QualifiedValueShape.new(*elements, **options.dup)
    when 'severity'           then node_opts[:severity] = iri(v, **options)
    when 'targetClass'        then node_opts[:targetClass] = as_array(v).map {|vv| iri(vv, **options)} if v
    when 'targetNode'
      node_opts[:targetNode] = as_array(v).map do |vv|
        from_expanded_value(vv, **options)
      end if v
    when 'targetObjectsOf'    then node_opts[:targetObjectsOf] = as_array(v).map {|vv| iri(vv, **options)} if v
    when 'targetSubjectsOf'   then node_opts[:targetSubjectsOf] = as_array(v).map {|vv| iri(vv, **options)} if v
    when 'type'               then node_opts[:type] = as_array(v).map {|vv| iri(vv, **options)} if v
    when 'xone'
      elements = as_array(v).map {|vv| SHACL::Algebra.from_json(vv, **options)}
      operands << Xone.new(*elements, **options.dup)
    else
      # Add as a plain option if it is recognized
      node_opts[k.to_sym] = to_rdf(k.to_sym, v, **options) if ALL_KEYS.include?(k.to_sym)
    end
  end

  new(*operands, **node_opts)
end

.iri(value, base: RDF::Vocab::SHACL.to_uri, vocab: true, **options) ⇒ RDF::Value

Create URIs

Parameters:

  • value (RDF::Value, String)
  • base (RDF::URI) (defaults to: RDF::Vocab::SHACL.to_uri)

    Base IRI used for resolving relative values (RDF::Vocab::SHACL.to_uri).

  • vocab (Boolean) (defaults to: true)

    resolve vocabulary relative to the builtin context.

  • options (Hash{Symbol => Object})

Returns:

  • (RDF::Value)


104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/shacl/algebra/operator.rb', line 104

def iri(value, base: RDF::Vocab::SHACL.to_uri, vocab: true, **options)
  # Context will have been pre-loaded
  @context ||= JSON::LD::Context.parse("http://github.com/ruby-rdf/shacl/")

  value = value['id'] || value['@id'] if value.is_a?(Hash)
  result = @context.expand_iri(value, base: base, vocab: vocab)
  result = RDF::URI(result) if result.is_a?(String)
  if result.respond_to?(:qname) && result.qname
    result = RDF::URI.new(result.to_s) if result.frozen?
    result.lexical = result.qname.join(':')
  end
  result
end

.parse_path(path, **options) ⇒ RDF::URI, SPARQL::Algebra::Expression

Parse the “patH” attribute into a SPARQL Property Path and evaluate to find related nodes.

Parameters:

  • path (Object)

Returns:

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


196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/shacl/algebra/operator.rb', line 196

def parse_path(path, **options)
  case path
  when RDF::URI then path
  when String then iri(path)
  when Hash
    # Creates a SPARQL S-Expression resulting in a query which can be used to find corresponding
    {
      alternativePath: :alt,
      inversePath: :reverse,
      oneOrMorePath: :"path+",
      "@list": :seq,
      zeroOrMorePath: :"path*",
      zeroOrOnePath: :"path?",
    }.each do |prop, op_sym|
      if path[prop.to_s]
        value = path[prop.to_s]
        value = value['@list'] if value.is_a?(Hash) && value.key?('@list')
        value = [value] if !value.is_a?(Array)
        value = value.map {|e| parse_path(e, **options)}
        op = SPARQL::Algebra::Operator.for(op_sym)
        if value.length > op.arity
          # Divide into the first operand followed by the operator re-applied to the reamining operands
          value = value.first, apply_op(op, value[1..-1])
        end
        return op.new(*value)
      end
    end

    if path['id']
      iri(path['id'])
    else
      log_error('PropertyPath', "Can't handle path", **options) {path.to_sxp}
    end
  else
    log_error('PropertyPath', "Can't handle path", **options) {path.to_sxp}
  end
end

.to_rdf(term, item, **options) ⇒ Object

Turn a JSON-LD value into its RDF representation

Parameters:

  • term (Symbol)
  • item (Object)

Returns:

  • RDF::Term

See Also:

  • JSON::LD::ToRDF.item_to_rdf


123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/shacl/algebra/operator.rb', line 123

def to_rdf(term, item, **options)
  @context ||= JSON::LD::Context.parse("http://github.com/ruby-rdf/shacl/")

  return item.map {|v| to_rdf(term, v, **options)} if item.is_a?(Array)

  case
  when item.is_a?(TrueClass) || item.is_a?(FalseClass) || item.is_a?(Numeric)
    return RDF::Literal(item)
  when value?(item)
    value, datatype = item.fetch('@value'), item.fetch('type', nil)
    case value
    when TrueClass, FalseClass, Numeric
      return RDF::Literal(value)
    else
      datatype ||= item.has_key?('@direction') ?
        RDF::URI("https://www.w3.org/ns/i18n##{item.fetch('@language', '').downcase}_#{item['@direction']}") :
        (item.has_key?('@language') ? RDF.langString : RDF::XSD.string)
    end
    datatype = iri(datatype) if datatype
            
    # Initialize literal as an RDF literal using value and datatype. If element has the key @language and datatype is xsd:string, then add the value associated with the @language key as the language of the object.
    language = item.fetch('@language', nil) if datatype == RDF.langString
    return RDF::Literal.new(value, datatype: datatype, language: language)
  when node?(item)
    return iri(item, **options)
  when list?(item)
    RDF::List(*item['@list'].map {|v| to_rdf(term, v, **options)})
  when item.is_a?(String)
    RDF::Literal(item)
  else
    raise "Can't transform #{item.inspect} to RDF on property #{term}"
  end
end

Instance Method Details

#commentRDF::Literal

Any comment associated with this operator

Returns:

  • (RDF::Literal)


262
# File 'lib/shacl/algebra/operator.rb', line 262

def comment; @options[:comment]; end

#conforms(node, depth: 0, **options) ⇒ Array<ValidationResult>

Validates the specified ‘node` within `graph`, a list of ValidationResult.

A node conforms if it is not deactivated and all of its operands conform.

Parameters:

  • node (RDF::Term)
  • options (Hash{Symbol => Object})

Returns:

Raises:

  • (NotImplemented)


281
282
283
# File 'lib/shacl/algebra/operator.rb', line 281

def conforms(node, depth: 0, **options)
  raise NotImplemented
end

#deactivated?Boolean

Is this shape deactivated?

Returns:

  • (Boolean)


258
# File 'lib/shacl/algebra/operator.rb', line 258

def deactivated?; @options[:deactivated] == RDF::Literal::TRUE; end

#idRDF::Resource

The ID of this operator

Returns:

  • (RDF::Resource)


246
# File 'lib/shacl/algebra/operator.rb', line 246

def id; @options[:id]; end

#iri(value, base: RDF::Vocab::SHACL.to_uri, vocab: true, **options) ⇒ RDF::Value

Create URIs

Parameters:

  • value (RDF::Value, String)
  • base (RDF::URI) (defaults to: RDF::Vocab::SHACL.to_uri)

    Base IRI used for resolving relative values (RDF::Vocab::SHACL.to_uri).

  • vocab (Boolean) (defaults to: true)

    resolve vocabulary relative to the builtin context.

  • options (Hash{Symbol => Object})

Returns:

  • (RDF::Value)


270
271
272
# File 'lib/shacl/algebra/operator.rb', line 270

def iri(value, base: RDF::Vocab::SHACL.to_uri, vocab: true, **options)
  self.class.iri(value, base: base, vocab: vocab, **options)
end

#labelRDF::Literal

Any label associated with this operator

Returns:

  • (RDF::Literal)


254
# File 'lib/shacl/algebra/operator.rb', line 254

def label; @options[:label]; end

#not_satisfied(focus:, shape:, component:, resultSeverity: RDF::Vocab::SHACL.Violation, path: nil, value: nil, details: nil, message: nil, **options) ⇒ Array<SHACL::ValidationResult>

Create a result that does not satisfies the shape.

Parameters:

  • focus (RDF::Term)
  • shape (RDF::Resource)
  • component (RDF::URI)
  • resultSeverity (RDF::URI) (defaults to: RDF::Vocab::SHACL.Violation)

    (RDF:::Vocab::SHACL.Violation)

  • path (Array<RDF::URI>) (defaults to: nil)

    (nil)

  • value (RDF::Term) (defaults to: nil)

    (nil)

  • details (RDF::Term) (defaults to: nil)

    (nil)

  • message (String) (defaults to: nil)

    (nil)

Returns:



323
324
325
326
327
# File 'lib/shacl/algebra/operator.rb', line 323

def not_satisfied(focus:, shape:, component:, resultSeverity: RDF::Vocab::SHACL.Violation, path: nil, value: nil, details: nil, message: nil, **options)
  log_info(self.class.const_get(:NAME), "not satisfied #{value.to_sxp if value}#{': ' + message if message}", **options)
  [SHACL::ValidationResult.new(focus, path, shape, resultSeverity, component,
                               details, value, message)]
end

#satisfy(focus:, shape:, component:, resultSeverity: nil, path: nil, value: nil, details: nil, message: nil, **options) ⇒ Array<SHACL::ValidationResult>

Create a result that satisfies the shape.

Parameters:

  • focus (RDF::Term)
  • shape (RDF::Resource)
  • component (RDF::URI)
  • resultSeverity (RDF::URI) (defaults to: nil)

    (nil)

  • path (Array<RDF::URI>) (defaults to: nil)

    (nil)

  • value (RDF::Term) (defaults to: nil)

    (nil)

  • details (RDF::Term) (defaults to: nil)

    (nil)

  • message (String) (defaults to: nil)

    (nil)

Returns:



305
306
307
308
309
# File 'lib/shacl/algebra/operator.rb', line 305

def satisfy(focus:, shape:, component:, resultSeverity: nil, path: nil, value: nil, details: nil, message: nil, **options)
  log_debug(self.class.const_get(:NAME), "#{'not ' if resultSeverity}satisfied #{value.to_sxp if value}#{': ' + message if message}", **options)
  [SHACL::ValidationResult.new(focus, path, shape, resultSeverity, component,
                               details, value, message)]
end

#to_sxp_binObject



285
286
287
288
289
290
291
# File 'lib/shacl/algebra/operator.rb', line 285

def to_sxp_bin
  expressions = ALL_KEYS.inject([self.class.const_get(:NAME)]) do |memo, sym|
    @options[sym] ? memo.push([sym, *@options[sym]]) : memo
  end + operands

  expressions.to_sxp_bin
end

#typeArray<RDF::URI>

The types associated with this operator

Returns:

  • (Array<RDF::URI>)


250
# File 'lib/shacl/algebra/operator.rb', line 250

def type; @options[:type]; end