class SHACL::Algebra::Operator

The SHACL operator.

@abstract

Constants

ALL_KEYS

All keys associated with shapes which are set in options

@return [Array<Symbol>]

Attributes

graph[RW]

Graph against which shapes are validaed

options[RW]

Initialization options

Public Class Methods

from_expanded_value(item, **options) click to toggle source

Interpret a JSON-LD expanded value @param [Hash] item @return [RDF::Term]

# 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) click to toggle source

Creates an operator instance from a parsed SHACL representation @param [Hash] operator @param [Hash] options ({}) @option options [Hash{String => RDF::URI}] :prefixes @return [Operator]

# 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) click to toggle source

Create URIs @param (see iri) @option (see iri) @return (see iri)

# 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) click to toggle source

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

@param [Object] path @return [RDF::URI, SPARQL::Algebra::Expression]

# 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) click to toggle source

Turn a JSON-LD value into its RDF representation @see JSON::LD::ToRDF.item_to_rdf @param [Symbol] term @param [Object] item @return RDF::Term

# 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

Protected Class Methods

apply_op(op, values) click to toggle source

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

# 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

Public Instance Methods

comment() click to toggle source

Any comment associated with this operator @return [RDF::Literal]

# File lib/shacl/algebra/operator.rb, line 262
def comment; @options[:comment]; end
conforms(node, depth: 0, **options) click to toggle source

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

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

@param [RDF::Term] node @param [Hash{Symbol => Object}] options @return [Array<ValidationResult>]

# File lib/shacl/algebra/operator.rb, line 281
def conforms(node, depth: 0, **options)
  raise NotImplemented
end
deactivated?() click to toggle source

Is this shape deactivated? @return [Boolean]

# File lib/shacl/algebra/operator.rb, line 258
def deactivated?; @options[:deactivated] == RDF::Literal::TRUE; end
id() click to toggle source

The ID of this operator @return [RDF::Resource]

# File lib/shacl/algebra/operator.rb, line 246
def id; @options[:id]; end
iri(value, base: RDF::Vocab::SHACL.to_uri, vocab: true, **options) click to toggle source

Create URIs @param [RDF::Value, String] value @param [RDF::URI] base Base IRI used for resolving relative values (RDF::Vocab::SHACL.to_uri). @param [Boolean] vocab resolve vocabulary relative to the builtin context. @param [Hash{Symbol => Object}] options @return [RDF::Value]

# 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
label() click to toggle source

Any label associated with this operator @return [RDF::Literal]

# 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) click to toggle source

Create a result that does not satisfies the shape.

@param [RDF::Term] focus @param [RDF::Resource] shape @param [RDF::URI] component @param [RDF::URI] resultSeverity (RDF:::Vocab::SHACL.Violation) @param [Array<RDF::URI>] path (nil) @param [RDF::Term] value (nil) @param [RDF::Term] details (nil) @param [String] message (nil) @return [Array<SHACL::ValidationResult>]

# 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) click to toggle source

Create a result that satisfies the shape.

@param [RDF::Term] focus @param [RDF::Resource] shape @param [RDF::URI] component @param [RDF::URI] resultSeverity (nil) @param [Array<RDF::URI>] path (nil) @param [RDF::Term] value (nil) @param [RDF::Term] details (nil) @param [String] message (nil) @return [Array<SHACL::ValidationResult>]

# 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_bin() click to toggle source
# 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
type() click to toggle source

The types associated with this operator @return [Array<RDF::URI>]

# File lib/shacl/algebra/operator.rb, line 250
def type; @options[:type]; end