rdf-reasoner 0.2.2 → 0.3.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
2
  SHA1:
3
- metadata.gz: f1980413b4fbf92e655601081fbc493d9a5dcb77
4
- data.tar.gz: 545ce25a9e024691ee7b8d7e0166921bfa0f876a
3
+ metadata.gz: 82811d1af73f62458a8c0ae0b719ff6a0ea14a3f
4
+ data.tar.gz: 37ba1a731d2a5435e9fb61d0a2caa7b008b58d51
5
5
  SHA512:
6
- metadata.gz: e4e95ad17224135fee1f801455eaa802a277efb82227f9d4837f3eccdeeb6b5a3c7c5a4f9686ca6dbed50afd3ea67dd625d30150b2299ff15ac56d45fef4f883
7
- data.tar.gz: 19fb975b67c1e0aaff517e98f9513c42b6ca618272d8d862fbcc44b104ad62f33fc2442ae9d835ac70dc724bc24ba6d9ccd55ddc061e1d0e56798a781477ddd5
6
+ metadata.gz: 93b537c3d167f0cdd99d19163b40caa33a2a611e9b6a5f3550ef29d211472215cfaa5fabce00dbde74a3865ffd8a82f8c00394899e9a551ef9c5c3fadda46aba
7
+ data.tar.gz: facc4fbf521fb828f83488a13dd713b051576350fc959c4fd4493aeb954c4f96c294a0a1f6c4ce17a6489adf75b223d4ec6cd2947dbf7203ccb2bb4d66f53e93
data/README.md CHANGED
@@ -40,7 +40,7 @@ Domain and Range entailment include specific rules for schema.org vocabularies.
40
40
  require 'rdf/reasoner'
41
41
 
42
42
  RDF::Reasoner.apply(:rdfs)
43
- term = RDF::FOAF.Person
43
+ term = RDF::Vocab::FOAF.Person
44
44
  term.entail(:subClass) # => [foaf:Person, mo:SoloMusicArtist]
45
45
 
46
46
  ### Determine if a resource is compatible with the domains of a property
@@ -81,6 +81,22 @@ Domain and Range entailment include specific rules for schema.org vocabularies.
81
81
  graph = RDF::Graph.load("etc/doap.ttl")
82
82
  graph.enum_statement.entail.count # >= graph.enum_statement.count
83
83
 
84
+ ### Lint an expanded graph
85
+
86
+ require 'rdf/reasoner'
87
+ require 'rdf/turtle'
88
+
89
+ RDF::Reasoner.apply(:rdfs, :owl)
90
+ graph = RDF::Graph.load("etc/doap.ttl")
91
+ graph.entail!
92
+ messages = graph.lint
93
+ messages.each do |kind, term_messages|
94
+ term_messages.each do |term, messages|
95
+ options[:output].puts "#{kind} #{term}"
96
+ messages.each {|m| options[:output].puts " #{m}"}
97
+ end
98
+ end
99
+
84
100
  ## Dependencies
85
101
 
86
102
  * [Ruby](http://ruby-lang.org/) (>= 1.9.2)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.2
1
+ 0.3.0
data/lib/rdf/reasoner.rb CHANGED
@@ -41,6 +41,13 @@ module RDF
41
41
  end
42
42
  module_function :apply
43
43
 
44
+ ##
45
+ # Add all entailment regimes
46
+ def apply_all
47
+ apply(*%w(rdfs owl schema))
48
+ end
49
+ module_function :apply_all
50
+
44
51
  ##
45
52
  # A reasoner error
46
53
  class Error < RuntimeError; end
@@ -101,7 +101,9 @@ module RDF
101
101
  # For best results, either run rules separately expanding the enumberated graph, or run repeatedly until no new statements are added to the enumerable containing both original and entailed statements. As `:subClassOf` and `:subPropertyOf` entailments are implicitly recursive, this may not be necessary except for extreme cases.
102
102
  #
103
103
  # @overload entail
104
- # @param [Array<Symbol>] *rules Registered entailment method(s)
104
+ # @param [Array<Symbol>] *rules
105
+ # Registered entailment method(s).
106
+ #
105
107
  # @yield statement
106
108
  # @yieldparam [RDF::Statement] statement
107
109
  # @return [void]
@@ -111,7 +113,7 @@ module RDF
111
113
  # @return [Enumerator]
112
114
  def entail(*rules, &block)
113
115
  if block_given?
114
- rules = @@entailments.keys if rules.empty?
116
+ rules = %w(subClassOf subPropertyOf domain range).map(&:to_sym) if rules.empty?
115
117
 
116
118
  self.each do |statement|
117
119
  rules.each {|rule| statement.entail(rule, &block)}
@@ -141,7 +143,7 @@ module RDF
141
143
 
142
144
  # Return a new mutable, composed of original and entailed statements
143
145
  #
144
- # @param [Array<Symbol>] *rules Registered entailment method(s)
146
+ # @param [Array<Symbol>] rules Registered entailment method(s)
145
147
  # @return [RDF::Mutable]
146
148
  # @see [RDF::Enumerable#entail]
147
149
  def entail(*rules, &block)
@@ -150,11 +152,11 @@ module RDF
150
152
 
151
153
  # Add entailed statements to the mutable
152
154
  #
153
- # @param [Array<Symbol>] *rules Registered entailment method(s)
155
+ # @param [Array<Symbol>] rules Registered entailment method(s)
154
156
  # @return [RDF::Mutable]
155
157
  # @see [RDF::Enumerable#entail]
156
158
  def entail!(*rules, &block)
157
- rules = @@entailments.keys if rules.empty?
159
+ rules = %w(subClassOf subPropertyOf domain range).map(&:to_sym) if rules.empty?
158
160
  statements = []
159
161
 
160
162
  self.each do |statement|
@@ -164,8 +166,99 @@ module RDF
164
166
  end
165
167
  end
166
168
  end
167
- self.insert *statements
169
+ self.insert(*statements)
168
170
  self
169
171
  end
170
172
  end
173
+
174
+ module Queryable
175
+ # Lint a queryable, presuming that it has already had RDFS entailment expansion.
176
+ # @return [Hash{Symbol => Hash{Symbol => Array<String>}}] messages found for classes and properties by term
177
+ def lint
178
+ messages = {}
179
+
180
+ # Check for defined classes in known vocabularies
181
+ self.query(:predicate => RDF.type) do |stmt|
182
+ vocab = RDF::Vocabulary.find(stmt.object)
183
+ term = (RDF::Vocabulary.find_term(stmt.object) rescue nil) if vocab
184
+ pname = term ? term.pname : stmt.object.pname
185
+
186
+ # Must be a defined term, not in RDF or RDFS vocabularies
187
+ if term && term.class?
188
+ # Warn against using a deprecated term
189
+ superseded = term.attributes['schema:supersededBy']
190
+ (messages[:class] ||= {})[pname] = ["Term is superseded by #{superseded}"] if superseded
191
+ else
192
+ (messages[:class] ||= {})[pname] = ["No class definition found"] unless vocab.nil? || [RDF::RDFV, RDF::RDFS].include?(vocab)
193
+ end
194
+ end
195
+
196
+ # Check for defined predicates in known vocabularies and domain/range
197
+ resource_types = {}
198
+ self.each_statement do |stmt|
199
+ vocab = RDF::Vocabulary.find(stmt.predicate)
200
+ term = (RDF::Vocabulary.find_term(stmt.predicate) rescue nil) if vocab
201
+ pname = term ? term.pname : stmt.predicate.pname
202
+
203
+ # Must be a defined property
204
+ if term && term.property?
205
+ # Warn against using a deprecated term
206
+ superseded = term.attributes['schema:supersededBy']
207
+ (messages[:property] ||= {})[pname] = ["Term is superseded by #{superseded}"] if superseded
208
+ else
209
+ ((messages[:property] ||= {})[pname] ||= []) << "No property definition found" unless vocab.nil?
210
+ next
211
+ end
212
+
213
+ # See if type of the subject is in the domain of this predicate
214
+ resource_types[stmt.subject] ||= self.query(subject: stmt.subject, predicate: RDF.type).
215
+ map {|s| (t = (RDF::Vocabulary.find_term(s.object) rescue nil)) && t.entail(:subClassOf)}.
216
+ flatten.
217
+ uniq.
218
+ compact
219
+
220
+ unless term.domain_compatible?(stmt.subject, self, :types => resource_types[stmt.subject])
221
+ ((messages[:property] ||= {})[pname] ||= []) << if term.respond_to?(:domain)
222
+ "Subject #{show_resource(stmt.subject)} not compatible with domain (#{Array(term.domain).map {|d| d.pname|| d}.join(',')})"
223
+ else
224
+ "Subject #{show_resource(stmt.subject)} not compatible with domainIncludes (#{term.domainIncludes.map {|d| d.pname|| d}.join(',')})"
225
+ end
226
+ end
227
+
228
+ # Make sure that if ranges are defined, the object has an appropriate type
229
+ resource_types[stmt.object] ||= self.query(subject: stmt.object, predicate: RDF.type).
230
+ map {|s| (t = (RDF::Vocabulary.find_term(s.object) rescue nil)) && t.entail(:subClassOf)}.
231
+ flatten.
232
+ uniq.
233
+ compact if stmt.object.resource?
234
+
235
+ unless term.range_compatible?(stmt.object, self, :types => resource_types[stmt.object])
236
+ ((messages[:property] ||= {})[pname] ||= []) << if term.respond_to?(:range)
237
+ "Object #{show_resource(stmt.object)} not compatible with range (#{Array(term.range).map {|d| d.pname|| d}.join(',')})"
238
+ else
239
+ "Object #{show_resource(stmt.object)} not compatible with rangeIncludes (#{term.rangeIncludes.map {|d| d.pname|| d}.join(',')})"
240
+ end
241
+ end
242
+ end
243
+
244
+ messages[:class].each {|k, v| messages[:class][k] = v.uniq} if messages[:class]
245
+ messages[:property].each {|k, v| messages[:property][k] = v.uniq} if messages[:property]
246
+ messages
247
+ end
248
+
249
+ private
250
+
251
+ # Show resource in diagnostic output
252
+ def show_resource(resource)
253
+ if resource.node?
254
+ resource.to_ntriples + '(' +
255
+ self.query(subject: resource, predicate: RDF.type).
256
+ map {|s| s.object.uri? ? s.object.pname : s.object.to_ntriples}
257
+ .join(',') +
258
+ ')'
259
+ else
260
+ resource.to_ntriples
261
+ end
262
+ end
263
+ end
171
264
  end
@@ -61,6 +61,7 @@ module RDF::Reasoner
61
61
  statements << RDF::Statement.new(self.to_hash.merge(object: t))
62
62
  end
63
63
  end
64
+ #$stderr.puts("subClassf(#{self.predicate.pname}): #{statements.map(&:object).map {|r| r.respond_to?(:pname) ? r.pname : r.to_ntriples}}}")
64
65
  end
65
66
  statements.each {|s| yield s} if block_given?
66
67
  statements
@@ -128,6 +129,7 @@ module RDF::Reasoner
128
129
  term._entail_subPropertyOf do |t|
129
130
  statements << RDF::Statement.new(self.to_hash.merge(predicate: t))
130
131
  end
132
+ #$stderr.puts("subPropertyOf(#{self.predicate.pname}): #{statements.map(&:object).map {|r| r.respond_to?(:pname) ? r.pname : r.to_ntriples}}}")
131
133
  end
132
134
  statements.each {|s| yield s} if block_given?
133
135
  statements
@@ -147,6 +149,7 @@ module RDF::Reasoner
147
149
  statements << RDF::Statement.new(self.to_hash.merge(predicate: RDF.type, object: t))
148
150
  end
149
151
  end
152
+ #$stderr.puts("domain(#{self.predicate.pname}): #{statements.map(&:object).map {|r| r.respond_to?(:pname) ? r.pname : r.to_ntriples}}}")
150
153
  statements.each {|s| yield s} if block_given?
151
154
  statements
152
155
  else []
@@ -165,6 +168,7 @@ module RDF::Reasoner
165
168
  statements << RDF::Statement.new(self.to_hash.merge(subject: self.object, predicate: RDF.type, object: t))
166
169
  end
167
170
  end
171
+ #$stderr.puts("range(#{self.predicate.pname}): #{statements.map(&:object).map {|r| r.respond_to?(:pname) ? r.pname : r.to_ntriples}}")
168
172
  statements.each {|s| yield s} if block_given?
169
173
  statements
170
174
  else []
@@ -40,7 +40,7 @@ module RDF::Reasoner
40
40
 
41
41
  # Resource may still be acceptable if types include schema:Role, and any any other resource references `resource` using this property
42
42
  resource_acceptable ||
43
- types.include?(RDF::SCHEMA.Role) &&
43
+ types.include?(RDF::Vocab::SCHEMA.Role) &&
44
44
  !queryable.query(predicate: self, object: resource).empty?
45
45
  end
46
46
 
@@ -65,11 +65,11 @@ module RDF::Reasoner
65
65
  ranges.any? do |range|
66
66
  case range
67
67
  when RDF::RDFS.Literal then true
68
- when RDF::SCHEMA.Text then resource.plain? || resource.datatype == RDF::SCHEMA.Text
69
- when RDF::SCHEMA.Boolean
70
- [RDF::SCHEMA.Boolean, RDF::XSD.boolean].include?(resource.datatype) ||
68
+ when RDF::Vocab::SCHEMA.Text then resource.plain? || resource.datatype == RDF::Vocab::SCHEMA.Text
69
+ when RDF::Vocab::SCHEMA.Boolean
70
+ [RDF::Vocab::SCHEMA.Boolean, RDF::XSD.boolean].include?(resource.datatype) ||
71
71
  resource.plain? && RDF::Literal::Boolean.new(resource.value).valid?
72
- when RDF::SCHEMA.Date
72
+ when RDF::Vocab::SCHEMA.Date
73
73
  # Schema.org date based on ISO 8601, mapped to appropriate XSD types for validation
74
74
  case resource
75
75
  when RDF::Literal::Date, RDF::Literal::Time, RDF::Literal::DateTime, RDF::Literal::Duration
@@ -77,35 +77,35 @@ module RDF::Reasoner
77
77
  else
78
78
  ISO_8601.match(resource.value)
79
79
  end
80
- when RDF::SCHEMA.DateTime
81
- resource.datatype == RDF::SCHEMA.DateTime ||
80
+ when RDF::Vocab::SCHEMA.DateTime
81
+ resource.datatype == RDF::Vocab::SCHEMA.DateTime ||
82
82
  resource.is_a?(RDF::Literal::DateTime) ||
83
83
  resource.plain? && RDF::Literal::DateTime.new(resource.value).valid?
84
- when RDF::SCHEMA.Duration
84
+ when RDF::Vocab::SCHEMA.Duration
85
85
  value = resource.value
86
86
  value = "P#{value}" unless value.start_with?("P")
87
- resource.datatype == RDF::SCHEMA.Duration ||
87
+ resource.datatype == RDF::Vocab::SCHEMA.Duration ||
88
88
  resource.is_a?(RDF::Literal::Duration) ||
89
89
  resource.plain? && RDF::Literal::Duration.new(value).valid?
90
- when RDF::SCHEMA.Time
91
- resource.datatype == RDF::SCHEMA.Time ||
90
+ when RDF::Vocab::SCHEMA.Time
91
+ resource.datatype == RDF::Vocab::SCHEMA.Time ||
92
92
  resource.is_a?(RDF::Literal::Time) ||
93
93
  resource.plain? && RDF::Literal::Time.new(resource.value).valid?
94
- when RDF::SCHEMA.Number
94
+ when RDF::Vocab::SCHEMA.Number
95
95
  resource.is_a?(RDF::Literal::Numeric) ||
96
- [RDF::SCHEMA.Number, RDF::SCHEMA.Float, RDF::SCHEMA.Integer].include?(resource.datatype) ||
96
+ [RDF::Vocab::SCHEMA.Number, RDF::Vocab::SCHEMA.Float, RDF::Vocab::SCHEMA.Integer].include?(resource.datatype) ||
97
97
  resource.plain? && RDF::Literal::Integer.new(resource.value).valid? ||
98
98
  resource.plain? && RDF::Literal::Double.new(resource.value).valid?
99
- when RDF::SCHEMA.Float
99
+ when RDF::Vocab::SCHEMA.Float
100
100
  resource.is_a?(RDF::Literal::Double) ||
101
- [RDF::SCHEMA.Number, RDF::SCHEMA.Float].include?(resource.datatype) ||
101
+ [RDF::Vocab::SCHEMA.Number, RDF::Vocab::SCHEMA.Float].include?(resource.datatype) ||
102
102
  resource.plain? && RDF::Literal::Double.new(resource.value).valid?
103
- when RDF::SCHEMA.Integer
103
+ when RDF::Vocab::SCHEMA.Integer
104
104
  resource.is_a?(RDF::Literal::Integer) ||
105
- [RDF::SCHEMA.Number, RDF::SCHEMA.Integer].include?(resource.datatype) ||
105
+ [RDF::Vocab::SCHEMA.Number, RDF::Vocab::SCHEMA.Integer].include?(resource.datatype) ||
106
106
  resource.plain? && RDF::Literal::Integer.new(resource.value).valid?
107
- when RDF::SCHEMA.URL
108
- resource.datatype == RDF::SCHEMA.URL ||
107
+ when RDF::Vocab::SCHEMA.URL
108
+ resource.datatype == RDF::Vocab::SCHEMA.URL ||
109
109
  resource.datatype == RDF::XSD.anyURI ||
110
110
  resource.plain? && RDF::Literal::AnyURI.new(resource.value).valid?
111
111
  else
@@ -123,11 +123,11 @@ module RDF::Reasoner
123
123
  end
124
124
  end
125
125
  end
126
- elsif %w(True False).map {|v| RDF::SCHEMA[v]}.include?(resource) && ranges.include?(RDF::SCHEMA.Boolean)
126
+ elsif %w(True False).map {|v| RDF::Vocab::SCHEMA[v]}.include?(resource) && ranges.include?(RDF::Vocab::SCHEMA.Boolean)
127
127
  true # Special case for schema boolean resources
128
- elsif ranges.include?(RDF::SCHEMA.URL) && resource.uri?
128
+ elsif ranges.include?(RDF::Vocab::SCHEMA.URL) && resource.uri?
129
129
  true # schema:URL matches URI resources
130
- elsif ranges.include?(RDF::SCHEMA.Text) && resource.uri?
130
+ elsif ranges.include?(RDF::Vocab::SCHEMA.Text) && resource.uri?
131
131
  # Allowed if resource is untyped
132
132
  # Fully entailed types of the resource
133
133
  types = options.fetch(:types) do
@@ -157,7 +157,7 @@ module RDF::Reasoner
157
157
  resource_acceptable ||
158
158
 
159
159
  # Resource also acceptable if it is a Role, and the Role object contains the same predicate having a compatible object
160
- types.include?(RDF::SCHEMA.Role) &&
160
+ types.include?(RDF::Vocab::SCHEMA.Role) &&
161
161
  queryable.query(subject: resource, predicate: self).any? do |stmt|
162
162
  acc = self.range_compatible_schema?(stmt.object, queryable)
163
163
  acc
@@ -178,9 +178,9 @@ module RDF::Reasoner
178
178
  def literal_range?(ranges)
179
179
  ranges.all? do |range|
180
180
  case range
181
- when RDF::RDFS.Literal, RDF::SCHEMA.Text, RDF::SCHEMA.Boolean, RDF::SCHEMA.Date,
182
- RDF::SCHEMA.DateTime, RDF::SCHEMA.Time, RDF::SCHEMA.URL,
183
- RDF::SCHEMA.Number, RDF::SCHEMA.Float, RDF::SCHEMA.Integer
181
+ when RDF::RDFS.Literal, RDF::Vocab::SCHEMA.Text, RDF::Vocab::SCHEMA.Boolean, RDF::Vocab::SCHEMA.Date,
182
+ RDF::Vocab::SCHEMA.DateTime, RDF::Vocab::SCHEMA.Time, RDF::Vocab::SCHEMA.URL,
183
+ RDF::Vocab::SCHEMA.Number, RDF::Vocab::SCHEMA.Float, RDF::Vocab::SCHEMA.Integer
184
184
  true
185
185
  else
186
186
  # If this is an XSD range, look for appropriate literal
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rdf-reasoner
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.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: 2015-05-27 00:00:00.000000000 Z
11
+ date: 2015-10-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rdf
@@ -167,7 +167,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
167
167
  version: '0'
168
168
  requirements: []
169
169
  rubyforge_project:
170
- rubygems_version: 2.4.7
170
+ rubygems_version: 2.4.5.1
171
171
  signing_key:
172
172
  specification_version: 4
173
173
  summary: RDFS/OWL Reasoner for RDF.rb