Module: RDF::Queryable

Defined in:
lib/rdf/reasoner/extensions.rb

Instance Method Summary collapse

Instance Method Details

#lintHash{Symbol => Hash{Symbol => Array<String>}}

Lint a queryable, presuming that it has already had RDFS entailment expansion.

Returns:

  • (Hash{Symbol => Hash{Symbol => Array<String>}})

    messages found for classes and properties by term



232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'lib/rdf/reasoner/extensions.rb', line 232

def lint
  messages = {}

  # Check for defined classes in known vocabularies
  self.query(predicate: RDF.type) do |stmt|
    vocab = RDF::Vocabulary.find(stmt.object)
    term = (RDF::Vocabulary.find_term(stmt.object) rescue nil) if vocab
    pname = term ? term.pname : stmt.object.pname
    
    # Must be a defined term, not in RDF or RDFS vocabularies
    if term && term.class?
      # Warn against using a deprecated term
      superseded = term.attributes[:'schema:supersededBy']
      superseded = superseded.pname if superseded.respond_to?(:pname)
      (messages[:class] ||= {})[pname] = ["Term is superseded by #{superseded}"] if superseded
    else
      (messages[:class] ||= {})[pname] = ["No class definition found"] unless vocab.nil? || [RDF::RDFV, RDF::RDFS].include?(vocab)
    end
  end

  # Check for defined predicates in known vocabularies and domain/range
  resource_types = {}
  self.each_statement do |stmt|
    vocab = RDF::Vocabulary.find(stmt.predicate)
    term = (RDF::Vocabulary.find_term(stmt.predicate) rescue nil) if vocab
    pname = term ? term.pname : stmt.predicate.pname

    # Must be a valid statement
    begin
      stmt.validate!
    rescue
      ((messages[:statement] ||= {})[pname] ||= []) << "Triple #{stmt.to_ntriples} is invalid"
    end

    # Must be a defined property
    if term && term.property?
      # Warn against using a deprecated term
      superseded = term.attributes[:'schema:supersededBy']
      superseded = superseded.pname if superseded.respond_to?(:pname)
      (messages[:property] ||= {})[pname] = ["Term is superseded by #{superseded}"] if superseded
    else
      ((messages[:property] ||= {})[pname] ||= []) << "No property definition found" unless vocab.nil?
      next
    end

    # See if type of the subject is in the domain of this predicate
    resource_types[stmt.subject] ||= self.query(subject: stmt.subject, predicate: RDF.type).
    map {|s| (t = (RDF::Vocabulary.find_term(s.object) rescue nil)) && t.entail(:subClassOf)}.
      flatten.
      uniq.
      compact

    unless term.domain_compatible?(stmt.subject, self, types: resource_types[stmt.subject])
      ((messages[:property] ||= {})[pname] ||= []) << if !term.domain.empty?
       "Subject #{show_resource(stmt.subject)} not compatible with domain (#{Array(term.domain).map {|d| d.pname|| d}.join(',')})"
      else
        "Subject #{show_resource(stmt.subject)} not compatible with domainIncludes (#{term.domainIncludes.map {|d| d.pname|| d}.join(',')})"
      end
    end

    # Make sure that if ranges are defined, the object has an appropriate type
    resource_types[stmt.object] ||= self.query(subject: stmt.object, predicate: RDF.type).
      map {|s| (t = (RDF::Vocabulary.find_term(s.object) rescue nil)) && t.entail(:subClassOf)}.
      flatten.
      uniq.
      compact if stmt.object.resource?

    unless term.range_compatible?(stmt.object, self, types: resource_types[stmt.object])
      ((messages[:property] ||= {})[pname] ||= []) << if !term.range.empty?
       "Object #{show_resource(stmt.object)} not compatible with range (#{Array(term.range).map {|d| d.pname|| d}.join(',')})"
      else
        "Object #{show_resource(stmt.object)} not compatible with rangeIncludes (#{term.rangeIncludes.map {|d| d.pname|| d}.join(',')})"
      end
    end
  end

  messages[:class].each {|k, v| messages[:class][k] = v.uniq} if messages[:class]
  messages[:property].each {|k, v| messages[:property][k] = v.uniq} if messages[:property]
  messages
end