Module: RDF::Reasoner::RDFS

Defined in:
vendor/bundler/ruby/3.3.0/bundler/gems/rdf-reasoner-e77a80426b61/lib/rdf/reasoner/rdfs.rb

Overview

Rules for generating RDFS entailment triples

Extends RDF::URI and RDF::Statement with specific entailment capabilities

Instance Method Summary collapse

Instance Method Details

#domain_compatible_rdfs?(resource, queryable, options = {}) ⇒ Boolean

RDFS requires that if the property has a domain, and the resource has a type that some type matches every domain.

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.

Parameters:

Options Hash (options):

Returns:

  • (Boolean)

Raises:



252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-reasoner-e77a80426b61/lib/rdf/reasoner/rdfs.rb', line 252

def domain_compatible_rdfs?(resource, queryable, options = {})
  raise RDF::Reasoner::Error, "#{self} can't get domains" unless property?
  domains = Array(self.domain).reject(&:node?) - [RDF::OWL.Thing, RDF::RDFS.Resource]

  # Fully entailed types of the resource
  types = options.fetch(:types) do
    queryable.query({subject: resource, predicate: RDF.type}).
      map {|s| (t = (RDF::Vocabulary.find_term(s.object)) rescue nil) && t.entail(:subClassOf)}.
      flatten.
      uniq.
      compact
  end unless domains.empty?

  # Every domain must match some entailed type
  Array(types).empty? || domains.all? {|d| types.include?(d)}
end

#range_compatible_rdfs?(resource, queryable, options = {}) ⇒ Boolean

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.

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.

Parameters:

Options Hash (options):

Returns:

  • (Boolean)

Raises:



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
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-reasoner-e77a80426b61/lib/rdf/reasoner/rdfs.rb', line 279

def range_compatible_rdfs?(resource, queryable, options = {})
  raise RDF::Reasoner::Error, "#{self} can't get ranges" unless property?
  if !(ranges = Array(self.range).reject(&:node?) - [RDF::OWL.Thing, RDF::RDFS.Resource]).empty?
    if resource.literal?
      ranges.all? do |range|
        if [RDF::RDFS.Literal, RDF.XMLLiteral, RDF.HTML].include?(range)
          true  # Don't bother checking for validity
        elsif range == RDF.langString
          # Value must have a language
          resource.has_language?
        elsif range.start_with?(RDF::XSD)
          # XSD types are valid if the datatype matches, or they are plain and valid according to the grammar of the range
            resource.datatype == range ||
            resource.plain? && RDF::Literal.new(resource.value, datatype: range).valid?
        elsif range.start_with?("http://ogp.me/ns/class#")
          case range
          when RDF::URI("http://ogp.me/ns/class#boolean_str")
            [RDF::URI("http://ogp.me/ns/class#boolean_str"), RDF::XSD.boolean].include?(resource.datatype) ||
            resource.plain? && RDF::Literal::Boolean.new(resource.value).valid?
          when RDF::URI("http://ogp.me/ns/class#date_time_str")
            # Schema.org date based on ISO 8601, mapped to appropriate XSD types for validation
            case resource
            when RDF::Literal::Date, RDF::Literal::Time, RDF::Literal::DateTime, RDF::Literal::Duration
              resource.valid?
            else
              ISO_8601.match(resource.value)
            end
          when RDF::URI("http://ogp.me/ns/class#determiner_str")
            # The lexical space: "", "the", "a", "an", and "auto".
            resource.plain? && (%w(the a an auto) + [""]).include?(resource.value)
          when RDF::URI("http://ogp.me/ns/class#float_str")
            # A string representation of a 64-bit signed floating point number.  Example lexical values include "1.234", "-1.234", "1.2e3", "-1.2e3", and "7E-10".
            [RDF::URI("http://ogp.me/ns/class#float_str"), RDF::Literal::Double, RDF::Literal::Float].include?(resource.datatype) ||
            resource.plain? && RDF::Literal::Double.new(resource.value).valid?
          when RDF::URI("http://ogp.me/ns/class#integer_str")
            resource.is_a?(RDF::Literal::Integer) ||
            [RDF::URI("http://ogp.me/ns/class#integer_str")].include?(resource.datatype) ||
            resource.plain? && RDF::Literal::Integer.new(resource.value).valid?
          when RDF::URI("http://ogp.me/ns/class#mime_type_str")
            # Valid mime type strings \(e.g., "application/mp3"\).
            [RDF::URI("http://ogp.me/ns/class#mime_type_str")].include?(resource.datatype) ||
            resource.plain? && resource.value =~ %r(^[\w\-\+]+/[\w\-\+]+$)
          when RDF::URI("http://ogp.me/ns/class#string")
            resource.plain?
          when RDF::URI("http://ogp.me/ns/class#url")
            # A string of Unicode characters forming a valid URL having the http or https scheme.
            u = RDF::URI(resource.value)
            resource.datatype == RDF::URI("http://ogp.me/ns/class#url") ||
            resource.datatype == RDF::XSD.anyURI ||
            resource.simple? && u.valid? && u.scheme.to_s =~ /^https?$/
          else
            # Unknown datatype
            false
          end
        else
          false
        end
      end
    else
      # Fully entailed types of the resource
      types = options.fetch(:types) do
        queryable.query({subject: resource, predicate: RDF.type}).
          map {|s| (t = (RDF::Vocabulary.find_term(s.object) rescue nil)) && t.entail(:subClassOf)}.
          flatten.
          uniq.
          compact
      end

      # If any type is a class, add rdfs:Class
      if types.any? {|t| t.is_a?(RDF::Vocabulary::Term) && t.class?} && !types.include?(RDF::RDFS.Class)
        types << RDF::RDFS.Class
      end

      # Every range must match some entailed type
      Array(types).empty? || ranges.all? {|d| types.include?(d)}
    end
  else
    true
  end
end

#subClassArray<RDF::Vocabulary::Term>

Get the immediate subclasses of this class.

This iterates over terms defined in the vocabulary of this term, as well as the vocabularies imported by this vocabulary.



116
117
118
119
120
121
# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-reasoner-e77a80426b61/lib/rdf/reasoner/rdfs.rb', line 116

def subClass
  raise RDF::Reasoner::Error, "#{self} Can't entail subClass" unless class?
  subClass_cache[self] ||= ([self.vocab] + self.vocab.imported_from).map do |v|
    Array(v.properties).select {|p| p.class? && Array(p.subClassOf).include?(self)}
  end.flatten.compact
end

#subPropertyArray<RDF::Vocabulary::Term>

Get the immediate subproperties of this property.

This iterates over terms defined in the vocabulary of this term, as well as the vocabularies imported by this vocabulary.



189
190
191
192
193
194
195
196
197
198
# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-reasoner-e77a80426b61/lib/rdf/reasoner/rdfs.rb', line 189

def subProperty
  raise RDF::Reasoner::Error,
    "#{self} Can't entail subProperty" unless property?
  vocabs = [self.vocab] + self.vocab.imported_from
  subProperty_cache[self] ||= vocabs.map do |v|
    Array(v.properties).select do |p|
      p.property? && Array(p.subPropertyOf).include?(self)
    end
  end.flatten.compact
end