module RDF::Queryable

Public Instance Methods

lint() click to toggle source

Lint a queryable, presuming that it has already had RDFS entailment expansion. @return [Hash{Symbol => Hash{Symbol => Array<String>}}] messages found for classes and properties by term

# 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.properties[:'http://schema.org/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.respond_to?(:property?) && term.property?
      # Warn against using a deprecated term
      superseded = term.properties[:'http://schema.org/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
        domains = Array(term.domainIncludes) +
                  Array(term.properties[:'https://schema.org/domainIncludes'])
        "Subject #{show_resource(stmt.subject)} not compatible with domainIncludes (#{domains.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
        ranges = Array(term.rangeIncludes) +
                  Array(term.properties[:'https://schema.org/rangeIncludes'])
        "Object #{show_resource(stmt.object)} not compatible with rangeIncludes (#{ranges.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

Private Instance Methods

show_resource(resource) click to toggle source

Show resource in diagnostic output

# File lib/rdf/reasoner/extensions.rb, line 320
def show_resource(resource)
  if resource.node?
    resource.to_ntriples + '(' +
      self.query({subject: resource, predicate: RDF.type}).
        map {|s| s.object.uri? ? s.object.pname : s.object.to_ntriples}
        .join(',') +
      ')'
  else
    resource.to_ntriples
  end
end