shacl 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,65 @@
1
+ module SHACL::Algebra
2
+ ##
3
+ class Xone < Operator
4
+ NAME = :xone
5
+
6
+ ##
7
+ # Specifies the condition that each value node conforms to exactly one of the provided shapes.
8
+ #
9
+ # @example
10
+ # ex:XoneConstraintExampleShape
11
+ # a sh:NodeShape ;
12
+ # sh:targetClass ex:Person ;
13
+ # sh:xone (
14
+ # [
15
+ # sh:property [
16
+ # sh:path ex:fullName ;
17
+ # sh:minCount 1 ;
18
+ # ]
19
+ # ]
20
+ # [
21
+ # sh:property [
22
+ # sh:path ex:firstName ;
23
+ # sh:minCount 1 ;
24
+ # ] ;
25
+ # sh:property [
26
+ # sh:path ex:lastName ;
27
+ # sh:minCount 1 ;
28
+ # ]
29
+ # ]
30
+ # ) .
31
+ #
32
+ # @param [RDF::Term] node
33
+ # @param [Hash{Symbol => Object}] options
34
+ # @return [Array<SHACL::ValidationResult>]
35
+ def conforms(node, path: nil, depth: 0, **options)
36
+ log_debug(NAME, depth: depth) {SXP::Generator.string({node: node}.to_sxp_bin)}
37
+ num_conform = operands.inject(0) do |memo, op|
38
+ results = op.conforms(node, depth: depth + 1, **options)
39
+ memo += (results.all?(&:conform?) ? 1 : 0)
40
+ end
41
+ case num_conform
42
+ when 0
43
+ not_satisfied(focus: node, path: path,
44
+ value: node,
45
+ message: "node does not conform to any shape",
46
+ resultSeverity: options.fetch(:severity),
47
+ component: RDF::Vocab::SHACL.XoneConstraintComponent,
48
+ depth: depth, **options)
49
+ when 1
50
+ satisfy(focus: node, path: path,
51
+ value: node,
52
+ message: "node conforms to a single shape",
53
+ component: RDF::Vocab::SHACL.XoneConstraintComponent,
54
+ depth: depth, **options)
55
+ else
56
+ not_satisfied(focus: node, path: path,
57
+ value: node,
58
+ message: "node conforms to #{num_conform} shapes",
59
+ resultSeverity: options.fetch(:severity),
60
+ component: RDF::Vocab::SHACL.XoneConstraintComponent,
61
+ depth: depth, **options)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,41 @@
1
+ # -*- encoding: utf-8 -*-
2
+ # frozen_string_literal: true
3
+ # This file generated automatically from http://github.com/ruby-rdf/shacl/
4
+ require 'json/ld'
5
+ class JSON::LD::Context
6
+ add_preloaded("http://github.com/ruby-rdf/shacl/") do
7
+ new(vocab: "http://www.w3.org/ns/shacl#", processingMode: "json-ld-1.1", term_definitions: {
8
+ "and" => TermDefinition.new("and", id: "http://www.w3.org/ns/shacl#and", type_mapping: "@id", container_mapping: "@list"),
9
+ "annotationProperty" => TermDefinition.new("annotationProperty", id: "http://www.w3.org/ns/shacl#annotationProperty", type_mapping: "@id"),
10
+ "class" => TermDefinition.new("class", id: "http://www.w3.org/ns/shacl#class", type_mapping: "@id"),
11
+ "comment" => TermDefinition.new("comment", id: "http://www.w3.org/2000/01/rdf-schema#comment", simple: true),
12
+ "condition" => TermDefinition.new("condition", id: "http://www.w3.org/ns/shacl#condition", type_mapping: "@id"),
13
+ "datatype" => TermDefinition.new("datatype", id: "http://www.w3.org/ns/shacl#datatype", type_mapping: "@vocab"),
14
+ "declare" => TermDefinition.new("declare", id: "http://www.w3.org/ns/shacl#declare", type_mapping: "@id"),
15
+ "disjoint" => TermDefinition.new("disjoint", id: "http://www.w3.org/ns/shacl#disjoint", type_mapping: "@id"),
16
+ "entailment" => TermDefinition.new("entailment", id: "http://www.w3.org/ns/shacl#entailment", type_mapping: "@id"),
17
+ "equals" => TermDefinition.new("equals", id: "http://www.w3.org/ns/shacl#equals", type_mapping: "@id"),
18
+ "id" => TermDefinition.new("id", id: "@id", simple: true),
19
+ "ignoredProperties" => TermDefinition.new("ignoredProperties", id: "http://www.w3.org/ns/shacl#ignoredProperties", type_mapping: "@id", container_mapping: "@list"),
20
+ "in" => TermDefinition.new("in", id: "http://www.w3.org/ns/shacl#in", type_mapping: "@none", container_mapping: "@list"),
21
+ "inversePath" => TermDefinition.new("inversePath", id: "http://www.w3.org/ns/shacl#inversePath", type_mapping: "@id"),
22
+ "label" => TermDefinition.new("label", id: "http://www.w3.org/2000/01/rdf-schema#label", simple: true),
23
+ "languageIn" => TermDefinition.new("languageIn", id: "http://www.w3.org/ns/shacl#languageIn", container_mapping: "@list"),
24
+ "lessThan" => TermDefinition.new("lessThan", id: "http://www.w3.org/ns/shacl#lessThan", type_mapping: "@id"),
25
+ "lessThanOrEquals" => TermDefinition.new("lessThanOrEquals", id: "http://www.w3.org/ns/shacl#lessThanOrEquals", type_mapping: "@id"),
26
+ "nodeKind" => TermDefinition.new("nodeKind", id: "http://www.w3.org/ns/shacl#nodeKind", type_mapping: "@vocab"),
27
+ "or" => TermDefinition.new("or", id: "http://www.w3.org/ns/shacl#or", type_mapping: "@id", container_mapping: "@list"),
28
+ "path" => TermDefinition.new("path", id: "http://www.w3.org/ns/shacl#path", type_mapping: "@none"),
29
+ "property" => TermDefinition.new("property", id: "http://www.w3.org/ns/shacl#property", type_mapping: "@id"),
30
+ "rdfs" => TermDefinition.new("rdfs", id: "http://www.w3.org/2000/01/rdf-schema#", simple: true, prefix: true),
31
+ "severity" => TermDefinition.new("severity", id: "http://www.w3.org/ns/shacl#severity", type_mapping: "@vocab"),
32
+ "sh" => TermDefinition.new("sh", id: "http://www.w3.org/ns/shacl#", simple: true, prefix: true),
33
+ "shacl" => TermDefinition.new("shacl", id: "http://www.w3.org/ns/shacl#", simple: true, prefix: true),
34
+ "targetClass" => TermDefinition.new("targetClass", id: "http://www.w3.org/ns/shacl#targetClass", type_mapping: "@id"),
35
+ "targetNode" => TermDefinition.new("targetNode", id: "http://www.w3.org/ns/shacl#targetNode", type_mapping: "@none"),
36
+ "type" => TermDefinition.new("type", id: "@type", container_mapping: "@set"),
37
+ "xone" => TermDefinition.new("xone", id: "http://www.w3.org/ns/shacl#xone", type_mapping: "@id", container_mapping: "@list"),
38
+ "xsd" => TermDefinition.new("xsd", id: "http://www.w3.org/2001/XMLSchema#", simple: true, prefix: true)
39
+ })
40
+ end
41
+ end
@@ -0,0 +1,88 @@
1
+ require 'rdf/format'
2
+
3
+ module SHACL
4
+ ##
5
+ # SHACL format specification. Note that this format does not define any readers or writers.
6
+ #
7
+ # @example Obtaining an ShEx format class
8
+ # RDF::Format.for(:shacl) #=> ShEx::Format
9
+ class Format < RDF::Format
10
+ ##
11
+ # Hash of CLI commands appropriate for this format
12
+ # @return [Hash{Symbol => Lambda(Array, Hash)}]
13
+ def self.cli_commands
14
+ {
15
+ shacl: {
16
+ description: "Validate repository given shape",
17
+ help: %(shacl [--shape URI] [--focus Resource] [--replace]
18
+
19
+ Evaluates the repository according the the specified shapes.
20
+ If no shape file is specified, it will look for one or more
21
+ shapes graphs using the sh:shapesGraph property found within
22
+ the repository.
23
+ ).gsub(/^\s+/, ''),
24
+ parse: true,
25
+ lambda: -> (argv, **options) do
26
+ shacl = case options[:shape]
27
+ when IO, StringIO
28
+ SHACL.get_shapes(RDF::Reader.new(options[:shape]), **options)
29
+ when nil
30
+ SHACL.from_queryable(RDF::CLI.repository, **options)
31
+ else SHACL.open(options[:shape], **options)
32
+ end
33
+
34
+ if options[:to_sxp]
35
+ options[:messages][:shacl] = {}
36
+ options[:messages][:shacl].merge!({"S-Expression": [SXP::Generator.string(shacl.to_sxp_bin)]})
37
+ else
38
+ start = Time.now
39
+ report = shacl.execute(RDF::CLI.repository, **options)
40
+ secs = Time.new - start
41
+ options[:logger].info "SHACL resulted in #{report.conform? ? 'success' : 'failure'} including #{report.count} results."
42
+ options[:logger].info "Validated in #{secs} seconds."
43
+ options[:messages][:shacl] = {result: report.conform? ? "Satisfied shape" : "Did not satisfy shape"}
44
+ if report.conform?
45
+ options[:messages][:shacl] = {result: ["Satisfied shape"]}
46
+ else
47
+ RDF::CLI.repository << report
48
+ options[:messages][:shacl] = {result: ["Did not satisfy shape: #{report.count} results"]}
49
+ options[:messages].merge!(report.linter_messages)
50
+ end
51
+ end
52
+ RDF::CLI.repository
53
+ end,
54
+ options: [
55
+ RDF::CLI::Option.new(
56
+ symbol: :focus,
57
+ datatype: String,
58
+ control: :text,
59
+ on: ["--focus Resource"],
60
+ description: "Focus node within repository"
61
+ ) {|v| RDF::URI(v)},
62
+ RDF::CLI::Option.new(
63
+ symbol: :replace,
64
+ datatype: TrueClass,
65
+ control: :checkbox,
66
+ on: ["--replace"],
67
+ description: "Replaces the data graph with the validation report"
68
+ ) {|v| RDF::URI(v)},
69
+ RDF::CLI::Option.new(
70
+ symbol: :shape,
71
+ datatype: String,
72
+ control: :url2,
73
+ on: ["--shape URI"],
74
+ description: "SHACL shapes graph location"
75
+ ) {|v| RDF::URI(v)},
76
+ RDF::CLI::Option.new(
77
+ symbol: :to_sxp,
78
+ datatype: String,
79
+ control: :checkbox,
80
+ on: ["--to-sxp"],
81
+ description: "Display parsed shapes as an S-Expression"
82
+ ),
83
+ ]
84
+ }
85
+ }
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,198 @@
1
+ # Localized refinements to externally defined classes
2
+ module SHACL::Refinements
3
+ using SHACL::Refinements
4
+
5
+ refine Hash do
6
+ # @!parse
7
+ # # Refinements on Hash
8
+ # class Hash
9
+ # ##
10
+ # # Deep merge two hashes folding array values together.
11
+ # #
12
+ # # @param [Hash] second
13
+ # # @return [Hash]
14
+ # def deep_merge(second); end
15
+ # end
16
+ def deep_merge(second)
17
+ merger = ->(_, v1, v2) {Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : Array === v1 && Array === v2 ? v1 | v2 : v2.nil? ? v1 : v2 }
18
+ merge(second.to_h, &merger)
19
+ end
20
+ end
21
+
22
+ refine SPARQL::Algebra::Operator::Alt do
23
+ # @!parse
24
+ # # Refinements on SPARQL::Algebra::Operator::Alt
25
+ # class SPARQL::Algebra::Operator::Alt
26
+ # ##
27
+ # # Retrieve the possibly newly assigned blank node subject to use for representing this operator.
28
+ # # @return [RDF::Node]
29
+ # attr_accessor :subject
30
+ #
31
+ # ##
32
+ # # Generate the SHACL representation of this operator
33
+ # # @return [RDF::Node]
34
+ # def each_statement(&block); end.
35
+ # end
36
+ attr_accessor :subject
37
+ def each_statement(&block)
38
+ @subject = RDF::Node.new
39
+ elements = operands.map do |op|
40
+ if op.respond_to?(:each_statement)
41
+ op.each_statement(&block)
42
+ op.subject
43
+ else
44
+ op
45
+ end
46
+ end
47
+ list = RDF::List(*elements)
48
+ list.each_statement(&block)
49
+ block.call(RDF::Statement(@subject, RDF::Vocab::SHACL.alternativePath, list.subject))
50
+ end
51
+ end
52
+
53
+ refine SPARQL::Algebra::Operator::PathOpt do
54
+ # @!parse
55
+ # # Refinements on SPARQL::Algebra::Operator::PathOpt
56
+ # class SPARQL::Algebra::Operator::Alt
57
+ # ##
58
+ # # Retrieve the possibly newly assigned blank node subject to use for representing this operator.
59
+ # # @return [RDF::Node]
60
+ # attr_accessor :subject
61
+ #
62
+ # ##
63
+ # # Generate the SHACL representation of this operator
64
+ # # @return [RDF::Node]
65
+ # def each_statement(&block); end.
66
+ # end
67
+ attr_accessor :subject
68
+ def each_statement(&block)
69
+ @subject = RDF::Node.new
70
+ operands.each do |op|
71
+ obj = if op.respond_to?(:each_statement)
72
+ op.each_statement(&block)
73
+ op.subject
74
+ else
75
+ op
76
+ end
77
+ block.call(RDF::Statement(@subject, RDF::Vocab::SHACL.zeroOrOnePath, obj))
78
+ end
79
+ end
80
+ end
81
+
82
+ refine SPARQL::Algebra::Operator::PathPlus do
83
+ # @!parse
84
+ # # Refinements on SPARQL::Algebra::Operator::PathPlus
85
+ # class SPARQL::Algebra::Operator::Alt
86
+ # ##
87
+ # # Retrieve the possibly newly assigned blank node subject to use for representing this operator.
88
+ # # @return [RDF::Node]
89
+ # attr_accessor :subject
90
+ #
91
+ # ##
92
+ # # Generate the SHACL representation of this operator
93
+ # # @return [RDF::Node]
94
+ # def each_statement(&block); end.
95
+ # end
96
+ attr_accessor :subject
97
+ def each_statement(&block)
98
+ @subject = RDF::Node.new
99
+ operands.each do |op|
100
+ obj = if op.respond_to?(:each_statement)
101
+ op.each_statement(&block)
102
+ op.subject
103
+ else
104
+ op
105
+ end
106
+ block.call(RDF::Statement(@subject, RDF::Vocab::SHACL.oneOrMorePath, obj))
107
+ end
108
+ end
109
+ end
110
+
111
+ refine SPARQL::Algebra::Operator::PathStar do
112
+ # @!parse
113
+ # # Refinements on SPARQL::Algebra::Operator::PathPlus
114
+ # class SPARQL::Algebra::Operator::Alt
115
+ # ##
116
+ # # Retrieve the possibly newly assigned blank node subject to use for representing this operator.
117
+ # # @return [RDF::Node]
118
+ # attr_accessor :subject
119
+ #
120
+ # ##
121
+ # # Generate the SHACL representation of this operator
122
+ # # @return [RDF::Node]
123
+ # def each_statement(&block); end.
124
+ # end
125
+ attr_accessor :subject
126
+ def each_statement(&block)
127
+ @subject = RDF::Node.new
128
+ operands.each do |op|
129
+ obj = if op.respond_to?(:each_statement)
130
+ op.each_statement(&block)
131
+ op.subject
132
+ else
133
+ op
134
+ end
135
+ block.call(RDF::Statement(@subject, RDF::Vocab::SHACL.zeroOrMorePath, obj))
136
+ end
137
+ end
138
+ end
139
+
140
+ refine SPARQL::Algebra::Operator::Reverse do
141
+ # @!parse
142
+ # # Refinements on SPARQL::Algebra::Operator::Reverse
143
+ # class SPARQL::Algebra::Operator::Alt
144
+ # ##
145
+ # # Retrieve the possibly newly assigned blank node subject to use for representing this operator.
146
+ # # @return [RDF::Node]
147
+ # attr_accessor :subject
148
+ #
149
+ # ##
150
+ # # Generate the SHACL representation of this operator
151
+ # # @return [RDF::Node]
152
+ # def each_statement(&block); end.
153
+ # end
154
+ attr_accessor :subject
155
+ def each_statement(&block)
156
+ @subject = RDF::Node.new
157
+ operands.each do |op|
158
+ obj = if op.respond_to?(:each_statement)
159
+ op.each_statement(&block)
160
+ op.subject
161
+ else
162
+ op
163
+ end
164
+ block.call(RDF::Statement(@subject, RDF::Vocab::SHACL.inversePath, obj))
165
+ end
166
+ end
167
+ end
168
+
169
+ refine SPARQL::Algebra::Operator::Seq do
170
+ # @!parse
171
+ # # Refinements on SPARQL::Algebra::Operator::Seq
172
+ # class SPARQL::Algebra::Operator::Alt
173
+ # ##
174
+ # # Retrieve the possibly newly assigned blank node subject to use for representing this operator.
175
+ # # @return [RDF::Node]
176
+ # attr_accessor :subject
177
+ #
178
+ # ##
179
+ # # Generate the SHACL representation of this operator
180
+ # # @return [RDF::Node]
181
+ # def each_statement(&block); end.
182
+ # end
183
+ attr_accessor :subject
184
+ def each_statement(&block)
185
+ elements = operands.map do |op|
186
+ if op.respond_to?(:each_statement)
187
+ op.each_statement(&block)
188
+ op.subject
189
+ else
190
+ op
191
+ end
192
+ end
193
+ list = RDF::List(*elements)
194
+ list.each_statement(&block)
195
+ @subject = list.subject
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,160 @@
1
+ require_relative 'algebra'
2
+ require_relative 'validation_report'
3
+ require_relative 'context'
4
+ require 'json/ld'
5
+
6
+ module SHACL
7
+ ##
8
+ # The set of shapes loaded from a graph.
9
+ class Shapes < Array
10
+ include RDF::Util::Logger
11
+
12
+ # The graphs which have been loaded as shapes
13
+ #
14
+ # @return [Array<RDF::URI>]
15
+ attr_reader :loaded_graphs
16
+
17
+ # The JSON used to instantiate shapes
18
+ #
19
+ # @return [Array<Hash>]
20
+ attr_reader :shape_json
21
+
22
+ ##
23
+ # Initializes the shapes from `graph`loading `owl:imports` until all references are loaded.
24
+ #
25
+ # The shapes come from the following:
26
+ # * Instances of `sh:NodeShape` or `sh:PropertyShape`
27
+ # * resources that have any of the properties `sh:targetClass`, `sh:targetNode`, `sh:targetObjectsOf`, or `sh:targetSubjectsOf`.
28
+ #
29
+ # @param [RDF::Graph] graph
30
+ # @param [Array<RDF::URI>] loaded_graphs = []
31
+ # @param [Hash{Symbol => Object}] options
32
+ # @return [Shapes]
33
+ # @raise [SHACL::Error]
34
+ def self.from_graph(graph, loaded_graphs: [], **options)
35
+ @loded_graphs = loaded_graphs
36
+
37
+ import_count = 0
38
+ while (imports = graph.query(predicate: RDF::OWL.imports).map(&:object)).count > import_count
39
+ # Load each imported graph
40
+ imports.each do |ref|
41
+ graph.load(imports)
42
+ loaded_graphs << ref
43
+ import_count += 1
44
+ end
45
+ end
46
+
47
+ # Serialize the graph as framed JSON-LD and initialize patterns, recursively.
48
+ shape_json = JSON::LD::API.fromRdf(graph, useNativeTypes: true) do |expanded|
49
+ JSON::LD::API.frame(expanded, SHAPES_FRAME, omitGraph: false, embed: '@always', expanded: true)
50
+ end['@graph']
51
+
52
+ # Create an array of the framed shapes
53
+ shapes = self.new(shape_json.map {|o| Algebra.from_json(o, **options)})
54
+ shapes.instance_variable_set(:@shape_json, shape_json)
55
+ shapes
56
+ end
57
+
58
+ ##
59
+ # Retrieve shapes from a sh:shapesGraph reference within the queryable
60
+ #
61
+ # @param [RDF::Queryable] queryable
62
+ # The data graph which may contain references to the shapes graph
63
+ # @param [Hash{Symbol => Object}] options
64
+ # @return [Shapes]
65
+ # @raise [SHACL::Error]
66
+ def self.from_queryable(queryable, **options)
67
+ # Query queryable to find one ore more shapes graphs
68
+ graphs = queryable.query(predicate: RDF::Vocab::SHACL.shapesGraph).objects
69
+ graph = RDF::Graph.new do |g|
70
+ graphs.each {|iri| g.load(iri)}
71
+ end
72
+ from_graph(graph, loaded_graphs: graphs, **options)
73
+ end
74
+
75
+ ##
76
+ # Match on schema. Finds appropriate shape for node, and matches that shape.
77
+ #
78
+ # @param [RDF::Queryable] graph
79
+ # @return [Hash{RDF::Term => Array<ValidationResult>}] Returns _ValidationResults_, a hash of focus nodes to the results of their associated shapes
80
+ # @param [Hash{Symbol => Object}] options
81
+ # @option options [RDF::Term] :focus
82
+ # An explicit focus node, overriding any defined on the top-level shaps.
83
+ # @return [SHACL::ValidationReport]
84
+ def execute(graph, depth: 0, **options)
85
+ self.each do |shape|
86
+ shape.graph = graph
87
+ shape.each_descendant do |op|
88
+ op.graph = graph
89
+ end
90
+ end
91
+
92
+ # Execute all shapes against their target nodes
93
+ ValidationReport.new(self.map do |shape|
94
+ nodes = Array(options.fetch(:focus, shape.targetNodes))
95
+ nodes.map do |node|
96
+ shape.conforms(node, depth: depth + 1)
97
+ end
98
+ end.flatten)
99
+ end
100
+
101
+ def to_sxp_bin
102
+ [:shapes, super]
103
+ end
104
+
105
+ def to_sxp
106
+ to_sxp_bin.to_sxp
107
+ end
108
+
109
+ SHAPES_FRAME = JSON.parse(%({
110
+ "@context": {
111
+ "id": "@id",
112
+ "type": {"@id": "@type", "@container": "@set"},
113
+ "@vocab": "http://www.w3.org/ns/shacl#",
114
+ "rdfs": "http://www.w3.org/2000/01/rdf-schema#",
115
+ "shacl": "http://www.w3.org/ns/shacl#",
116
+ "sh": "http://www.w3.org/ns/shacl#",
117
+ "xsd": "http://www.w3.org/2001/XMLSchema#",
118
+ "and": {"@type": "@id", "@container": "@list"},
119
+ "annotationProperty": {"@type": "@id"},
120
+ "class": {"@type": "@id"},
121
+ "comment": "http://www.w3.org/2000/01/rdf-schema#comment",
122
+ "condition": {"@type": "@id"},
123
+ "datatype": {"@type": "@vocab"},
124
+ "declare": {"@type": "@id"},
125
+ "disjoint": {"@type": "@id"},
126
+ "entailment": {"@type": "@id"},
127
+ "equals": {"@type": "@id"},
128
+ "ignoredProperties": {"@type": "@id", "@container": "@list"},
129
+ "in": {"@type": "@none", "@container": "@list"},
130
+ "inversePath": {"@type": "@id"},
131
+ "label": "http://www.w3.org/2000/01/rdf-schema#label",
132
+ "languageIn": {"@container": "@list"},
133
+ "lessThan": {"@type": "@id"},
134
+ "lessThanOrEquals": {"@type": "@id"},
135
+ "nodeKind": {"@type": "@vocab"},
136
+ "or": {"@type": "@id", "@container": "@list"},
137
+ "path": {"@type": "@none"},
138
+ "property": {"@type": "@id"},
139
+ "severity": {"@type": "@vocab"},
140
+ "targetClass": {"@type": "@id"},
141
+ "targetNode": {"@type": "@none"},
142
+ "xone": {"@type": "@id", "@container": "@list"}
143
+ },
144
+ "and": {},
145
+ "class": {},
146
+ "datatype": {},
147
+ "in": {},
148
+ "node": {},
149
+ "nodeKind": {},
150
+ "not": {},
151
+ "or": {},
152
+ "property": {},
153
+ "targetClass": {},
154
+ "targetNode": {},
155
+ "targetObjectsOf": {},
156
+ "xone": {},
157
+ "targetSubjectsOf": {}
158
+ })).freeze
159
+ end
160
+ end