rdf-reasoner 0.4.4 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 377e84cbd7ab81ee861446c9918634bc97bcfe25
4
- data.tar.gz: b03ed57e310bc107a48923e391cef30344e64776
2
+ SHA256:
3
+ metadata.gz: 911fc7c40f3afc055e166946ff41a65d061cd08aa8b028401f85357b61b62617
4
+ data.tar.gz: 9702cd3e837fcc9abba925f154a40257a6d49cd8b28db990c6bd57147324049e
5
5
  SHA512:
6
- metadata.gz: 4d0aeeec319bf740256541783eee1514fea11ae98d3a3ae26863fb85357e79e3630b18f0ce3adc5faac42663b9b0c9d742bec1ff1b268f7c8152691125018cc4
7
- data.tar.gz: 6b8d37050cb611c36e70ea612542fdcf2b36375023bc5072f111f9a12387f9379b7131c7e06fb90243cd619f63a28826683fe5949cbb8e6dea6618df652a4639
6
+ metadata.gz: 827da1634fe249c21598824019f785d3efdbb8a94afad1dcf7d3c8852d87a41ec2a3ee59ced83d2705c33a480be719065caf0dc115104e290e0f28d44bec0088
7
+ data.tar.gz: 9d5010b729d25351bfec0efba6fd90c0789080a7a56fac8f829c3f56fc80fad712291acbd8c9a5eb728451c0c36077e8abe23ef7cd39e17d308934bcf9dcf5c2
data/README.md CHANGED
@@ -8,6 +8,7 @@ Reasons over RDFS/OWL vocabularies and schema.org to generate statements which a
8
8
  * Entail `rdfs:subPropertyOf` generating an array of terms which are ancestors of the subject.
9
9
  * Entail `rdfs:domain` and `rdfs:range` adding `rdf:type` assertions on the subject or object.
10
10
  * Inverse `rdfs:subClassOf` entailment, to find descendant classes of the subject term.
11
+ * Inverse `rdfs:subPropertyOf` entailment, to find descendant properties of the subject term.
11
12
  * Entail `owl:equivalentClass` generating an array of terms equivalent to the subject.
12
13
  * Entail `owl:equivalentProperty` generating an array of terms equivalent to the subject.
13
14
  * `domainCompatible?` determines if a particular resource is compatible with the domain definition of a given predicate, based on the intersection of entailed subclasses with the property domain.
@@ -51,7 +52,7 @@ Domain and Range entailment include specific rules for schema.org vocabularies.
51
52
 
52
53
  RDF::Reasoner.apply(:rdfs)
53
54
  graph = RDF::Graph.load("etc/doap.ttl")
54
- subj = RDF::URI("http://rubygems.org/gems/rdf-reasoner")
55
+ subj = RDF::URI("https://rubygems.org/gems/rdf-reasoner")
55
56
  RDF::Vocab::DOAP.name.domain_compatible?(subj, graph) # => true
56
57
 
57
58
  ### Determine if a resource is compatible with the ranges of a property
@@ -103,16 +104,16 @@ The `rdf` command-line interface is extended with `entail` and `lint` commands.
103
104
 
104
105
  ## Dependencies
105
106
 
106
- * [Ruby](http://ruby-lang.org/) (>= 2.2.2)
107
- * [RDF.rb](http://rubygems.org/gems/rdf) (>= 2.1.1)
107
+ * [Ruby](https://ruby-lang.org/) (>= 2.4)
108
+ * [RDF.rb](https://rubygems.org/gems/rdf) (~> 3.1)
108
109
 
109
110
  ## Mailing List
110
111
 
111
- * <http://lists.w3.org/Archives/Public/public-rdf-ruby/>
112
+ * <https://lists.w3.org/Archives/Public/public-rdf-ruby/>
112
113
 
113
114
  ## Authors
114
115
 
115
- * [Gregg Kellogg](http://github.com/gkellogg) - <http://greggkellogg.net/>
116
+ * [Gregg Kellogg](https://github.com/gkellogg) - <https://greggkellogg.net/>
116
117
 
117
118
  ## Contributing
118
119
 
@@ -132,17 +133,17 @@ The `rdf` command-line interface is extended with `entail` and `lint` commands.
132
133
  ## License
133
134
 
134
135
  This is free and unencumbered public domain software. For more information,
135
- see <http://unlicense.org/> or the accompanying {file:UNLICENSE} file.
136
-
137
- [Ruby]: http://ruby-lang.org/
138
- [RDF]: http://www.w3.org/RDF/
139
- [YARD]: http://yardoc.org/
140
- [YARD-GS]: http://rubydoc.info/docs/yard/file/docs/GettingStarted.md
141
- [PDD]: http://lists.w3.org/Archives/Public/public-rdf-ruby/2010May/0013.html
142
- [SPARQL]: http://en.wikipedia.org/wiki/SPARQL
143
- [SPARQL Query]: http://www.w3.org/TR/2013/REC-sparql11-query-20130321/
144
- [SPARQL Entailment]:http://www.w3.org/TR/sparql11-entailment/
145
- [RDF 1.1]: http://www.w3.org/TR/rdf11-concepts
146
- [RDF.rb]: http://www.rubydoc.info/github/ruby-rdf/rdf/
147
- [RDF Schema]: http://www.w3.org/TR/rdf-schema/
136
+ see <https://unlicense.org/> or the accompanying {file:UNLICENSE} file.
137
+
138
+ [Ruby]: https://ruby-lang.org/
139
+ [RDF]: https://www.w3.org/RDF/
140
+ [YARD]: https://yardoc.org/
141
+ [YARD-GS]: https://rubydoc.info/docs/yard/file/docs/GettingStarted.md
142
+ [PDD]: https://lists.w3.org/Archives/Public/public-rdf-ruby/2010May/0013.html
143
+ [SPARQL]: https://en.wikipedia.org/wiki/SPARQL
144
+ [SPARQL Query]: https://www.w3.org/TR/2013/REC-sparql11-query-20130321/
145
+ [SPARQL Entailment]:https://www.w3.org/TR/sparql11-entailment/
146
+ [RDF 1.1]: https://www.w3.org/TR/rdf11-concepts
147
+ [RDF.rb]: https://www.rubydoc.info/github/ruby-rdf/rdf/
148
+ [RDF Schema]: https://www.w3.org/TR/rdf-schema/
148
149
  [Rack]: https://rack.github.io/
data/UNLICENSE CHANGED
@@ -21,4 +21,4 @@ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
21
  ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
22
  OTHER DEALINGS IN THE SOFTWARE.
23
23
 
24
- For more information, please refer to <http://unlicense.org/>
24
+ For more information, please refer to <https://unlicense.org/>
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.4
1
+ 0.6.0
@@ -7,7 +7,7 @@ module RDF
7
7
  # RDFS/OWL reasonsing for RDF.rb.
8
8
  #
9
9
  # @see http://www.w3.org/TR/2013/REC-sparql11-entailment-20130321/
10
- # @author [Gregg Kellogg](http://greggkellogg.net/)
10
+ # @author [Gregg Kellogg](https://greggkellogg.net/)
11
11
  module Reasoner
12
12
  require 'rdf/reasoner/format'
13
13
  autoload :OWL, 'rdf/reasoner/owl'
@@ -2,7 +2,7 @@
2
2
  require 'rdf'
3
3
 
4
4
  module RDF
5
- class Vocabulary::Term
5
+ class URI
6
6
  class << self
7
7
  @@entailments = {}
8
8
 
@@ -34,9 +34,9 @@ module RDF
34
34
  # @param [Hash{Symbol => Object}] options ({})
35
35
  # @option options [Array<RDF::Vocabulary::Term>] :types
36
36
  # Fully entailed types of resource, if not provided, they are queried
37
- def domain_compatible?(resource, queryable, options = {})
37
+ def domain_compatible?(resource, queryable, **options)
38
38
  %w(owl rdfs schema).map {|r| "domain_compatible_#{r}?".to_sym}.all? do |meth|
39
- !self.respond_to?(meth) || self.send(meth, resource, queryable, options)
39
+ !self.respond_to?(meth) || self.send(meth, resource, queryable, **options)
40
40
  end
41
41
  end
42
42
 
@@ -50,9 +50,64 @@ module RDF
50
50
  # @param [Hash{Symbol => Object}] options ({})
51
51
  # @option options [Array<RDF::Vocabulary::Term>] :types
52
52
  # Fully entailed types of resource, if not provided, they are queried
53
- def range_compatible?(resource, queryable, options = {})
53
+ def range_compatible?(resource, queryable, **options)
54
54
  %w(owl rdfs schema).map {|r| "range_compatible_#{r}?".to_sym}.all? do |meth|
55
- !self.respond_to?(meth) || self.send(meth, resource, queryable, options)
55
+ !self.respond_to?(meth) || self.send(meth, resource, queryable, **options)
56
+ end
57
+ end
58
+ end
59
+
60
+ class Node
61
+ class << self
62
+ @@entailments = {}
63
+
64
+ ##
65
+ # Add an entailment method. The method accepts no arguments, and returns or yields an array of values associated with the particular entailment method
66
+ # @param [Symbol] method
67
+ # @param [Proc] proc
68
+ def add_entailment(method, proc)
69
+ @@entailments[method] = proc
70
+ end
71
+ end
72
+
73
+ ##
74
+ # Perform an entailment on this term.
75
+ #
76
+ # @param [Symbol] method A registered entailment method
77
+ # @yield term
78
+ # @yieldparam [Term] term
79
+ # @return [Array<Term>]
80
+ def entail(method, &block)
81
+ self.send(@@entailments.fetch(method), &block)
82
+ end
83
+
84
+ ##
85
+ # Determine if the domain of a property term is consistent with the specified resource in `queryable`.
86
+ #
87
+ # @param [RDF::Resource] resource
88
+ # @param [RDF::Queryable] queryable
89
+ # @param [Hash{Symbol => Object}] options ({})
90
+ # @option options [Array<RDF::Vocabulary::Term>] :types
91
+ # Fully entailed types of resource, if not provided, they are queried
92
+ def domain_compatible?(resource, queryable, **options)
93
+ %w(owl rdfs schema).map {|r| "domain_compatible_#{r}?".to_sym}.all? do |meth|
94
+ !self.respond_to?(meth) || self.send(meth, resource, queryable, **options)
95
+ end
96
+ end
97
+
98
+ ##
99
+ # Determine if the range of a property term is consistent with the specified resource in `queryable`.
100
+ #
101
+ # Specific entailment regimes should insert themselves before this to apply the appropriate semantic condition
102
+ #
103
+ # @param [RDF::Resource] resource
104
+ # @param [RDF::Queryable] queryable
105
+ # @param [Hash{Symbol => Object}] options ({})
106
+ # @option options [Array<RDF::Vocabulary::Term>] :types
107
+ # Fully entailed types of resource, if not provided, they are queried
108
+ def range_compatible?(resource, queryable, **options)
109
+ %w(owl rdfs schema).map {|r| "range_compatible_#{r}?".to_sym}.all? do |meth|
110
+ !self.respond_to?(meth) || self.send(meth, resource, queryable, **options)
56
111
  end
57
112
  end
58
113
  end
@@ -178,7 +233,7 @@ module RDF
178
233
  messages = {}
179
234
 
180
235
  # Check for defined classes in known vocabularies
181
- self.query(predicate: RDF.type) do |stmt|
236
+ self.query({predicate: RDF.type}) do |stmt|
182
237
  vocab = RDF::Vocabulary.find(stmt.object)
183
238
  term = (RDF::Vocabulary.find_term(stmt.object) rescue nil) if vocab
184
239
  pname = term ? term.pname : stmt.object.pname
@@ -187,6 +242,7 @@ module RDF
187
242
  if term && term.class?
188
243
  # Warn against using a deprecated term
189
244
  superseded = term.attributes[:'schema:supersededBy']
245
+ superseded = superseded.pname if superseded.respond_to?(:pname)
190
246
  (messages[:class] ||= {})[pname] = ["Term is superseded by #{superseded}"] if superseded
191
247
  else
192
248
  (messages[:class] ||= {})[pname] = ["No class definition found"] unless vocab.nil? || [RDF::RDFV, RDF::RDFS].include?(vocab)
@@ -211,6 +267,7 @@ module RDF
211
267
  if term && term.property?
212
268
  # Warn against using a deprecated term
213
269
  superseded = term.attributes[:'schema:supersededBy']
270
+ superseded = superseded.pname if superseded.respond_to?(:pname)
214
271
  (messages[:property] ||= {})[pname] = ["Term is superseded by #{superseded}"] if superseded
215
272
  else
216
273
  ((messages[:property] ||= {})[pname] ||= []) << "No property definition found" unless vocab.nil?
@@ -218,14 +275,14 @@ module RDF
218
275
  end
219
276
 
220
277
  # See if type of the subject is in the domain of this predicate
221
- resource_types[stmt.subject] ||= self.query(subject: stmt.subject, predicate: RDF.type).
278
+ resource_types[stmt.subject] ||= self.query({subject: stmt.subject, predicate: RDF.type}).
222
279
  map {|s| (t = (RDF::Vocabulary.find_term(s.object) rescue nil)) && t.entail(:subClassOf)}.
223
280
  flatten.
224
281
  uniq.
225
282
  compact
226
283
 
227
284
  unless term.domain_compatible?(stmt.subject, self, types: resource_types[stmt.subject])
228
- ((messages[:property] ||= {})[pname] ||= []) << if term.respond_to?(:domain)
285
+ ((messages[:property] ||= {})[pname] ||= []) << if !term.domain.empty?
229
286
  "Subject #{show_resource(stmt.subject)} not compatible with domain (#{Array(term.domain).map {|d| d.pname|| d}.join(',')})"
230
287
  else
231
288
  "Subject #{show_resource(stmt.subject)} not compatible with domainIncludes (#{term.domainIncludes.map {|d| d.pname|| d}.join(',')})"
@@ -233,14 +290,14 @@ module RDF
233
290
  end
234
291
 
235
292
  # Make sure that if ranges are defined, the object has an appropriate type
236
- resource_types[stmt.object] ||= self.query(subject: stmt.object, predicate: RDF.type).
293
+ resource_types[stmt.object] ||= self.query({subject: stmt.object, predicate: RDF.type}).
237
294
  map {|s| (t = (RDF::Vocabulary.find_term(s.object) rescue nil)) && t.entail(:subClassOf)}.
238
295
  flatten.
239
296
  uniq.
240
297
  compact if stmt.object.resource?
241
298
 
242
299
  unless term.range_compatible?(stmt.object, self, types: resource_types[stmt.object])
243
- ((messages[:property] ||= {})[pname] ||= []) << if term.respond_to?(:range)
300
+ ((messages[:property] ||= {})[pname] ||= []) << if !term.range.empty?
244
301
  "Object #{show_resource(stmt.object)} not compatible with range (#{Array(term.range).map {|d| d.pname|| d}.join(',')})"
245
302
  else
246
303
  "Object #{show_resource(stmt.object)} not compatible with rangeIncludes (#{term.rangeIncludes.map {|d| d.pname|| d}.join(',')})"
@@ -259,7 +316,7 @@ module RDF
259
316
  def show_resource(resource)
260
317
  if resource.node?
261
318
  resource.to_ntriples + '(' +
262
- self.query(subject: resource, predicate: RDF.type).
319
+ self.query({subject: resource, predicate: RDF.type}).
263
320
  map {|s| s.object.uri? ? s.object.pname : s.object.to_ntriples}
264
321
  .join(',') +
265
322
  ')'
@@ -5,7 +5,7 @@ module RDF::Reasoner
5
5
  # @example Obtaining an LD Patch format class
6
6
  # RDF::Format.for(:reasoner) #=> RDF::Reasoner::Format
7
7
  #
8
- # @see http://www.w3.org/TR/ldpatch/
8
+ # @see https://www.w3.org/TR/ldpatch/
9
9
  class Format < RDF::Format
10
10
 
11
11
  ##
@@ -4,7 +4,7 @@ module RDF::Reasoner
4
4
  ##
5
5
  # Rules for generating OWL entailment triples
6
6
  #
7
- # Extends `RDF::Vocabulary::Term` and `RDF::Statement` with specific entailment capabilities
7
+ # Extends `RDF::URI` and `RDF::Statement` with specific entailment capabilities
8
8
  module OWL
9
9
  ##
10
10
  # @return [RDF::Util::Cache]
@@ -26,8 +26,8 @@ module RDF::Reasoner
26
26
  # @private
27
27
  def _entail_equivalentClass
28
28
  case self
29
- when RDF::Vocabulary::Term
30
- unless class? && respond_to?(:equivalentClass)
29
+ when RDF::URI, RDF::Node
30
+ unless class?
31
31
  yield self if block_given?
32
32
  return Array(self)
33
33
  end
@@ -50,7 +50,7 @@ module RDF::Reasoner
50
50
  if self.predicate == RDF.type
51
51
  if term = (RDF::Vocabulary.find_term(self.object) rescue nil)
52
52
  term._entail_equivalentClass do |t|
53
- statements << RDF::Statement(self.to_h.merge(object: t, inferred: true))
53
+ statements << RDF::Statement(**self.to_h.merge(object: t, inferred: true))
54
54
  end
55
55
  end
56
56
  end
@@ -66,8 +66,8 @@ module RDF::Reasoner
66
66
  # @private
67
67
  def _entail_equivalentProperty
68
68
  case self
69
- when RDF::Vocabulary::Term
70
- unless property? && respond_to?(:equivalentProperty)
69
+ when RDF::URI, RDF::Node
70
+ unless property?
71
71
  yield self if block_given?
72
72
  return Array(self)
73
73
  end
@@ -89,7 +89,7 @@ module RDF::Reasoner
89
89
  statements = []
90
90
  if term = (RDF::Vocabulary.find_term(self.predicate) rescue nil)
91
91
  term._entail_equivalentProperty do |t|
92
- statements << RDF::Statement(self.to_h.merge(predicate: t, inferred: true))
92
+ statements << RDF::Statement(**self.to_h.merge(predicate: t, inferred: true))
93
93
  end
94
94
  end
95
95
  statements.each {|s| yield s} if block_given?
@@ -98,38 +98,14 @@ module RDF::Reasoner
98
98
  end
99
99
  end
100
100
 
101
- ##
102
- # EquivalentClass of this term, also populates reverse equivalents.
103
- #
104
- # When first called, this initializes a cache of reverse terms to terms where the the reverse term is listed as an equivalent of the original term.
105
- #
106
- # It returns the list of terms which are equivalent to this term however defined.
107
- # @return [Array<RDF::Vocabulary::Term>]
108
- def equivalentClass
109
- raise RDF::Reasoner::Error, "#{self} Can't entail equivalentClass" unless class?
110
- Array(self.attributes[:"owl:equivalentClass"]).map {|t| RDF::Vocabulary.expand_pname(t)}
111
- end
112
-
113
- ##
114
- # EquivalentProperty of this term, also populates reverse equivalents.
115
- #
116
- # When first called, this initializes a cache of reverse terms to terms where the the reverse term is listed as an equivalent of the original term.
117
- #
118
- # It returns the list of terms which are equivalent to this term however defined.
119
- # @return [Array<RDF::Vocabulary::Term>]
120
- def equivalentProperty
121
- raise RDF::Reasoner::Error, "#{self} Can't entail equivalentProperty" unless property?
122
- Array(self.attributes[:"owl:equivalentProperty"]).map {|t| RDF::Vocabulary.expand_pname(t)}
123
- end
124
-
125
101
  def self.included(mod)
126
102
  mod.add_entailment :equivalentClass, :_entail_equivalentClass
127
103
  mod.add_entailment :equivalentProperty, :_entail_equivalentProperty
128
104
  end
129
105
  end
130
106
 
131
- # Extend Term with these methods
132
- ::RDF::Vocabulary::Term.send(:include, OWL)
107
+ # Extend URI with these methods
108
+ ::RDF::URI.send(:include, OWL)
133
109
 
134
110
  # Extend Statement with these methods
135
111
  ::RDF::Statement.send(:include, OWL)
@@ -4,7 +4,7 @@ module RDF::Reasoner
4
4
  ##
5
5
  # Rules for generating RDFS entailment triples
6
6
  #
7
- # Extends `RDF::Vocabulary::Term` and `RDF::Statement` with specific entailment capabilities
7
+ # Extends `RDF::URI` and `RDF::Statement` with specific entailment capabilities
8
8
  module RDFS
9
9
  ##
10
10
  # @return [RDF::Util::Cache]
@@ -34,14 +34,29 @@ module RDF::Reasoner
34
34
  @@subPropertyOf_cache ||= RDF::Util::Cache.new(-1)
35
35
  end
36
36
 
37
+ ##
38
+ # @return [RDF::Util::Cache]
39
+ # @private
40
+ def subProperty_cache
41
+ @@subProperty_cache ||= RDF::Util::Cache.new(-1)
42
+ end
43
+
44
+ ##
45
+ # @return [RDF::Util::Cache]
46
+ # @private
47
+ def descendant_property_cache
48
+ @@descendant_property_cache ||= RDF::Util::Cache.new(-1)
49
+ end
50
+
37
51
  ##
38
52
  # For a Term: yield or return inferred subClassOf relationships by recursively applying to named super classes to get a complete set of classes in the ancestor chain of this class
39
53
  # For a Statement: if predicate is `rdf:types`, yield or return inferred statements having a subClassOf relationship to the type of this statement
54
+ # @todo Should be able to entail owl:Restriction, which is a BNode. This should be allowed, and also add BNode values of that node, recursively, similar to SPARQL concise_bounded_description.uu
40
55
  # @private
41
56
  def _entail_subClassOf
42
57
  case self
43
- when RDF::Vocabulary::Term
44
- unless class? && respond_to?(:subClassOf)
58
+ when RDF::URI, RDF::Node
59
+ unless class?
45
60
  yield self if block_given?
46
61
  return Array(self)
47
62
  end
@@ -58,7 +73,8 @@ module RDF::Reasoner
58
73
  if self.predicate == RDF.type
59
74
  if term = (RDF::Vocabulary.find_term(self.object) rescue nil)
60
75
  term._entail_subClassOf do |t|
61
- statements << RDF::Statement(self.to_h.merge(object: t, inferred: true))
76
+ next if t.node? # Don't entail BNodes
77
+ statements << RDF::Statement(**self.to_h.merge(object: t, inferred: true))
62
78
  end
63
79
  end
64
80
  #$stderr.puts("subClassf(#{self.predicate.pname}): #{statements.map(&:object).map {|r| r.respond_to?(:pname) ? r.pname : r.to_ntriples}}}")
@@ -75,7 +91,7 @@ module RDF::Reasoner
75
91
  # @private
76
92
  def _entail_subClass
77
93
  case self
78
- when RDF::Vocabulary::Term
94
+ when RDF::URI, RDF::Node
79
95
  unless class?
80
96
  yield self if block_given?
81
97
  return Array(self)
@@ -110,8 +126,8 @@ module RDF::Reasoner
110
126
  # @private
111
127
  def _entail_subPropertyOf
112
128
  case self
113
- when RDF::Vocabulary::Term
114
- unless property? && respond_to?(:subPropertyOf)
129
+ when RDF::URI, RDF::Node
130
+ unless property?
115
131
  yield self if block_given?
116
132
  return Array(self)
117
133
  end
@@ -127,7 +143,7 @@ module RDF::Reasoner
127
143
  statements = []
128
144
  if term = (RDF::Vocabulary.find_term(self.predicate) rescue nil)
129
145
  term._entail_subPropertyOf do |t|
130
- statements << RDF::Statement(self.to_h.merge(predicate: t, inferred: true))
146
+ statements << RDF::Statement(**self.to_h.merge(predicate: t, inferred: true))
131
147
  end
132
148
  #$stderr.puts("subPropertyOf(#{self.predicate.pname}): #{statements.map(&:object).map {|r| r.respond_to?(:pname) ? r.pname : r.to_ntriples}}}")
133
149
  end
@@ -137,8 +153,53 @@ module RDF::Reasoner
137
153
  end
138
154
  end
139
155
 
156
+ ##
157
+ # For a Term: yield or return inferred subProperty relationships
158
+ # by recursively applying to named subproperties to get a complete
159
+ # set of properties in the descendant chain of this property
160
+ #
161
+ # For a Statement: this is a no-op, as it's not useful in this context
162
+ # @private
163
+
164
+ def _entail_subProperty
165
+ case self
166
+ when RDF::URI, RDF::Node
167
+ unless property?
168
+ yield self if block_given?
169
+ return Array(self)
170
+ end
171
+
172
+ terms = descendant_property_cache[self] ||= (
173
+ Array(self.subProperty).map do |c|
174
+ c._entail_subProperty rescue c
175
+ end.flatten + Array(self)).compact
176
+
177
+ terms.each {|t| yield t } if block_given?
178
+ terms
179
+ else []
180
+ end
181
+ end
182
+
183
+ ##
184
+ # Get the immediate subproperties of this property.
185
+ #
186
+ # This iterates over terms defined in the vocabulary of this term,
187
+ # as well as the vocabularies imported by this vocabulary.
188
+ # @return [Array<RDF::Vocabulary::Term>]
189
+ def subProperty
190
+ raise RDF::Reasoner::Error,
191
+ "#{self} Can't entail subProperty" unless property?
192
+ vocabs = [self.vocab] + self.vocab.imported_from
193
+ subProperty_cache[self] ||= vocabs.map do |v|
194
+ Array(v.properties).select do |p|
195
+ p.property? && Array(p.subPropertyOf).include?(self)
196
+ end
197
+ end.flatten.compact
198
+ end
199
+
140
200
  ##
141
201
  # For a Statement: yield or return inferred statements having an rdf:type of the domain of the statement predicate
202
+ # @todo Should be able to entail owl:unionOf, which is a BNode. This should be allowed, and also add BNode values of that node, recursively, similar to SPARQL concise_bounded_description.uu
142
203
  # @private
143
204
  def _entail_domain
144
205
  case self
@@ -146,7 +207,8 @@ module RDF::Reasoner
146
207
  statements = []
147
208
  if term = (RDF::Vocabulary.find_term(self.predicate) rescue nil)
148
209
  term.domain.each do |t|
149
- statements << RDF::Statement(self.to_h.merge(predicate: RDF.type, object: t, inferred: true))
210
+ next if t.node? # Don't entail BNodes
211
+ statements << RDF::Statement(**self.to_h.merge(predicate: RDF.type, object: t, inferred: true))
150
212
  end
151
213
  end
152
214
  #$stderr.puts("domain(#{self.predicate.pname}): #{statements.map(&:object).map {|r| r.respond_to?(:pname) ? r.pname : r.to_ntriples}}}")
@@ -158,6 +220,7 @@ module RDF::Reasoner
158
220
 
159
221
  ##
160
222
  # For a Statement: if object is a resource, yield or return inferred statements having an rdf:type of the range of the statement predicate
223
+ # @todo Should be able to entail owl:unionOf, which is a BNode. This should be allowed, and also add BNode values of that node, recursively, similar to SPARQL concise_bounded_description.uu
161
224
  # @private
162
225
  def _entail_range
163
226
  case self
@@ -165,7 +228,8 @@ module RDF::Reasoner
165
228
  statements = []
166
229
  if object.resource? && term = (RDF::Vocabulary.find_term(self.predicate) rescue nil)
167
230
  term.range.each do |t|
168
- statements << RDF::Statement(self.to_h.merge(subject: self.object, predicate: RDF.type, object: t, inferred: true))
231
+ next if t.node? # Don't entail BNodes
232
+ statements << RDF::Statement(**self.to_h.merge(subject: self.object, predicate: RDF.type, object: t, inferred: true))
169
233
  end
170
234
  end
171
235
  #$stderr.puts("range(#{self.predicate.pname}): #{statements.map(&:object).map {|r| r.respond_to?(:pname) ? r.pname : r.to_ntriples}}")
@@ -185,25 +249,21 @@ module RDF::Reasoner
185
249
  # @param [Hash{Symbol => Object}] options ({})
186
250
  # @option options [Array<RDF::Vocabulary::Term>] :types
187
251
  # Fully entailed types of resource, if not provided, they are queried
188
- def domain_compatible_rdfs?(resource, queryable, options = {})
252
+ def domain_compatible_rdfs?(resource, queryable, **options)
189
253
  raise RDF::Reasoner::Error, "#{self} can't get domains" unless property?
190
- if respond_to?(:domain)
191
- domains = Array(self.domain) - [RDF::OWL.Thing, RDF::RDFS.Resource]
254
+ domains = Array(self.domain).reject(&:node?) - [RDF::OWL.Thing, RDF::RDFS.Resource]
192
255
 
193
- # Fully entailed types of the resource
194
- types = options.fetch(:types) do
195
- queryable.query(subject: resource, predicate: RDF.type).
196
- map {|s| (t = (RDF::Vocabulary.find_term(s.object)) rescue nil) && t.entail(:subClassOf)}.
197
- flatten.
198
- uniq.
199
- compact
200
- end unless domains.empty?
256
+ # Fully entailed types of the resource
257
+ types = options.fetch(:types) do
258
+ queryable.query({subject: resource, predicate: RDF.type}).
259
+ map {|s| (t = (RDF::Vocabulary.find_term(s.object)) rescue nil) && t.entail(:subClassOf)}.
260
+ flatten.
261
+ uniq.
262
+ compact
263
+ end unless domains.empty?
201
264
 
202
- # Every domain must match some entailed type
203
- Array(types).empty? || domains.all? {|d| types.include?(d)}
204
- else
205
- true
206
- end
265
+ # Every domain must match some entailed type
266
+ Array(types).empty? || domains.all? {|d| types.include?(d)}
207
267
  end
208
268
 
209
269
  ##
@@ -216,9 +276,9 @@ module RDF::Reasoner
216
276
  # @param [Hash{Symbol => Object}] options ({})
217
277
  # @option options [Array<RDF::Vocabulary::Term>] :types
218
278
  # Fully entailed types of resource, if not provided, they are queried
219
- def range_compatible_rdfs?(resource, queryable, options = {})
279
+ def range_compatible_rdfs?(resource, queryable, **options)
220
280
  raise RDF::Reasoner::Error, "#{self} can't get ranges" unless property?
221
- if respond_to?(:range) && !(ranges = Array(self.range) - [RDF::OWL.Thing, RDF::RDFS.Resource]).empty?
281
+ if !(ranges = Array(self.range).reject(&:node?) - [RDF::OWL.Thing, RDF::RDFS.Resource]).empty?
222
282
  if resource.literal?
223
283
  ranges.all? do |range|
224
284
  if [RDF::RDFS.Literal, RDF.XMLLiteral, RDF.HTML].include?(range)
@@ -277,7 +337,7 @@ module RDF::Reasoner
277
337
  else
278
338
  # Fully entailed types of the resource
279
339
  types = options.fetch(:types) do
280
- queryable.query(subject: resource, predicate: RDF.type).
340
+ queryable.query({subject: resource, predicate: RDF.type}).
281
341
  map {|s| (t = (RDF::Vocabulary.find_term(s.object) rescue nil)) && t.entail(:subClassOf)}.
282
342
  flatten.
283
343
  uniq.
@@ -301,13 +361,14 @@ module RDF::Reasoner
301
361
  mod.add_entailment :subClassOf, :_entail_subClassOf
302
362
  mod.add_entailment :subClass, :_entail_subClass
303
363
  mod.add_entailment :subPropertyOf, :_entail_subPropertyOf
364
+ mod.add_entailment :subProperty, :_entail_subProperty
304
365
  mod.add_entailment :domain, :_entail_domain
305
366
  mod.add_entailment :range, :_entail_range
306
367
  end
307
368
  end
308
369
 
309
- # Extend Term with these methods
310
- ::RDF::Vocabulary::Term.send(:include, RDFS)
370
+ # Extend URI with these methods
371
+ ::RDF::URI.send(:include, RDFS)
311
372
 
312
373
  # Extend Statement with these methods
313
374
  ::RDF::Statement.send(:include, RDFS)
@@ -317,4 +378,4 @@ module RDF::Reasoner
317
378
 
318
379
  # Extend Mutable with these methods
319
380
  ::RDF::Mutable.send(:include, RDFS)
320
- end
381
+ end
@@ -7,7 +7,7 @@ module RDF::Reasoner
7
7
  ##
8
8
  # Rules for generating RDFS entailment triples
9
9
  #
10
- # Extends `RDF::Vocabulary::Term` with specific entailment capabilities
10
+ # Extends `RDF::URI` with specific entailment capabilities
11
11
  module Schema
12
12
 
13
13
  ##
@@ -22,12 +22,12 @@ module RDF::Reasoner
22
22
  # @param [Hash{Symbol => Object}] options
23
23
  # @option options [Array<RDF::Vocabulary::Term>] :types
24
24
  # Fully entailed types of resource, if not provided, they are queried
25
- def domain_compatible_schema?(resource, queryable, options = {})
25
+ def domain_compatible_schema?(resource, queryable, **options)
26
26
  raise RDF::Reasoner::Error, "#{self} can't get domains" unless property?
27
27
  domains = Array(self.domainIncludes) - [RDF::OWL.Thing]
28
28
 
29
29
  # Fully entailed types of the resource
30
- types = entailed_types(resource, queryable, options) unless domains.empty?
30
+ types = entailed_types(resource, queryable, **options) unless domains.empty?
31
31
 
32
32
  # Every domain must match some entailed type
33
33
  resource_acceptable = Array(types).empty? || domains.any? {|d| types.include?(d)}
@@ -35,7 +35,7 @@ module RDF::Reasoner
35
35
  # Resource may still be acceptable if types include schema:Role, and any any other resource references `resource` using this property
36
36
  resource_acceptable ||
37
37
  types.include?(RDF::Vocab::SCHEMA.Role) &&
38
- !queryable.query(predicate: self, object: resource).empty?
38
+ !queryable.query({predicate: self, object: resource}).empty?
39
39
  end
40
40
 
41
41
  ##
@@ -52,9 +52,9 @@ module RDF::Reasoner
52
52
  # @param [Hash{Symbol => Object}] options ({})
53
53
  # @option options [Array<RDF::Vocabulary::Term>] :types
54
54
  # Fully entailed types of resource, if not provided, they are queried
55
- def range_compatible_schema?(resource, queryable, options = {})
55
+ def range_compatible_schema?(resource, queryable, **options)
56
56
  raise RDF::Reasoner::Error, "#{self} can't get ranges" unless property?
57
- if respond_to?(:rangeIncludes) && !(ranges = Array(self.rangeIncludes) - [RDF::OWL.Thing]).empty?
57
+ if !(ranges = Array(self.rangeIncludes) - [RDF::OWL.Thing]).empty?
58
58
  if resource.literal?
59
59
  ranges.any? do |range|
60
60
  case range
@@ -123,12 +123,12 @@ module RDF::Reasoner
123
123
  true # schema:URL matches URI resources
124
124
  elsif ranges == [RDF::Vocab::SCHEMA.Text] && resource.uri?
125
125
  # Allowed if resource is untyped
126
- entailed_types(resource, queryable, options).empty?
126
+ entailed_types(resource, queryable, **options).empty?
127
127
  elsif literal_range?(ranges)
128
128
  false # If resource isn't literal, this is a range violation
129
129
  else
130
130
  # Fully entailed types of the resource
131
- types = entailed_types(resource, queryable, options)
131
+ types = entailed_types(resource, queryable, **options)
132
132
 
133
133
  # Every range must match some entailed type
134
134
  resource_acceptable = Array(types).empty? || ranges.any? {|d| types.include?(d)}
@@ -138,7 +138,7 @@ module RDF::Reasoner
138
138
 
139
139
  # Resource also acceptable if it is a Role, and the Role object contains the same predicate having a compatible object
140
140
  types.include?(RDF::Vocab::SCHEMA.Role) &&
141
- queryable.query(subject: resource, predicate: self).any? do |stmt|
141
+ queryable.query({subject: resource, predicate: self}).any? do |stmt|
142
142
  acc = self.range_compatible_schema?(stmt.object, queryable)
143
143
  acc
144
144
  end ||
@@ -174,9 +174,9 @@ module RDF::Reasoner
174
174
 
175
175
  private
176
176
  # Fully entailed types
177
- def entailed_types(resource, queryable, options = {})
177
+ def entailed_types(resource, queryable, **options)
178
178
  options.fetch(:types) do
179
- queryable.query(subject: resource, predicate: RDF.type).
179
+ queryable.query({subject: resource, predicate: RDF.type}).
180
180
  map {|s| (t = (RDF::Vocabulary.find_term(s.object) rescue nil)) && t.entail(:subClassOf)}.
181
181
  flatten.
182
182
  uniq.
@@ -185,6 +185,6 @@ module RDF::Reasoner
185
185
  end
186
186
  end
187
187
 
188
- # Extend the Term with this methods
189
- ::RDF::Vocabulary::Term.send(:include, Schema)
188
+ # Extend URI with this methods
189
+ ::RDF::URI.send(:include, Schema)
190
190
  end
metadata CHANGED
@@ -1,135 +1,99 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rdf-reasoner
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.4
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gregg Kellogg
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-12-14 00:00:00.000000000 Z
11
+ date: 2019-12-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rdf
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '2.2'
20
- - - "<"
17
+ - - "~>"
21
18
  - !ruby/object:Gem::Version
22
- version: '4.0'
19
+ version: '3.1'
23
20
  type: :runtime
24
21
  prerelease: false
25
22
  version_requirements: !ruby/object:Gem::Requirement
26
23
  requirements:
27
- - - ">="
28
- - !ruby/object:Gem::Version
29
- version: '2.2'
30
- - - "<"
24
+ - - "~>"
31
25
  - !ruby/object:Gem::Version
32
- version: '4.0'
26
+ version: '3.1'
33
27
  - !ruby/object:Gem::Dependency
34
28
  name: rdf-vocab
35
29
  requirement: !ruby/object:Gem::Requirement
36
30
  requirements:
37
- - - ">="
38
- - !ruby/object:Gem::Version
39
- version: '2.2'
40
- - - "<"
31
+ - - "~>"
41
32
  - !ruby/object:Gem::Version
42
- version: '4.0'
33
+ version: '3.1'
43
34
  type: :runtime
44
35
  prerelease: false
45
36
  version_requirements: !ruby/object:Gem::Requirement
46
37
  requirements:
47
- - - ">="
48
- - !ruby/object:Gem::Version
49
- version: '2.2'
50
- - - "<"
38
+ - - "~>"
51
39
  - !ruby/object:Gem::Version
52
- version: '4.0'
40
+ version: '3.1'
53
41
  - !ruby/object:Gem::Dependency
54
42
  name: rdf-xsd
55
43
  requirement: !ruby/object:Gem::Requirement
56
44
  requirements:
57
- - - ">="
58
- - !ruby/object:Gem::Version
59
- version: '2.2'
60
- - - "<"
45
+ - - "~>"
61
46
  - !ruby/object:Gem::Version
62
- version: '4.0'
47
+ version: '3.1'
63
48
  type: :runtime
64
49
  prerelease: false
65
50
  version_requirements: !ruby/object:Gem::Requirement
66
51
  requirements:
67
- - - ">="
68
- - !ruby/object:Gem::Version
69
- version: '2.2'
70
- - - "<"
52
+ - - "~>"
71
53
  - !ruby/object:Gem::Version
72
- version: '4.0'
54
+ version: '3.1'
73
55
  - !ruby/object:Gem::Dependency
74
56
  name: rdf-spec
75
57
  requirement: !ruby/object:Gem::Requirement
76
58
  requirements:
77
- - - ">="
78
- - !ruby/object:Gem::Version
79
- version: '2.2'
80
- - - "<"
59
+ - - "~>"
81
60
  - !ruby/object:Gem::Version
82
- version: '4.0'
61
+ version: '3.1'
83
62
  type: :development
84
63
  prerelease: false
85
64
  version_requirements: !ruby/object:Gem::Requirement
86
65
  requirements:
87
- - - ">="
88
- - !ruby/object:Gem::Version
89
- version: '2.2'
90
- - - "<"
66
+ - - "~>"
91
67
  - !ruby/object:Gem::Version
92
- version: '4.0'
68
+ version: '3.1'
93
69
  - !ruby/object:Gem::Dependency
94
- name: json-ld
70
+ name: rdf-turtle
95
71
  requirement: !ruby/object:Gem::Requirement
96
72
  requirements:
97
- - - ">="
98
- - !ruby/object:Gem::Version
99
- version: '2.1'
100
- - - "<"
73
+ - - "~>"
101
74
  - !ruby/object:Gem::Version
102
- version: '4.0'
75
+ version: '3.1'
103
76
  type: :development
104
77
  prerelease: false
105
78
  version_requirements: !ruby/object:Gem::Requirement
106
79
  requirements:
107
- - - ">="
108
- - !ruby/object:Gem::Version
109
- version: '2.1'
110
- - - "<"
80
+ - - "~>"
111
81
  - !ruby/object:Gem::Version
112
- version: '4.0'
82
+ version: '3.1'
113
83
  - !ruby/object:Gem::Dependency
114
- name: rdf-turtle
84
+ name: json-ld
115
85
  requirement: !ruby/object:Gem::Requirement
116
86
  requirements:
117
- - - ">="
118
- - !ruby/object:Gem::Version
119
- version: '2.2'
120
- - - "<"
87
+ - - "~>"
121
88
  - !ruby/object:Gem::Version
122
- version: '4.0'
89
+ version: '3.1'
123
90
  type: :development
124
91
  prerelease: false
125
92
  version_requirements: !ruby/object:Gem::Requirement
126
93
  requirements:
127
- - - ">="
128
- - !ruby/object:Gem::Version
129
- version: '2.2'
130
- - - "<"
94
+ - - "~>"
131
95
  - !ruby/object:Gem::Version
132
- version: '4.0'
96
+ version: '3.1'
133
97
  - !ruby/object:Gem::Dependency
134
98
  name: equivalent-xml
135
99
  requirement: !ruby/object:Gem::Requirement
@@ -150,28 +114,28 @@ dependencies:
150
114
  requirements:
151
115
  - - "~>"
152
116
  - !ruby/object:Gem::Version
153
- version: '3.5'
117
+ version: '3.9'
154
118
  type: :development
155
119
  prerelease: false
156
120
  version_requirements: !ruby/object:Gem::Requirement
157
121
  requirements:
158
122
  - - "~>"
159
123
  - !ruby/object:Gem::Version
160
- version: '3.5'
124
+ version: '3.9'
161
125
  - !ruby/object:Gem::Dependency
162
126
  name: yard
163
127
  requirement: !ruby/object:Gem::Requirement
164
128
  requirements:
165
129
  - - "~>"
166
130
  - !ruby/object:Gem::Version
167
- version: '0.9'
131
+ version: 0.9.20
168
132
  type: :development
169
133
  prerelease: false
170
134
  version_requirements: !ruby/object:Gem::Requirement
171
135
  requirements:
172
136
  - - "~>"
173
137
  - !ruby/object:Gem::Version
174
- version: '0.9'
138
+ version: 0.9.20
175
139
  description: Reasons over RDFS/OWL vocabularies to generate statements which are entailed
176
140
  based on base RDFS/OWL rules along with vocabulary information. It can also be used
177
141
  to ask specific questions, such as if a given object is consistent with the vocabulary
@@ -192,7 +156,7 @@ files:
192
156
  - lib/rdf/reasoner/rdfs.rb
193
157
  - lib/rdf/reasoner/schema.rb
194
158
  - lib/rdf/reasoner/version.rb
195
- homepage: http://github.com/gkellogg/rdf-reasoner
159
+ homepage: https://github.com/ruby-rdf/rdf-reasoner
196
160
  licenses:
197
161
  - Unlicense
198
162
  metadata: {}
@@ -204,15 +168,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
204
168
  requirements:
205
169
  - - ">="
206
170
  - !ruby/object:Gem::Version
207
- version: 2.2.2
171
+ version: '2.4'
208
172
  required_rubygems_version: !ruby/object:Gem::Requirement
209
173
  requirements:
210
174
  - - ">="
211
175
  - !ruby/object:Gem::Version
212
176
  version: '0'
213
177
  requirements: []
214
- rubyforge_project:
215
- rubygems_version: 2.6.14
178
+ rubygems_version: 3.0.6
216
179
  signing_key:
217
180
  specification_version: 4
218
181
  summary: RDFS/OWL Reasoner for RDF.rb