rdf-reasoner 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7b64147d73a5e38f5925417e9f090176cc01dfa1
4
+ data.tar.gz: cd4660566c88ac7074beec70172b92a6fd220706
5
+ SHA512:
6
+ metadata.gz: a8eb0b42cbfae2278cf3fe2c3b15aa9c6a985b600e9ab8b79f4b7feb3c6adcd7e89da0df91a6d5717a41d01aa650672c3f6e243f16028f206ac5bb49ec54e6dc
7
+ data.tar.gz: 35f93debd3072386883f6cdda9761a8aac60158f008ddd058c07420dae06f8d5f2883b5dde1bbab1b72204ef0a58c30afc238e124dba305aed12cf1be88b01ef
data/AUTHORS ADDED
@@ -0,0 +1 @@
1
+ * Gregg Kellogg <gregg@greggkellogg.net>
data/README.md ADDED
@@ -0,0 +1,59 @@
1
+ # rdf-reasoner
2
+
3
+ Reasons over RDFS/OWL vocabularies and schema.org to generate statements which are entailed based on base RDFS/OWL rules along with vocabulary information. It can also be used to ask specific questions, such as if a given object is consistent with the vocabulary ruleset. This can be used to implement [SPARQL Entailment][] Regimes and [RDF Schema][] entailment.
4
+
5
+ ## Features
6
+
7
+ * Entail `rdfs:subClassOf` generating an array of terms which are ancestors of the subject.
8
+ * Entail `rdfs:subPropertyOf` generating an array of terms which are ancestors of the subject.
9
+ * Inverse `rdfs:subClassOf` entailment, to find descendant classes of the subject term.
10
+ * `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.
11
+ * `rangeCompatible?` determines if a particular resource is compatible with the range definition of a given predicate, based on the intersection of entailed subclasses or literal types with the property domain.
12
+
13
+ Domain and Range entailment include specific rules for schema.org vocabularies.
14
+
15
+ ## Dependencies
16
+
17
+ * [Ruby](http://ruby-lang.org/) (>= 1.9.2)
18
+ * [RDF.rb](http://rubygems.org/gems/rdf) (>= 1.1)
19
+
20
+ ## Mailing List
21
+
22
+ * <http://lists.w3.org/Archives/Public/public-rdf-ruby/>
23
+
24
+ ## Authors
25
+
26
+ * [Gregg Kellogg](http://github.com/gkellogg) - <http://greggkellogg.net/>
27
+
28
+ ## Contributing
29
+
30
+ * Do your best to adhere to the existing coding conventions and idioms.
31
+ * Don't use hard tabs, and don't leave trailing whitespace on any line.
32
+ Before committing, run `git diff --check` to make sure of this.
33
+ * Do document every method you add using [YARD][] annotations. Read the
34
+ [tutorial][YARD-GS] or just look at the existing code for examples.
35
+ * Don't touch the `.gemspec`, `VERSION` or `AUTHORS` files. If you need to
36
+ change them, do so on your private branch only.
37
+ * Do feel free to add yourself to the `CREDITS` file and the corresponding
38
+ list in the the `README`. Alphabetical order applies.
39
+ * Do note that in order for us to merge any non-trivial changes (as a rule
40
+ of thumb, additions larger than about 15 lines of code), we need an
41
+ explicit [public domain dedication][PDD] on record from you.
42
+
43
+ ## License
44
+
45
+ This is free and unencumbered public domain software. For more information,
46
+ see <http://unlicense.org/> or the accompanying {file:UNLICENSE} file.
47
+
48
+ [Ruby]: http://ruby-lang.org/
49
+ [RDF]: http://www.w3.org/RDF/
50
+ [YARD]: http://yardoc.org/
51
+ [YARD-GS]: http://rubydoc.info/docs/yard/file/docs/GettingStarted.md
52
+ [PDD]: http://lists.w3.org/Archives/Public/public-rdf-ruby/2010May/0013.html
53
+ [SPARQL]: http://en.wikipedia.org/wiki/SPARQL
54
+ [SPARQL Query]: http://www.w3.org/TR/2013/REC-sparql11-query-20130321/
55
+ [SPARQL Entailment]:http://www.w3.org/TR/sparql11-entailment/
56
+ [RDF 1.1]: http://www.w3.org/TR/rdf11-concepts
57
+ [RDF.rb]: http://rdf.rubyforge.org/
58
+ [RDF Schema]: http://www.w3.org/TR/rdf-schema/
59
+ [Rack]: http://rack.rubyforge.org/
data/UNLICENSE ADDED
@@ -0,0 +1,24 @@
1
+ This is free and unencumbered software released into the public domain.
2
+
3
+ Anyone is free to copy, modify, publish, use, compile, sell, or
4
+ distribute this software, either in source code form or as a compiled
5
+ binary, for any purpose, commercial or non-commercial, and by any
6
+ means.
7
+
8
+ In jurisdictions that recognize copyright laws, the author or authors
9
+ of this software dedicate any and all copyright interest in the
10
+ software to the public domain. We make this dedication for the benefit
11
+ of the public at large and to the detriment of our heirs and
12
+ successors. We intend this dedication to be an overt act of
13
+ relinquishment in perpetuity of all present and future rights to this
14
+ software under copyright law.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ For more information, please refer to <http://unlicense.org/>
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,29 @@
1
+ require 'rdf'
2
+ require 'rdf/reasoner/extensions'
3
+
4
+ module RDF
5
+ ##
6
+ # RDFS/OWL reasonsing for RDF.rb.
7
+ #
8
+ # @see http://www.w3.org/TR/2013/REC-sparql11-entailment-20130321/
9
+ # @author [Gregg Kellogg](http://greggkellogg.net/)
10
+ module Reasoner
11
+ autoload :OWL, 'rdf/reasoner/owl'
12
+ autoload :RDFS, 'rdf/reasoner/rdfs'
13
+ autoload :SCHEMA, 'rdf/reasoner/schema'
14
+ autoload :VERSION, 'rdf/reasoner/version'
15
+
16
+ ##
17
+ # Add entailment support for the specified regime
18
+ #
19
+ # @param [Array<:owl, :rdfs, :schema>] regime
20
+ def apply(*regime)
21
+ regime.each {|r| require "rdf/reasoner/#{r.downcase}"}
22
+ end
23
+ module_function :apply
24
+
25
+ ##
26
+ # A reasoner error
27
+ class Error < RuntimeError; end
28
+ end
29
+ end
@@ -0,0 +1,55 @@
1
+ # Extensions to RDF core classes to support reasoning
2
+ require 'rdf'
3
+
4
+ class RDF::Vocabulary::Term
5
+ class << self
6
+ @@entailments = {}
7
+
8
+ ##
9
+ # Add an entailment method. The method accepts no arguments, and returns an array of values associated with the particular entailment method
10
+ # @param [Symbol] method
11
+ # @param [Proc] proc
12
+ def add_entailment(method, proc)
13
+ @@entailments[method] = proc
14
+ end
15
+ end
16
+
17
+ ##
18
+ # Perform an entailment on this term. Entailments defined within this module are `:subClassOf`, `:subPropertyOf`, and `:subClass`.
19
+ #
20
+ # @param [Symbol] method A registered entailment method
21
+ # @return [Array<Term>]
22
+ def entail(method)
23
+ self.send @@entailments.fetch(method)
24
+ end
25
+
26
+ ##
27
+ # Determine if the domain of a property term is consistent with the specified resource in `queryable`.
28
+ #
29
+ # @param [RDF::Resource] resource
30
+ # @param [RDF::Queryable] queryable
31
+ # @param [Hash{Symbol => Object}] options ({})
32
+ # @option options [Array<RDF::Vocabulary::Term>] :types
33
+ # Fully entailed types of resource, if not provided, they are queried
34
+ def domain_compatible?(resource, queryable, options = {})
35
+ %w(owl rdfs schema).map {|r| "domain_compatible_#{r}?".to_sym}.all? do |meth|
36
+ !self.respond_to?(meth) || self.send(meth, resource, queryable, options)
37
+ end
38
+ end
39
+
40
+ ##
41
+ # Determine if the range of a property term is consistent with the specified resource in `queryable`.
42
+ #
43
+ # Specific entailment regimes should insert themselves before this to apply the appropriate semantic condition
44
+ #
45
+ # @param [RDF::Resource] resource
46
+ # @param [RDF::Queryable] queryable
47
+ # @param [Hash{Symbol => Object}] options ({})
48
+ # @option options [Array<RDF::Vocabulary::Term>] :types
49
+ # Fully entailed types of resource, if not provided, they are queried
50
+ def range_compatible?(resource, queryable, options = {})
51
+ %w(owl rdfs schema).map {|r| "range_compatible_#{r}?".to_sym}.all? do |meth|
52
+ !self.respond_to?(meth) || self.send(meth, resource, queryable, options)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,347 @@
1
+ # coding: utf-8
2
+
3
+ module RDF::Reasoner
4
+ ##
5
+ # Rules for generating OWL entailment triples
6
+ #
7
+ # Create instances for each owl:Class, owl:ObjectProperty, owl:DatatypeProperty, owl:DataType and owl:Restriction. This allows querying for querying specific entailed relationships of each instance.
8
+ class OWL
9
+ # Base class for OWL classes
10
+ class Base
11
+ # resource attribute, the IRI or BNode subject of the class
12
+ # #!attribute [r] resource
13
+ # @return [RDF::Resource]
14
+ attr_reader :resource
15
+
16
+ # RDF::Enumerable containing entity definition
17
+ #
18
+ # #!attribute [r] enumerable
19
+ # @return [RDF::Enumerable]
20
+ attr_reader :enumerable
21
+
22
+ class << self
23
+ # Class reader for all defined entities
24
+ # @!attribute [r] all
25
+ # @return [Array<Property>]
26
+ attr_reader :all
27
+ end
28
+
29
+ # Find instance based on this resource
30
+ # @param [RDF::Resource] resource
31
+ # @return [Base]
32
+ def self.find(resource)
33
+ all.detect {|r| r.resource == resource}
34
+ end
35
+
36
+ ##
37
+ # Create a new entity based on resource within enumerable
38
+ #
39
+ # @param [RDF::Resource] resource
40
+ # @param [RDF::Enumerable] enumerable
41
+ def initialize(resource, enumerable)
42
+ @resource, @enumerable = resource, enumerable
43
+ (Base.all ||= []) << self
44
+ end
45
+
46
+ # Human label of this class
47
+ # @return [String]
48
+ def label
49
+ @label || @resource.split(/\/\#/).last
50
+ end
51
+
52
+ # Infered ranges of the {Restriction} or {Property}
53
+ # FIXME: This does not account for intersection/union
54
+ # @return [Array<OwlClass,DataType>]
55
+ def ranges
56
+ values = @on_class || @all_values_from || @some_values_from || @on_data_range || @range
57
+ return [] unless values
58
+ values.map do |v|
59
+ v.one_of || v.union_of || ([v] + v.descendant_classes)
60
+ end.flatten
61
+ end
62
+
63
+ # Is this entity the same as `cls`, or is it a union containing `cls`?
64
+ # @param [OwlClass] cls
65
+ # @return [TrueClass, FalseClass]
66
+ def class_of?(cls)
67
+ cls == self or union_of.include?(cls)
68
+ end
69
+
70
+ # Override for actual descendant classes
71
+ # @return [Array]
72
+ def descendant_classes; []; end
73
+
74
+ # Is this a named entity (i.e., not some OWL construction)
75
+ # @return [TrueClass, FalseClass]
76
+ def named?
77
+ @resource.iri?
78
+ end
79
+
80
+ ##
81
+ # Accessors for entity fields
82
+ #
83
+ # @overload all_values_from
84
+ # @return [Array<Base>] reflects owl:allValuesFrom
85
+ # @overload _cardinality
86
+ # @return [Integer]
87
+ # reflects owl:cardinality and owl:qualifiedCardinality.
88
+ # @see {Restriction#cardinality}
89
+ # @overload equivalent_property
90
+ # @return [Array<Property>] reflects owl:equivalentProperty
91
+ # ...
92
+ def method_missing(meth, *args)
93
+ def _access(preds, how)
94
+ @access ||= {}
95
+ # Memoize result
96
+ @access[meth] ||= begin
97
+ values = []
98
+ preds.each do |pred|
99
+ enumerable.query(:subject => resource, :predicate => pred) do |statement|
100
+ values << statement.object
101
+ end
102
+ end
103
+ case how
104
+ when true
105
+ # Value is simply true
106
+ true
107
+ when :ary
108
+ # Translate IRIs into instances of Base
109
+ values.map {|v| Base.find(v) || v}
110
+ when :list
111
+ # Access as list and translate IRIs into instances of Base
112
+ RDF::List(values.first, enumerable).to_a.map {|v| Base.find(v) || v}
113
+ when :obj, :int
114
+ # Take first element of array, and optionally translate to integer
115
+ how == :int ? values.first.to_i : values.first
116
+ end
117
+ end
118
+ end
119
+
120
+ case :meth
121
+ when :all_values_from then _access([RDF::OWL::allValuesFrom], :ary)
122
+ when :_cardinality then _access([RDF::OWL::cardinality, RDF::OWL::qualifiedCardinality], :int)
123
+ when :domain then _access([RDF::RDFS::domain], :ary)
124
+ when :equivalent_property then _access([RDF::OWL::equivalentProperty], :ary)
125
+ when :has_self then _access([RDF::OWL::hasSelf], true)
126
+ when :has_value then _access([RDF::OWL::hasValue], :ary)
127
+ when :intersection_of then _access([RDF::OWL::hasSelf], true)
128
+ when :inverse_of then _access([RDF::OWL::inverseOf], :obj)
129
+ when :max_cardinality then _access([RDF::OWL::maxCardinality, RDF::OWL::maxQualifiedCardinality], :int)
130
+ when :min_cardinality then _access([RDF::OWL::minCardinality, RDF::OWL::minQualifiedCardinality], :int)
131
+ when :one_of then _access([RDF::OWL::oneOf], :list)
132
+ when :on_class then _access([RDF::OWL::onClass], :obj)
133
+ when :on_datarange then _access([RDF::OWL::onDatatype], :obj)
134
+ when :on_property then _access([RDF::OWL::onProperty], :obj)
135
+ when :range then _access([RDF::RDFS::range], :ary)
136
+ when :some_values_from then _access([RDF::OWL::someValuesFrom], :ary)
137
+ when :sub_class_of then _access([RDF::RDFS::subClassOf], :ary)
138
+ when :sub_property_of then _access([RDF::RDFS::subPropertyOf], :ary)
139
+ when :union_of then _access([RDF::OWL::unionOf], :list)
140
+ when :with_restrictions then _access([RDF::OWL::withRestrictions], :list)
141
+ else
142
+ super
143
+ end
144
+ end
145
+ end
146
+
147
+ # Entries for owl objects which are Object or Datatype Properties
148
+ class Property < Base
149
+ class << self
150
+ # Class reader for all defined properties
151
+ # @!attribute [r] all
152
+ # @return [Array<Property>]
153
+ attr_reader :all
154
+ end
155
+
156
+ ##
157
+ # Create a new property based on resource within enumerable
158
+ #
159
+ # @param [RDF::Resource] resource
160
+ # @param [RDF::Enumerable] enumerable
161
+ def initialize(resource, enumerable)
162
+ super
163
+ (Property.all ||= []) << self
164
+ end
165
+
166
+ # Infered domains of this property
167
+ # FIXME: does not account for intersection/union, which is uncomon in domains
168
+ # @return [Array<OwlClass>]
169
+ def domains
170
+ self.domain.map do |v|
171
+ v.one_of || v.union_of || ([v] + v.descendant_classes)
172
+ end.flatten.uniq
173
+ end
174
+
175
+
176
+ # Does this property have a domain including cls?
177
+ # The JSON is defined to always represent domain as an array
178
+ # FIXME: this doesn't deal with intersection
179
+ # @param [OwlClass] cls
180
+ # @return [TrueClass, FalseClass]
181
+ def domain_of?(cls)
182
+ domain.any? {|dom| dom.class_of?(cls)}
183
+ end
184
+ end
185
+
186
+
187
+ # OWL Restrictions are similar to classes. They impose a restriction on
188
+ # values of some property, such as cardinality
189
+ class Restriction < Base
190
+ # For restrictions, return any defined cardinality
191
+ # as an array of \[min, max\] where either max may be `nil`.
192
+ # Min will always be an integer.
193
+ # @return [Array<(Integer, Integer)>]
194
+ def cardinality
195
+ [(@cardinality || @min_cardinality || 0), (@cardinality || @max_cardinality)]
196
+ end
197
+ end
198
+
199
+ # Entries for owl objects wich are classes
200
+ class OwlClass < Base
201
+ class << self
202
+ # Class accessor for all defined classes
203
+ # @!attribute [r] all
204
+ # @return [Array<OwlClass>]
205
+ attr_reader :all
206
+ end
207
+
208
+ ##
209
+ # Create a new class based on resource within enumerable
210
+ #
211
+ # @param [RDF::Resource] resource
212
+ # @param [RDF::Enumerable] enumerable
213
+ def initialize(resource, enumerable)
214
+ super
215
+ (OwlClass.all ||= []) << self
216
+ end
217
+
218
+ # Return super classes as an S-Expression
219
+ # @return [Array<OwlClass>]
220
+ def super_classes
221
+ @super_class_cache ||= begin
222
+ # Note in super-class, that this is a direct sub-class
223
+ sup = self.sub_class_of
224
+
225
+ anded_classes = sup.map do |cls|
226
+ if cls.is_a?(OwlClass) and cls.named?
227
+ cls
228
+ elsif cls.unionOf
229
+ ored_classes = cls.union_of.select {|c2| c2.is_a?(OwlClass)}.compact
230
+ case ored_classes.length
231
+ when 0 then nil
232
+ when 1 then ored_classes.first
233
+ else (%w(|) + ored_classes).freeze
234
+ end
235
+ end
236
+ end.compact
237
+
238
+ case anded_classes.length
239
+ when 0 then [].freeze
240
+ when 1 then anded_classes.first
241
+ else (%w(&) + anded_classes).freeze
242
+ end
243
+ end
244
+ end
245
+
246
+ # Return all direct sub-classes
247
+ # This counts on each class having had superClasses calculated to
248
+ # inject the sub-class relation
249
+ # @return [Array<OwlClass>]
250
+ def sub_classes
251
+ @sub_classes_cache ||= OwlClass.all.select do |c|
252
+ c.sub_class_of.any? {|cl| cl.class_of?(self)}
253
+ end
254
+ end
255
+
256
+ # Return all descendant classes
257
+ # @return [Array<OwlClass>]
258
+ def descendant_classes
259
+ @descendant_classes ||= begin
260
+ (sub_classes + sub_classes.map {|cls| cls.descendant_classes}.flatten).compact.freeze
261
+ end
262
+ end
263
+
264
+ # Return a list of all property restrictions on this class and super-classes
265
+ # @return [Array<Restriction>]
266
+ def property_restrictions
267
+ @property_restrictions_cache ||= begin
268
+ restrictions = evaluate_sexp(self.super_classes) {|c| c.property_restrictions}.dup
269
+ restrictions = [restrictions].compact unless restrictions.is_a?(Array)
270
+ # Add restrictions defined on this class
271
+ self.sub_class_of.select {|r| r.is_a?(Restriction)}.each do |restriction|
272
+ prop = restriction.on_property
273
+ # Remove any existing restriction on the same property
274
+ restrictions.reject! {|r| r.on_property == prop}
275
+ #puts "#{resource}: add local restriction: #{restriction.on_property.resource}"
276
+ restrictions << restriction
277
+ end
278
+
279
+ restrictions.freeze
280
+ end
281
+ end
282
+
283
+ # Return a list of all properties on this class and super-classes
284
+ # @return [Array<Property>]
285
+ def properties
286
+ @properties_cache ||= begin
287
+ # Inherited properties
288
+ props = evaluate_sexp(super_classes) {|c| c.properties}
289
+
290
+ # Add properties directly referencing this class
291
+ direct_props = Property.all.select do |prop|
292
+ prop.domain_of?(self) and
293
+ !props.include?(prop)
294
+ end
295
+
296
+ (props + direct_props).sort.freeze
297
+ end
298
+ end
299
+
300
+ private
301
+ # Evaluate a subclass S-Expression
302
+ def evaluate_sexp(classes)
303
+ case classes
304
+ when OWL then yield classes
305
+ when Array
306
+ case classes.first
307
+ when '|' # Union of classes
308
+ cls = yield(classes[1]).dup
309
+ classes[2..-1].each {|cls2| cls = cls | yield(cls2)}
310
+ cls
311
+ when '&' # Intersection of classes
312
+ cls = yield(classes[1]).dup
313
+ classes[2..-1].each {|cls2| cls = cls & yield(cls2)}
314
+ cls
315
+ else
316
+ $logger.error "Unexpected operator in S-Exp for #{@resource}: #{classes.inspect}" unless classes.empty?
317
+ classes.dup
318
+ end
319
+ else
320
+ $logger.error "Unexpected value in S-Exp for #{@resource}: #{classes.inspect}" if classes
321
+ nil
322
+ end
323
+ end
324
+ end
325
+
326
+ # Entries for XSD and RDF datatypes
327
+ class DataType < Base
328
+ attr_accessor :rdf_literal_class
329
+
330
+ def initialize(object)
331
+ super
332
+ @rdf_literal_class = RDF::Literal
333
+ end
334
+
335
+ def validate(value)
336
+ if (rdf_literal_class.new(value).valid? rescue false)
337
+ case
338
+ when one_of
339
+ "#{value.inspect} must be one of #{one_of.inspect}" unless one_of.include?(value)
340
+ end
341
+ else
342
+ "#{value.inspect} is not a valid #{label}(#{resource})"
343
+ end
344
+ end
345
+ end
346
+ end
347
+ end
@@ -0,0 +1,160 @@
1
+ # coding: utf-8
2
+
3
+ module RDF::Reasoner
4
+ ##
5
+ # Rules for generating RDFS entailment triples
6
+ #
7
+ # Extends `RDF::Vocabulary::Term` with specific entailment capabilities
8
+ module RDFS
9
+ ##
10
+ # @return [RDF::Util::Cache]
11
+ # @private
12
+ def subClassOf_cache
13
+ @@subClassOf_cache ||= RDF::Util::Cache.new(-1)
14
+ end
15
+
16
+ ##
17
+ # @return [RDF::Util::Cache]
18
+ # @private
19
+ def subClass_cache
20
+ @@subClass_cache_cache ||= RDF::Util::Cache.new(-1)
21
+ end
22
+
23
+ ##
24
+ # @return [RDF::Util::Cache]
25
+ # @private
26
+ def descendant_cache
27
+ @@descendant_cache ||= RDF::Util::Cache.new(-1)
28
+ end
29
+
30
+ ##
31
+ # @return [RDF::Util::Cache]
32
+ # @private
33
+ def subPropertyOf_cache
34
+ @@subPropertyOf_cache ||= RDF::Util::Cache.new(-1)
35
+ end
36
+
37
+ ##
38
+ # 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
+ # @private
40
+ def _entail_subClassOf
41
+ raise RDF::Reasoner::Error, "#{self} Can't entail subClassOf" unless class?
42
+ subClassOf_cache[self] ||= begin
43
+ (Array(self.subClassOf).map {|c| c._entail_subClassOf rescue c}.flatten + Array(self)).compact
44
+ end
45
+ end
46
+
47
+ ##
48
+ # Return inferred subClass relationships by recursively applying to named sub classes to get a complete set of classes in the descendant chain of this class
49
+ # @private
50
+ def _entail_subClass
51
+ raise RDF::Reasoner::Error, "#{self} Can't entail subClass" unless class?
52
+ descendant_cache[self] ||= begin
53
+ (Array(self.subClass).map {|c| c._entail_subClass rescue c}.flatten + Array(self)).compact
54
+ end
55
+ end
56
+
57
+ ##
58
+ # Get the immediate subclasses of this class
59
+ # @return [Array<RDF::Vocabulary::Term>]
60
+ def subClass
61
+ raise RDF::Reasoner::Error, "#{self} Can't entail subClass" unless class?
62
+ subClass_cache[self] ||= ::RDF::Vocabulary.map do |v|
63
+ Array(v.properties).select {|p| p.class? && Array(p.subClassOf).include?(self)}
64
+ end.flatten.compact
65
+ end
66
+
67
+ ##
68
+ # Return inferred subPropertyOf relationships by recursively applying to named super classes to get a complete set of classes in the ancestor chain of this class
69
+ # @private
70
+ def _entail_subPropertyOf
71
+ raise RDF::Reasoner::Error, "#{self} Can't entail subPropertyOf" unless property?
72
+ subPropertyOf_cache[self] ||= begin
73
+ (Array(self.subPropertyOf).map {|c| c._entail_subPropertyOf rescue c}.flatten + Array(self)).compact
74
+ end
75
+ end
76
+
77
+ ##
78
+ # RDFS requires that if the property has a domain, and the resource has a type that some type matches every domain.
79
+ #
80
+ # Note that this is different than standard entailment, which simply asserts that the resource has every type in the domain, but this is more useful to check if published data is consistent with the vocabulary definition.
81
+ #
82
+ # @param [RDF::Resource] resource
83
+ # @param [RDF::Queryable] queryable
84
+ # @param [Hash{Symbol => Object}] options ({})
85
+ # @option options [Array<RDF::Vocabulary::Term>] :types
86
+ # Fully entailed types of resource, if not provided, they are queried
87
+ def domain_compatible_rdfs?(resource, queryable, options = {})
88
+ raise RDF::Reasoner::Error, "#{self} can't get domains" unless property?
89
+ if respond_to?(:domain)
90
+ domains = Array(self.domain) - [RDF::OWL.Thing]
91
+
92
+ # Fully entailed types of the resource
93
+ types = options.fetch(:types) do
94
+ queryable.query(:subject => resource, :predicate => RDF.type).
95
+ map {|s| (t = RDF::Vocabulary.find_term(s.object)) && t.entail(:subClassOf)}.
96
+ flatten.
97
+ uniq.
98
+ compact
99
+ end unless domains.empty?
100
+
101
+ # Every domain must match some entailed type
102
+ Array(types).empty? || domains.all? {|d| types.include?(d)}
103
+ else
104
+ true
105
+ end
106
+ end
107
+
108
+ ##
109
+ # RDFS requires that if the property has a range, and the resource has a type that some type matches every range. If the resource is a datatyped Literal, and the range includes a datatype, the resource must be consistent with that.
110
+ #
111
+ # Note that this is different than standard entailment, which simply asserts that the resource has every type in the range, but this is more useful to check if published data is consistent with the vocabulary definition.
112
+ #
113
+ # @param [RDF::Resource] resource
114
+ # @param [RDF::Queryable] queryable
115
+ # @param [Hash{Symbol => Object}] options ({})
116
+ # @option options [Array<RDF::Vocabulary::Term>] :types
117
+ # Fully entailed types of resource, if not provided, they are queried
118
+ def range_compatible_rdfs?(resource, queryable, options = {})
119
+ raise RDF::Reasoner::Error, "#{self} can't get ranges" unless property?
120
+ if respond_to?(:range) && !(ranges = Array(self.range) - [RDF::OWL.Thing]).empty?
121
+ if resource.literal?
122
+ ranges.all? do |range|
123
+ case range
124
+ when RDF::RDFS.Literal, RDF.XMLLiteral, RDF.HTML then true
125
+ else
126
+ if range.start_with?(RDF::XSD)
127
+ resource.datatype == range ||
128
+ resource.simple? && RDF::Literal::Boolean.new(resource.value).valid?
129
+ else
130
+ false
131
+ end
132
+ end
133
+ end
134
+ else
135
+ # Fully entailed types of the resource
136
+ types = options.fetch(:types) do
137
+ queryable.query(:subject => resource, :predicate => RDF.type).
138
+ map {|s| (t = RDF::Vocabulary.find_term(s.object)) && t.entail(:subClassOf)}.
139
+ flatten.
140
+ uniq.
141
+ compact
142
+ end
143
+ # Every range must match some entailed type
144
+ Array(types).empty? || ranges.all? {|d| types.include?(d)}
145
+ end
146
+ else
147
+ true
148
+ end
149
+ end
150
+
151
+ def self.included(mod)
152
+ mod.add_entailment :subClassOf, :_entail_subClassOf
153
+ mod.add_entailment :subClass, :_entail_subClass
154
+ mod.add_entailment :subPropertyOf, :_entail_subPropertyOf
155
+ end
156
+ end
157
+
158
+ # Extend the Term with this methods
159
+ ::RDF::Vocabulary::Term.send(:include, RDFS)
160
+ end
@@ -0,0 +1,149 @@
1
+ # coding: utf-8
2
+
3
+ # Also requires RDFS reasoner
4
+ require 'rdf/reasoner/rdfs'
5
+
6
+ module RDF::Reasoner
7
+ ##
8
+ # Rules for generating RDFS entailment triples
9
+ #
10
+ # Extends `RDF::Vocabulary::Term` with specific entailment capabilities
11
+ module SCHEMA
12
+ # domain_includes accessor
13
+ # @return [Array<RDF::Vocabulary::Term>]
14
+ def domain_includes
15
+ Array(@attributes["schema:domainIncludes"]).map {|v| RDF::Vocabulary.expand_pname(v)}
16
+ end
17
+ alias_method :domainIncludes, :domain_includes
18
+
19
+ # range_includes accessor
20
+ # @return [Array<RDF::Vocabulary::Term>]
21
+ def range_includes
22
+ Array(@attributes["schema:rangeIncludes"]).map {|v| RDF::Vocabulary.expand_pname(v)}
23
+ end
24
+ alias_method :rangeIncludes, :range_includes
25
+
26
+ ##
27
+ # Schema.org requires that if the property has a domain, and the resource has a type that some type matches some domain.
28
+ #
29
+ # Note that this is different than standard entailment, which simply asserts that the resource has every type in the domain, but this is more useful to check if published data is consistent with the vocabulary definition.
30
+ #
31
+ # @param [RDF::Resource] resource
32
+ # @param [RDF::Queryable] queryable
33
+ # @param [Hash{Symbol => Object}] options
34
+ # @option options [Array<RDF::Vocabulary::Term>] :types
35
+ # Fully entailed types of resource, if not provided, they are queried
36
+ def domain_compatible_schema?(resource, queryable, options = {})
37
+ raise RDF::Reasoner::Error, "#{self} can't get domains" unless property?
38
+ domains = Array(self.domainIncludes) - [RDF::OWL.Thing]
39
+
40
+ # Fully entailed types of the resource
41
+ types = options.fetch(:types) do
42
+ queryable.query(:subject => resource, :predicate => RDF.type).
43
+ map {|s| (t = RDF::Vocabulary.find_term(s.object)) && t.entail(:subClassOf)}.
44
+ flatten.
45
+ uniq.
46
+ compact
47
+ end unless domains.empty?
48
+
49
+ # Every domain must match some entailed type
50
+ Array(types).empty? || domains.any? {|d| types.include?(d)}
51
+ end
52
+
53
+ ##
54
+ # Schema.org requires that if the property has a range, and the resource has a type that some type matches some range. If the resource is a datatyped Literal, and the range includes a datatype, the resource must be consistent with that.
55
+ #
56
+ # Also, a plain literal (or schema:Text) is always compatible with an object range.
57
+ #
58
+ # @param [RDF::Resource] resource
59
+ # @param [RDF::Queryable] queryable
60
+ # @param [Hash{Symbol => Object}] options ({})
61
+ # @option options [Array<RDF::Vocabulary::Term>] :types
62
+ # Fully entailed types of resource, if not provided, they are queried
63
+ def range_compatible_schema?(resource, queryable, options = {})
64
+ raise RDF::Reasoner::Error, "#{self} can't get ranges" unless property?
65
+ if respond_to?(:rangeIncludes) && !(ranges = Array(self.rangeIncludes) - [RDF::OWL.Thing]).empty?
66
+ if resource.literal?
67
+ ranges.any? do |range|
68
+ case range
69
+ when RDF::RDFS.Literal then true
70
+ when RDF::SCHEMA.Text then resource.plain? || resource.datatype == RDF::SCHEMA.Text
71
+ when RDF::SCHEMA.Boolean
72
+ [RDF::SCHEMA.Boolean, RDF::XSD.boolean].include?(resource.datatype) ||
73
+ resource.simple? && RDF::Literal::Boolean.new(resource.value).valid?
74
+ when RDF::SCHEMA.Date
75
+ resource.datatype == RDF::SCHEMA.Date ||
76
+ resource.is_a?(RDF::Literal::Date) ||
77
+ resource.simple? && RDF::Literal::Date.new(resource.value).valid?
78
+ when RDF::SCHEMA.DateTime
79
+ resource.datatype == RDF::SCHEMA.DateTime ||
80
+ resource.is_a?(RDF::Literal::DateTime) ||
81
+ resource.simple? && RDF::Literal::DateTime.new(resource.value).valid?
82
+ when RDF::SCHEMA.Duration
83
+ value = resource.value
84
+ value = "P#{value}" unless value.start_with?("P")
85
+ resource.datatype == RDF::SCHEMA.Duration ||
86
+ resource.is_a?(RDF::Literal::Duration) ||
87
+ resource.simple? && RDF::Literal::Duration.new(value).valid?
88
+ when RDF::SCHEMA.Time
89
+ resource.datatype == RDF::SCHEMA.Time ||
90
+ resource.is_a?(RDF::Literal::Time) ||
91
+ resource.simple? && RDF::Literal::Time.new(resource.value).valid?
92
+ when RDF::SCHEMA.Number
93
+ resource.is_a?(RDF::Literal::Numeric) ||
94
+ [RDF::SCHEMA.Number, RDF::SCHEMA.Float, RDF::SCHEMA.Integer].include?(resource.datatype) ||
95
+ resource.simple? && RDF::Literal::Integer.new(resource.value).valid? ||
96
+ resource.simple? && RDF::Literal::Double.new(resource.value).valid?
97
+ when RDF::SCHEMA.Float
98
+ resource.is_a?(RDF::Literal::Double) ||
99
+ [RDF::SCHEMA.Number, RDF::SCHEMA.Float].include?(resource.datatype) ||
100
+ resource.simple? && RDF::Literal::Double.new(resource.value).valid?
101
+ when RDF::SCHEMA.Integer
102
+ resource.is_a?(RDF::Literal::Integer) ||
103
+ [RDF::SCHEMA.Number, RDF::SCHEMA.Integer].include?(resource.datatype) ||
104
+ resource.simple? && RDF::Literal::Integer.new(resource.value).valid?
105
+ when RDF::SCHEMA.URL
106
+ resource.datatype == RDF::SCHEMA.URL ||
107
+ resource.datatype == RDF::XSD.anyURI ||
108
+ resource.simple? && RDF::Literal::AnyURI.new(resource.value).valid?
109
+ else
110
+ # If this is an XSD range, look for appropriate literal
111
+ if range.start_with?(RDF::XSD.to_s)
112
+ if resource.datatype == RDF::URI(range)
113
+ true
114
+ else
115
+ # Valid if cast as datatype
116
+ resource.simple? && RDF::Literal(resource.value, :datatype => RDF::URI(range)).valid?
117
+ end
118
+ else
119
+ # Otherwise, presume that the range refers to a typed resource
120
+ false
121
+ end
122
+ end
123
+ end
124
+ elsif %w(True False).map {|v| RDF::SCHEMA[v]}.include?(resource) && ranges.include?(RDF::SCHEMA.Boolean)
125
+ true # Special case for schema boolean resources
126
+ else
127
+ # Fully entailed types of the resource
128
+ types = options.fetch(:types) do
129
+ queryable.query(:subject => resource, :predicate => RDF.type).
130
+ map {|s| (t = RDF::Vocabulary.find_term(s.object)) && t.entail(:subClassOf)}.
131
+ flatten.
132
+ uniq.
133
+ compact
134
+ end
135
+ # Every range must match some entailed type
136
+ Array(types).empty? || ranges.any? {|d| types.include?(d)}
137
+ end
138
+ else
139
+ true
140
+ end
141
+ end
142
+
143
+ def self.included(mod)
144
+ end
145
+ end
146
+
147
+ # Extend the Term with this methods
148
+ ::RDF::Vocabulary::Term.send(:include, SCHEMA)
149
+ end
@@ -0,0 +1,18 @@
1
+ module RDF::Reasoner::VERSION
2
+ VERSION_FILE = File.join(File.expand_path(File.dirname(__FILE__)), "..", "..", "..", "VERSION")
3
+ MAJOR, MINOR, TINY, EXTRA = File.read(VERSION_FILE).chop.split(".")
4
+
5
+ STRING = [MAJOR, MINOR, TINY, EXTRA].compact.join('.')
6
+
7
+ ##
8
+ # @return [String]
9
+ def self.to_s() STRING end
10
+
11
+ ##
12
+ # @return [String]
13
+ def self.to_str() STRING end
14
+
15
+ ##
16
+ # @return [Array(Integer, Integer, Integer)]
17
+ def self.to_a() STRING.split(".") end
18
+ end
metadata ADDED
@@ -0,0 +1,161 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rdf-reasoner
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Gregg Kellogg
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-06-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rdf
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.1'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.1.4
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '1.1'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 1.1.4
33
+ - !ruby/object:Gem::Dependency
34
+ name: rdf-xsd
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.1'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.1'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rdf-turtle
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.1'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.1'
61
+ - !ruby/object:Gem::Dependency
62
+ name: linkeddata
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '1.1'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '1.1'
75
+ - !ruby/object:Gem::Dependency
76
+ name: equivalent-xml
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '0.4'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '0.4'
89
+ - !ruby/object:Gem::Dependency
90
+ name: rspec
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '2.14'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '2.14'
103
+ - !ruby/object:Gem::Dependency
104
+ name: yard
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '0.8'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '0.8'
117
+ description: Reasons over RDFS/OWL vocabularies to generate statements which are entailed
118
+ based on base RDFS/OWL rules along with vocabulary information. It can also be used
119
+ to ask specific questions, such as if a given object is consistent with the vocabulary
120
+ ruleset. This can be used to implement SPARQL Entailment Regimes.
121
+ email: public-rdf-ruby@w3.org
122
+ executables: []
123
+ extensions: []
124
+ extra_rdoc_files: []
125
+ files:
126
+ - AUTHORS
127
+ - README.md
128
+ - UNLICENSE
129
+ - VERSION
130
+ - lib/rdf/reasoner.rb
131
+ - lib/rdf/reasoner/extensions.rb
132
+ - lib/rdf/reasoner/owl.rb
133
+ - lib/rdf/reasoner/rdfs.rb
134
+ - lib/rdf/reasoner/schema.rb
135
+ - lib/rdf/reasoner/version.rb
136
+ homepage: http://github.com/gkellogg/rdf-reasoner
137
+ licenses:
138
+ - Public Domain
139
+ metadata: {}
140
+ post_install_message:
141
+ rdoc_options: []
142
+ require_paths:
143
+ - lib
144
+ required_ruby_version: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: 1.9.3
149
+ required_rubygems_version: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ requirements: []
155
+ rubyforge_project:
156
+ rubygems_version: 2.2.2
157
+ signing_key:
158
+ specification_version: 4
159
+ summary: RDFS/OWL Reasoner for RDF.rb
160
+ test_files: []
161
+ has_rdoc: false