Class: SHACL::Algebra::Operator Abstract
- Inherits:
-
SPARQL::Algebra::Operator
- Object
- SPARQL::Algebra::Operator
- SHACL::Algebra::Operator
- Extended by:
- JSON::LD::Utils
- Includes:
- RDF::Util::Logger
- Defined in:
- lib/shacl/algebra/operator.rb
Overview
The SHACL operator.
Direct Known Subclasses
ConstraintComponent, QualifiedValueConstraintComponent, Shape
Constant Summary collapse
- BUILTIN_KEYS =
All keys associated with shapes which are set in options
%i( id type label name comment description deactivated severity message order group defaultValue path targetNode targetClass targetSubjectsOf targetObjectsOf class datatype nodeKind minCount maxCount minExclusive minInclusive maxExclusive maxInclusive minLength maxLength languageIn uniqueLang equals disjoint lessThan lessThanOrEquals closed ignoredProperties hasValue in declare namespace prefix ).freeze
- PARAMETERS =
Parameters to components.
{ and: {class: :AndConstraintComponent}, class: { class: :ClassConstraintComponent, nodeKind: :IRI, }, closed: { class: :ClosedConstraintComponent, datatype: RDF::XSD.boolean, }, datatype: { class: :DatatypeConstraintComponent, nodeKind: :IRI, maxCount: 1, }, disjoint: { class: :DisjointConstraintComponent, nodeKind: :IRI, }, equals: { class: :EqualsConstraintComponent, nodeKind: :IRI, }, expression: {class: :ExpressionConstraintComponent}, flags: { class: :PatternConstraintComponent, datatype: RDF::XSD.string, optional: true }, hasValue: { class: :HasValueConstraintComponent, nodeKind: :IRIOrLiteral, }, ignoredProperties: { class: :ClosedConstraintComponent, nodeKind: :IRI, # Added optional: true, }, in: { class: :InConstraintComponent, nodeKind: :IRIOrLiteral, #maxCount: 1, # List internalized }, languageIn: { class: :LanguageInConstraintComponent, datatype: RDF::XSD.string, # Added #maxCount: 1, # List internalized }, lessThan: { class: :LessThanConstraintComponent, nodeKind: :IRI, }, lessThanOrEquals: { class: :LessThanOrEqualsConstraintComponent, nodeKind: :IRI, }, maxCount: { class: :MaxCountConstraintComponent, datatype: RDF::XSD.integer, maxCount: 1, }, maxExclusive: { class: :MaxExclusiveConstraintComponent, maxCount: 1, nodeKind: :Literal, }, maxInclusive: { class: :MaxInclusiveConstraintComponent, maxCount: 1, nodeKind: :Literal, }, maxLength: { class: :MaxLengthConstraintComponent, datatype: RDF::XSD.integer, maxCount: 1, }, minCount: { class: :MinCountConstraintComponent, datatype: RDF::XSD.integer, maxCount: 1, }, minExclusive: { class: :MinExclusiveConstraintComponent, maxCount: 1, nodeKind: :Literal, }, minInclusive: { class: :MinInclusiveConstraintComponent, maxCount: 1, nodeKind: :Literal, }, minLength: { class: :MinLengthConstraintComponent, datatype: RDF::XSD.integer, maxCount: 1, }, node: {class: :NodeConstraintComponent}, nodeKind: { class: :NodeKindConstraintComponent, in: %i(BlankNode IRI Literal BlankNodeOrIRI BlankNodeOrLiteral IRIOrLiteral), maxCount: 1, }, not: {class: :NotConstraintComponent}, or: {class: :OrConstraintComponent}, pattern: { class: :PatternConstraintComponent, datatype: RDF::XSD.string, }, property: {class: :PropertyConstraintComponent}, qualifiedMaxCount: { class: :QualifiedValueConstraintComponent, datatype: RDF::XSD.integer, }, qualifiedValueShape: { class: :QualifiedValueConstraintComponent, }, qualifiedValueShapesDisjoint: { class: :QualifiedValueConstraintComponent, datatype: RDF::XSD.boolean, optional: true, }, qualifiedMinCount: { class: :QualifiedValueConstraintComponent, datatype: RDF::XSD.integer }, sparql: {class: :SPARQLConstraintComponent}, uniqueLang: { class: :UniqueLangConstraintComponent, datatype: RDF::XSD.boolean, maxCount: 1, }, xone: {class: :XoneConstraintComponent}, }
Instance Attribute Summary collapse
-
#graph ⇒ RDF::Queryable
Graph against which shapes are validated.
-
#options ⇒ Object
Initialization options.
-
#shapes_graph ⇒ RDF::Graph
Graph from which original shapes were loaded.
Class Method Summary collapse
-
.add_component(cls, parameters) ⇒ Object
Add parameters and class def from a SPARQL-based Constraint Component.
-
.apply_op(op, values) ⇒ Object
Recursively apply operand to sucessive values until the argument count which is expected is achieved.
-
.component_params ⇒ Hash{Symbol => Hash}
Constraint Component classes indexed to their mandatory and optional parameters, which may be supplemented by SPARQL-based Constraint Components.
-
.from_expanded_value(item, **options) ⇒ RDF::Term
Interpret a JSON-LD expanded value.
-
.from_json(operator, **options) ⇒ Operator
Creates an operator instance from a parsed SHACL representation.
-
.iri(value, base: RDF::Vocab::SHACL.to_uri, vocab: true, **options) ⇒ RDF::Value
Create URIs.
-
.params ⇒ Hash{Symbol => Hash}
Defined parameters for components, which may be supplemented by SPARQL-based Constraint Components.
-
.parse_path(path, **options) ⇒ RDF::URI, SPARQL::Algebra::Expression
Parse the “path” attribute into a SPARQL Property Path and evaluate to find related nodes.
-
.to_rdf(term, item, **options) ⇒ Object
Turn a JSON-LD value into its RDF representation.
Instance Method Summary collapse
-
#comment ⇒ RDF::Literal
Any comment associated with this operator.
-
#conforms(node, depth: 0, **options) ⇒ Array<ValidationResult>
Validates the specified
node
withingraph
, a list of ValidationResult. -
#deactivated? ⇒ Boolean
Is this shape deactivated?.
-
#id ⇒ RDF::Resource
The ID of this operator.
-
#iri(value, base: RDF::Vocab::SHACL.to_uri, vocab: true, **options) ⇒ RDF::Value
Create URIs.
-
#label ⇒ RDF::Literal
Any label associated with this operator.
-
#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.
-
#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.
-
#to_sxp_bin ⇒ Object
Create structure for serializing this component/shape, beginning with its cononical name.
-
#type ⇒ Array<RDF::URI>
The types associated with this operator.
Instance Attribute Details
#graph ⇒ RDF::Queryable
Graph against which shapes are validated.
38 39 40 |
# File 'lib/shacl/algebra/operator.rb', line 38 def graph @graph end |
#options ⇒ Object
Initialization options
34 35 36 |
# File 'lib/shacl/algebra/operator.rb', line 34 def @options end |
#shapes_graph ⇒ RDF::Graph
Graph from which original shapes were loaded.
42 43 44 |
# File 'lib/shacl/algebra/operator.rb', line 42 def shapes_graph @shapes_graph end |
Class Method Details
.add_component(cls, parameters) ⇒ Object
Add parameters and class def from a SPARQL-based Constraint Component
185 186 187 188 189 190 191 |
# File 'lib/shacl/algebra/operator.rb', line 185 def add_component(cls, parameters) # Remember added paraemters. # FIXME: should merge parameters @added_parameters = (@added_parameters || {}).merge(parameters) # Rebuild @params = @component_params = nil end |
.apply_op(op, values) ⇒ Object
Recursively apply operand to sucessive values until the argument count which is expected is achieved
502 503 504 505 506 507 |
# File 'lib/shacl/algebra/operator.rb', line 502 def apply_op(op, values) if values.length > op.arity values = values.first, apply_op(op, values[1..-1]) end op.new(*values) end |
.component_params ⇒ Hash{Symbol => Hash}
Constraint Component classes indexed to their mandatory and optional parameters, which may be supplemented by SPARQL-based Constraint Components.
204 205 206 207 208 209 210 211 212 213 214 |
# File 'lib/shacl/algebra/operator.rb', line 204 def component_params @component_params ||= params.inject({}) do |memo, (param, properties)| memo.merge(Array(properties[:class]).inject(memo) do |mem, cls| entry = mem.fetch(cls, {}) param_type = properties[:optional] ? :optional : :mandatory entry[param_type] ||= [] entry[param_type] << param mem.merge(cls => entry) end) end end |
.from_expanded_value(item, **options) ⇒ RDF::Term
Interpret a JSON-LD expanded value
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 |
# File 'lib/shacl/algebra/operator.rb', line 427 def (item, **) 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'], **) else RDF::Node.new end end |
.from_json(operator, **options) ⇒ Operator
Creates an operator instance from a parsed SHACL representation
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 |
# File 'lib/shacl/algebra/operator.rb', line 222 def from_json(operator, **) operands = [] # Node options used to instantiate the relevant class instance. node_opts = .dup # Node Options and operands on shape or node, which are not Constraint Component Parameters operator.each do |k, v| k = k.to_sym next if v.nil? || params.include?(k) case k # List properties when :id then node_opts[:id] = iri(v, vocab: false, **) when :path then node_opts[:path] = parse_path(v, **) when :property operands.push(*as_array(v).map {|vv| PropertyShape.from_json(vv, **)}) when :severity then node_opts[:severity] = iri(v, **) when :targetClass then node_opts[:targetClass] = as_array(v).map {|vv| iri(vv, **)} when :targetNode node_opts[:targetNode] = as_array(v).map do |vv| (vv, **) end when :targetObjectsOf then node_opts[:targetObjectsOf] = as_array(v).map {|vv| iri(vv, **)} when :targetSubjectsOf then node_opts[:targetSubjectsOf] = as_array(v).map {|vv| iri(vv, **)} when :type then node_opts[:type] = as_array(v).map {|vv| iri(vv, **)} else if BUILTIN_KEYS.include?(k) # Add as a plain option otherwise node_opts[k] = to_rdf(k, v, **) end end end # Node Options and operands on shape or node, which are Constraint Component Parameters. # For constraints with a defined Ruby class, the primary parameter is the NAME from the constraint class. Other parameters are added as named operands to the component operator. used_components = {} operator.each do |k, v| k = k.to_sym next if v.nil? || !params.include?(k) param_props = params[k] param_classes = Array(param_props[:class]) # Keep track of components which have been used. param_classes.each {|cls| used_components[cls] ||= {}} # Check parameter constraints v = as_array(v) if param_props[:maxCount] && v.length > param_props[:maxCount] raise SHACL::Error, "Property #{k} on #{self.const_get(:NAME)} has too many values: #{v.inspect}" end # If an optional parameter exists without corresponding mandatory parameters on a given shape, raise a SHACL::Error. # # Records any instances of components which are created to re-attach non-primary parameters after all operators are processed. instances = case k # List properties when :node as_array(v).map {|vv| NodeShape.from_json(vv, **)} when :property as_array(v).map {|vv| PropertyShape.from_json(vv, **)} when :sparql as_array(v).map {|vv| SPARQLConstraintComponent.from_json(vv, **)} else # Process parameter values based on nodeKind, in, and datatype. elements = if param_props[:nodeKind] case param_props[:nodeKind] when :IRI v.map {|vv| iri(vv, **)} when :Literal v.map do |vv| vv.is_a?(Hash) ? (vv, **) : RDF::Literal(vv) end when :IRIOrLiteral to_rdf(k, v, **) end elsif param_props[:in] v.map do |vv| iri(vv, **) if param_props[:in].include?(vv.to_sym) end elsif param_props[:datatype] v.map {|vv| RDF::Literal(vv, datatype: param_props[:datatype])} else v.map {|vv| SHACL::Algebra.from_json(vv, **)} end # Builtins are added as options to the operator, otherwise, they are class instances of constraint components added as operators. if BUILTIN_KEYS.include?(k) node_opts[k] = elements [] # No instances created else klass = SHACL::Algebra.const_get(Array(param_props[:class]).first) name = klass.const_get(:NAME) # If the key `k` is the same as the NAME of the class, create the instance with the defined element values. if name == k param_classes.each do |cls| # Add `k` as a mandatory parameter fulfilled (used_components[cls][:mandatory_parameters] ||= []) << k end # Instantiate the compoent elements.map {|e| klass.new(*e, **.dup)} else # Add non-primary parameters for subsequent insertion param_classes.each do |cls| # Add `k` as a mandatory parameter fulfilled if it is so defined (used_components[cls][:mandatory_parameters] ||= []) << k unless params[k][:optional] # Add parameter as S-Expression operand (used_components[cls][:parameters] ||= []) << elements.unshift(k) end [] # No instances created end end end # Record the instances created by class and its operands param_classes.each do |cls| used_components[cls][:instances] = instances end # FIXME: Only add instances when all mandatory parameters are present. operands.push(*instances) end # Append any parameters to the used components used_components.each do |cls, props| instances = props[:instances] next unless instances # BUILTINs parameters = props.fetch(:parameters, []) instances.each do |op| parameters.each do |param| # Note the potential that the parameter gets added twice, if there are multiple classes for both the primary and secondary paramters. op.operands << param end end end new(*operands, **node_opts) end |
.iri(value, base: RDF::Vocab::SHACL.to_uri, vocab: true, **options) ⇒ RDF::Value
Create URIs
371 372 373 374 375 376 377 378 379 380 381 382 383 |
# File 'lib/shacl/algebra/operator.rb', line 371 def iri(value, base: RDF::Vocab::SHACL.to_uri, vocab: true, **) # 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.(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 |
.params ⇒ Hash{Symbol => Hash}
Defined parameters for components, which may be supplemented by SPARQL-based Constraint Components. A parameter may be mapped to more than one component class.
196 197 198 |
# File 'lib/shacl/algebra/operator.rb', line 196 def params @params ||= PARAMETERS.merge(@added_parameters || {}) 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.
463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 |
# File 'lib/shacl/algebra/operator.rb', line 463 def parse_path(path, **) 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, **)} 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", **) {path.to_sxp} end else log_error('PropertyPath', "Can't handle path", **) {path.to_sxp} end end |
.to_rdf(term, item, **options) ⇒ Object
Turn a JSON-LD value into its RDF representation
390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 |
# File 'lib/shacl/algebra/operator.rb', line 390 def to_rdf(term, item, **) @context ||= JSON::LD::Context.parse("http://github.com/ruby-rdf/shacl/") return item.map {|v| to_rdf(term, v, **)} 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, **) when list?(item) RDF::List(*item['@list'].map {|v| to_rdf(term, v, **)}) 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
#comment ⇒ RDF::Literal
Any comment associated with this operator
529 |
# File 'lib/shacl/algebra/operator.rb', line 529 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.
548 549 550 |
# File 'lib/shacl/algebra/operator.rb', line 548 def conforms(node, depth: 0, **) raise NotImplemented end |
#deactivated? ⇒ Boolean
Is this shape deactivated?
525 |
# File 'lib/shacl/algebra/operator.rb', line 525 def deactivated?; @options[:deactivated] == RDF::Literal::TRUE; end |
#id ⇒ RDF::Resource
The ID of this operator
513 |
# File 'lib/shacl/algebra/operator.rb', line 513 def id; @options[:id]; end |
#iri(value, base: RDF::Vocab::SHACL.to_uri, vocab: true, **options) ⇒ RDF::Value
Create URIs
537 538 539 |
# File 'lib/shacl/algebra/operator.rb', line 537 def iri(value, base: RDF::Vocab::SHACL.to_uri, vocab: true, **) self.class.iri(value, base: base, vocab: vocab, **) end |
#label ⇒ RDF::Literal
Any label associated with this operator
521 |
# File 'lib/shacl/algebra/operator.rb', line 521 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.
591 592 593 594 595 |
# File 'lib/shacl/algebra/operator.rb', line 591 def not_satisfied(focus:, shape:, component:, resultSeverity: RDF::Vocab::SHACL.Violation, path: nil, value: nil, details: nil, message: nil, **) log_info(self.class.const_get(:NAME), "not satisfied #{value.to_sxp if value}#{': ' + if }", **) [SHACL::ValidationResult.new(focus, path, shape, resultSeverity, component, details, value, )] 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.
573 574 575 576 577 |
# File 'lib/shacl/algebra/operator.rb', line 573 def satisfy(focus:, shape:, component:, resultSeverity: nil, path: nil, value: nil, details: nil, message: nil, **) log_debug(self.class.const_get(:NAME), "#{'not ' if resultSeverity}satisfied #{value.to_sxp if value}#{': ' + if }", **) [SHACL::ValidationResult.new(focus, path, shape, resultSeverity, component, details, value, )] end |
#to_sxp_bin ⇒ Object
Create structure for serializing this component/shape, beginning with its cononical name.
553 554 555 556 557 558 559 |
# File 'lib/shacl/algebra/operator.rb', line 553 def to_sxp_bin expressions = BUILTIN_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 ⇒ Array<RDF::URI>
The types associated with this operator
517 |
# File 'lib/shacl/algebra/operator.rb', line 517 def type; @options[:type]; end |