Class: SHACL::Shapes

Inherits:
Array
  • Object
show all
Includes:
RDF::Util::Logger
Defined in:
lib/shacl/shapes.rb

Overview

The set of shapes loaded from a graph.

Constant Summary collapse

SHAPES_FRAME =
JSON.parse(%({
  "@context": {
    "id": "@id",
    "type": {"@id": "@type", "@container": "@set"},
    "@vocab": "http://www.w3.org/ns/shacl#",
    "owl": "http://www.w3.org/2002/07/owl#",
    "rdfs": "http://www.w3.org/2000/01/rdf-schema#",
    "shacl": "http://www.w3.org/ns/shacl#",
    "sh": "http://www.w3.org/ns/shacl#",
    "xsd": "http://www.w3.org/2001/XMLSchema#",
    "and": {"@type": "@id"},
    "annotationProperty": {"@type": "@id"},
    "class": {"@type": "@id"},
    "comment": "http://www.w3.org/2000/01/rdf-schema#comment",
    "condition": {"@type": "@id"},
    "datatype": {"@type": "@vocab"},
    "declare": {"@type": "@id"},
    "disjoint": {"@type": "@id"},
    "entailment": {"@type": "@id"},
    "equals": {"@type": "@id"},
    "ignoredProperties": {"@type": "@id", "@container": "@list"},
    "imports": {"@id": "owl:imports", "@type": "@id"},
    "in": {"@type": "@none", "@container": "@list"},
    "inversePath": {"@type": "@id"},
    "label": "http://www.w3.org/2000/01/rdf-schema#label",
    "languageIn": {"@container": "@list"},
    "lessThan": {"@type": "@id"},
    "lessThanOrEquals": {"@type": "@id"},
    "namespace": {"@type": "xsd:anyURI"},
    "nodeKind": {"@type": "@vocab"},
    "or": {"@type": "@id"},
    "path": {"@type": "@none"},
    "prefixes": {"@type": "@id"},
    "property": {"@type": "@id"},
    "severity": {"@type": "@vocab"},
    "sparql": {"@type": "@id"},
    "targetClass": {"@type": "@id"},
    "targetNode": {"@type": "@none"},
    "xone": {"@type": "@id"}
  },
  "and": {},
  "class": {},
  "datatype": {},
  "in": {"@embed": "@never"},
  "node": {},
  "nodeKind": {},
  "not": {},
  "or": {},
  "property": {},
  "sparql": {},
  "targetClass": {},
  "targetNode": {},
  "targetObjectsOf": {},
  "xone": {},
  "targetSubjectsOf": {}
})).freeze
COMPONENTS_FRAME =
JSON.parse(%({
  "@context": {
    "id": "@id",
    "type": {"@id": "@type", "@container": "@set"},
    "@vocab": "http://www.w3.org/ns/shacl#",
    "owl": "http://www.w3.org/2002/07/owl#",
    "rdfs": "http://www.w3.org/2000/01/rdf-schema#",
    "shacl": "http://www.w3.org/ns/shacl#",
    "sh": "http://www.w3.org/ns/shacl#",
    "xsd": "http://www.w3.org/2001/XMLSchema#",
    "and": {"@type": "@id"},
    "annotationProperty": {"@type": "@id"},
    "class": {"@type": "@id"},
    "comment": "http://www.w3.org/2000/01/rdf-schema#comment",
    "condition": {"@type": "@id"},
    "datatype": {"@type": "@vocab"},
    "declare": {"@type": "@id"},
    "disjoint": {"@type": "@id"},
    "entailment": {"@type": "@id"},
    "equals": {"@type": "@id"},
    "ignoredProperties": {"@type": "@id", "@container": "@list"},
    "imports": {"@id": "owl:imports", "@type": "@id"},
    "in": {"@type": "@none", "@container": "@list"},
    "inversePath": {"@type": "@id"},
    "label": "http://www.w3.org/2000/01/rdf-schema#label",
    "languageIn": {"@container": "@list"},
    "lessThan": {"@type": "@id"},
    "lessThanOrEquals": {"@type": "@id"},
    "namespace": {"@type": "xsd:anyURI"},
    "nodeKind": {"@type": "@vocab"},
    "or": {"@type": "@id"},
    "path": {"@type": "@id"},
    "prefixes": {"@type": "@id"},
    "property": {"@type": "@id"},
    "severity": {"@type": "@vocab"},
    "sparql": {"@type": "@id"},
    "targetClass": {"@type": "@id"},
    "targetNode": {"@type": "@none"},
    "xone": {"@type": "@id"}
  },
  "@type": "ConstraintComponent"
})).freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#loaded_graphsArray<RDF::URI> (readonly)

The graphs which have been loaded as shapes

Returns:

  • (Array<RDF::URI>)


21
22
23
# File 'lib/shacl/shapes.rb', line 21

def loaded_graphs
  @loaded_graphs
end

#shape_jsonArray<Hash> (readonly)

The JSON used to instantiate shapes

Returns:

  • (Array<Hash>)


26
27
28
# File 'lib/shacl/shapes.rb', line 26

def shape_json
  @shape_json
end

#shapes_graphRDF::Graph (readonly)

The original shapes graph

Returns:

  • (RDF::Graph)


15
16
17
# File 'lib/shacl/shapes.rb', line 15

def shapes_graph
  @shapes_graph
end

Class Method Details

.from_graph(graph, loaded_graphs: [], **options) ⇒ Shapes

Initializes the shapes from graphloading owl:imports until all references are loaded.

The shapes come from the following: * Instances of sh:NodeShape or sh:PropertyShape * resources that have any of the properties sh:targetClass, sh:targetNode, sh:targetObjectsOf, or sh:targetSubjectsOf.

Parameters:

  • graph (RDF::Graph)
  • loaded_graphs (Array<RDF::URI>) (defaults to: [])

    = [] The graphs which have been loaded as shapes

  • options (Hash{Symbol => Object})

Returns:

Raises:



41
42
43
44
45
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
# File 'lib/shacl/shapes.rb', line 41

def self.from_graph(graph, loaded_graphs: [], **options)
  @loded_graphs = loaded_graphs

  import_count = 0
  while (imports = graph.query({predicate: RDF::OWL.imports}).map(&:object)).count > import_count
    # Load each imported graph
    imports.each do |ref|
      # Don't try import if the import subject is already in the graph
      unless graph.subject?(ref)
        begin
          options[:logger].info('Shapes') {"load import #{ref}"} if options[:logger].respond_to?(:info)
          graph.load(ref)
          loaded_graphs << ref
        rescue IOError => e
          # Skip import
          options[:logger].warn('Shapes') {"load import #{ref}"} if options[:logger].respond_to?(:warn)
        end
      end
      import_count += 1
    end
  end

  # Serialize the graph as framed JSON-LD and initialize patterns, recursively.
  expanded = JSON::LD::API.fromRdf(graph, useNativeTypes: true)

  # Node and Property constraints
  shape_json = JSON::LD::API.frame(expanded, SHAPES_FRAME,
    omitGraph: false,
    embed: '@always',
    expanded: true)['@graph']

  # Any defined Constraint Components
  components = JSON::LD::API.frame(expanded, COMPONENTS_FRAME,
    omitGraph: false,
    embed: '@always',
    expanded: true)['@graph']

  # Extract any constraint components and merge to top-level
  shape_json = components + shape_json

  # Create an array of the framed shapes
  shapes = self.new(shape_json.map {|o| Algebra.from_json(o, **options)})
  shapes.instance_variable_set(:@shape_json, shape_json)
  shapes.instance_variable_set(:@shapes_graph, graph)
  shapes
end

.from_queryable(queryable, **options) ⇒ Shapes

Retrieve shapes from a sh:shapesGraph reference within the queryable

Parameters:

  • queryable (RDF::Queryable)

    The data graph which may contain references to the shapes graph

  • options (Hash{Symbol => Object})

Returns:

Raises:



96
97
98
99
100
101
102
103
# File 'lib/shacl/shapes.rb', line 96

def self.from_queryable(queryable, **options)
  # Query queryable to find one ore more shapes graphs
  graph_names = queryable.query({predicate: RDF::Vocab::SHACL.shapesGraph}).objects
  graph = RDF::Graph.new(graph_name: graph_names.first, data: RDF::Repository.new) do |g|
    graph_names.each {|iri| g.load(iri, graph_name: graph_names.first)}
  end
  from_graph(graph, loaded_graphs: graph_names, **options)
end

Instance Method Details

#execute(graph, depth: 0, **options) ⇒ Hash{RDF::Term => Array<ValidationResult>}, SHACL::ValidationReport

Match on schema. Finds appropriate shape for node, and matches that shape.

Parameters:

  • graph (RDF::Queryable)
  • options (Hash{Symbol => Object})

Options Hash (**options):

  • :focus (RDF::Term)

    An explicit focus node, overriding any defined on the top-level shaps.

  • :logger (Logger, #write, #<<)

    Record error/info/debug output

Returns:



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/shacl/shapes.rb', line 116

def execute(graph, depth: 0, **options)
  self.each do |shape|
    shape.graph = graph
    shape.shapes_graph = shapes_graph
    shape.each_descendant do |op|
      op.instance_variable_set(:@logger, options[:logger]) if
        options[:logger] && op.respond_to?(:execute)
      op.graph = graph if op.respond_to?(:graph=)
      op.shapes_graph = shapes_graph if op.respond_to?(:shapes_graph=)
    end
  end

  # Execute all shapes against their target nodes
  ValidationReport.new(self.map do |shape|
    nodes = Array(options.fetch(:focus, shape.targetNodes))
    nodes.map do |node|
      shape.conforms(node, depth: depth + 1)
    end
  end.flatten)
end

#to_sxp(**options) ⇒ String

Transform Shapes into an SXP.

Returns:

  • (String)


145
146
147
# File 'lib/shacl/shapes.rb', line 145

def to_sxp(**options)
  to_sxp_bin.to_sxp(**options)
end

#to_sxp_binObject



137
138
139
# File 'lib/shacl/shapes.rb', line 137

def to_sxp_bin
  [:shapes, super]
end