module RDF::Vocabulary::Term

A Vocabulary Term is a {RDF::Resource} that can also act as an {Enumerable} to generate the RDF definition of vocabulary terms as defined within the vocabulary definition.

Terms include {Term#attributes} where values a embedded resources, lists or other terms. This allows, for example, navigation of a concept heirarchy.

Term attributes can also be accessed using {Term#properties} where the attribute values are transformed into different types of {RDF::Value}. Properties can be indexed by key, where a key is defined (See {Term::ATTR_URIs}), absolute URI, or PName, where the prefix is associated with a loaded vocabulary.

Constants

ATTR_URIs

Look up URIs for attribute symbols

@return [Hash{Symbol => RDF::URI}]

URI_ATTRs

Look up attribute symbols from URIs

@return [Hash{RDF::URI => Symbol}]

Attributes

attributes[R]

Attributes of this vocabulary term, used for finding ‘label` and `comment` and to serialize the term back to RDF.

Attributes are indexed by symbol. Symbols directly interpreted by a term are the accessors defined for the {RDF::Vocabulary::Term} class, also in {Term::ATTR_URIs}. Other keys are interpreted as absolute URIs or PNames for properties defined on this term.

Symbols which are accessors may also be looked up by their associated URI.

Values may be Strings, Hash (Map), or Terms, or an Array including a combination of these. A Hash (Map) is used to create a datatype/language map to one or more string values which represent either datatyped literals, or language-tagged literals as interpreted by {#attribute_value}.

In general, this accessor is used internally. The {#properties} method interprets these values as {RDF::Value}.

@note lookup by PName is DEPRECATED and will be removed in a future version.

@example looking up term label

RDF::RDFS.Literal.attributes[:label] #=> "Literal"
RDF::RDFS.Literal.attributes[:"rdfs:label"] #=> "Literal"
RDF::RDFS.Literal.attributes[RDF::RDFS.label] #=> "Literal"
RDF::RDFS.Literal.attributes["http://www.w3.org/2000/01/rdf-schema#label"] #=> "Literal"
RDF::RDFS.Literal.attributes[:"http://www.w3.org/2000/01/rdf-schema#label"] #=> "Literal"

@return [Hash{Symbol => String, Term, Hash{Symbol => String}, Array<String, Term, Hash{Symbol => String}>}] @see properties

vocab[R]

Vocabulary of this term.

@return [RDF::Vocabulary]

Public Class Methods

intern(str, *args, **options) click to toggle source

Returns an interned ‘RDF::URI` instance based on the given `uri` string.

The maximum number of cached interned URI references is given by the ‘CACHE_SIZE` constant. This value is unlimited by default, in which case an interned URI object will be purged only when the last strong reference to it is garbage collected (i.e., when its finalizer runs).

Excepting special memory-limited circumstances, it should always be safe and preferred to construct new URI references using ‘RDF::URI.intern` instead of `RDF::URI.new`, since if an interned object can’t be returned for some reason, this method will fall back to returning a freshly-allocated one.

@param (see initialize) @return [RDF::URI] an immutable, frozen URI object

# File lib/rdf/vocabulary.rb, line 1008
def self.intern(str, *args, **options)
  (URI.cache[(str = str.to_s).to_sym] ||= self.new(str, *args, **options)).freeze
end
new(*args, vocab: nil, attributes: {}, **options) click to toggle source

@overload new(uri, attributes:, vocab:, **options)

@param  [URI, String, #to_s]    uri
@param [Vocabulary] vocab Vocabulary of this term.
@param [Hash{Symbol => String,Term,Hash{Symbol=>String,Array<String>},Array<String>}] attributes ({})
  Attributes of this vocabulary term, used for finding `label` and `comment` and to serialize the term back to RDF. See {#attributes} and {#properties} for other ways to access.
@param  [Hash{Symbol => Object}] options
  Options from {URI#initialize}

@overload new(attributes:, vocab:, **options)

@param [Vocabulary] vocab Vocabulary of this term.
@param [Hash{Symbol => String,Term,Hash{Symbol=>String,Array<String>},Array<String>}] attributes ({})
  Attributes of this vocabulary term, used for finding `label`,  `comment` and other term properties, and to serialize the term back to RDF. See {#attributes} and {#properties} for other ways to access.
@param  [Hash{Symbol => Object}] options
  Options from {URI#initialize}
# File lib/rdf/vocabulary.rb, line 950
def self.new(*args, vocab: nil, attributes: {}, **options)
  klass = if args.first.nil?
    RDF::Node
  elsif args.first.is_a?(Hash)
    args.unshift(nil)
    RDF::Node
  elsif args.first.to_s.start_with?("_:")
    args = args[1..-1].unshift($1)
    RDF::Node
  else RDF::URI
  end

  # Create default proc on attributes to allow lookup by different key types.
  attributes = attributes.dup if attributes.frozen?
  attributes.default_proc = -> (hash, key) do
    sym = case key
    when RDF::URI
      URI_ATTRs.fetch(key, key.to_s.to_sym)
    when String
      URI_ATTRs.fetch(RDF::URI(key), key.to_s.to_sym)
    when Symbol
      case key.to_s
      when /^https?:/
        # Lookup by associated attribute, or pname
        URI_ATTRs.fetch(RDF::URI(key.to_s), RDF::URI(key).pname.to_sym)
      when /:/
        uri = RDF::Vocabulary.expand_pname(key)
        # Lookup by associated attribute or URI
        URI_ATTRs.fetch(uri, uri.to_s.to_sym)
      end
    end
    hash.fetch(sym, nil)
  end

  term = klass.allocate.extend(Term)
  term.send(:initialize, *args)
  term.instance_variable_set(:@vocab, vocab)
  term.instance_variable_set(:@attributes, attributes)
  term
end

Public Instance Methods

attribute_value(prop) click to toggle source

Values of an attributes as {RDF::Value}.

Attribute values are returned as either an {RDF::Value} or {Array<RDf::Value} if there is more than one value.

Attribute values which are not already a {RDF::Value} (including strings and symbols) are converted by a heuristic loookup as follows:

  • An {RDF::URI} if it can be turned into a valid IRI using {RDF::Vocabulary.expand_pname}. This includes IRIs already in non-relative form.

  • A {Hash{Symbol=>String,Array<String>}} is interpreted as a datatype/language map. If the key contains a ‘:’, it is treated as a PName or IRI datatype applied to the values. Otherwise, it is treated as a language-tag applied to the values.

  • {RDF::Literal::Date} if valid,

  • {RDF::Literal::DateTime} if valid,

  • {RDF::Literal::Integer} if valid,

  • {RDF::Literal::Decimal} if valid,

  • {RDF::Literal::Double} if valid,

  • {RDF::Literal::Boolean} if valid

  • Otherwise, {RDF::Literal} where type may be inferred by the class of the value.

@param [Symbol] prop @return [RDF::Value, Array<RDF::Value>]

# File lib/rdf/vocabulary.rb, line 1111
def attribute_value(prop)
  values = attributes[prop]
  return nil if values.nil?
  values = [values].compact unless values.is_a?(Array)
  prop_values = values.map do |value|
    v = value.is_a?(Symbol) ? value.to_s : value
    value = (RDF::Vocabulary.expand_pname(v) rescue nil) if v.is_a?(String) && v.include?(':')
    value = value.to_uri if value.respond_to?(:to_uri)
    value = if value.is_a?(RDF::Value) && value.valid?
      value
    elsif value.is_a?(Hash)
      # type/language map
      value.inject([]) do |memo, (k,v)|
        vv = [v] unless v.is_a?(Array)
        memo << if k.to_s.include?(':')
          dt = RDF::Vocabulary.expand_pname(v) rescue nil
          vv.map {|val| RDF::Literal(val, datatype: dt)}
        else
          vv.map {|val| RDF::Literal(val, language: k)}
        end
      end.flatten.compact.select(&:valid?)
    else
      # Use as most appropriate literal
      [
        RDF::Literal::Date,
        RDF::Literal::DateTime,
        RDF::Literal::Integer,
        RDF::Literal::Decimal,
        RDF::Literal::Double,
        RDF::Literal::Boolean,
        RDF::Literal
      ].inject(nil) do |m, klass|
        m || begin
          l = klass.new(v)
          l if l.valid?
        end
      end
    end
  end.flatten

  prop_values.length <= 1 ? prop_values.first : prop_values
end
class?() click to toggle source

Is this a class term? @return [Boolean]

# File lib/rdf/vocabulary.rb, line 1036
def class?
  Array(self.type).any? {|t| t.to_s.include?('Class')}
end
datatype?() click to toggle source

Is this a class term? @return [Boolean]

# File lib/rdf/vocabulary.rb, line 1050
def datatype?
  Array(self.type).any? {|t| t.to_s.include?('Datatype')}
end
domain_includes() click to toggle source

Accessor for ‘schema:domainIncludes` @return [RDF::URI]

# File lib/rdf/vocabulary.rb, line 1223
def domain_includes
  domainIncludes
end
dup() click to toggle source

Returns a duplicate copy of ‘self`.

@return [RDF::URI]

Calls superclass method
# File lib/rdf/vocabulary.rb, line 1016
def dup
  term = super.extend(Term)
  term.instance_variable_set(:@vocab, vocab)
  term.instance_variable_set(:@attributes, attributes)
  term
end
each_statement() { |Statement(self, prop, value)| ... } click to toggle source

Enumerate each statement constructed from the defined vocabulary terms

If a property value is known to be a {URI}, or expands to a {URI}, the ‘object` is a URI, otherwise, it will be a {Literal}.

@yield statement @yieldparam [RDF::Statement]

# File lib/rdf/vocabulary.rb, line 1161
def each_statement
  attributes.keys.each do |p|
    prop = ATTR_URIs.fetch(p) { RDF::Vocabulary::expand_pname(p)}
    values = attribute_value(p)
    values = [values].compact unless values.is_a?(Array)
    values.each do |value|
      yield RDF::Statement(self, prop, value) if prop.is_a?(RDF::URI)

      # Enumerate over value statements, if enumerable
      if value.is_a?(RDF::Enumerable) || (value.is_a?(Term) && value.node?)
        value.each_statement {|s| yield s}
      end
    end
  end
end
enum_for(method = :each_statement, *args) click to toggle source

Return an enumerator over {RDF::Statement} defined for this vocabulary. @return [RDF::Enumerable::Enumerator] @see Object#enum_for

# File lib/rdf/vocabulary.rb, line 1181
def enum_for(method = :each_statement, *args)
  # Ensure that enumerators are, themselves, queryable
  this = self
  Enumerable::Enumerator.new do |yielder|
    this.send(method, *args) {|*y| yielder << (y.length > 1 ? y : y.first)}
  end
end
Also aliased as: to_enum
inspect() click to toggle source

Returns a String representation of the URI object’s state.

@return [String] The URI object’s state, as a String.

# File lib/rdf/vocabulary.rb, line 1194
def inspect
  sprintf("#<%s:%#0x ID:%s>", Term.to_s, self.object_id, self.to_s)
end
other?() click to toggle source

Is this neither a class, property or datatype term? @return [Boolean]

# File lib/rdf/vocabulary.rb, line 1064
def other?
  Array(self.type).none? {|t| t.to_s.match?(/(Class|Property|Datatype|Restriction)/)}
end
properties() click to toggle source

Enumerate attributes with values transformed into {RDF::Value} instances Uses an empty hash with a default_proc which looks up values in attributes. The prevents specific attributes from being evaluated until acessed.

Properties are indexed by symbol. Symbols directly interpreted by a term are the accessors defined for the {RDF::Vocabulary::Term} class, also in {Term::ATTR_URIs}. Other keys are interpreted as absolute URIs or PNames for properties defined on this term.

Symbols which are accessors may also be looked up by their associated URI.

@note lookup by PName is DEPRECATED and will be removed in a future version.

@example looking up term label

RDF::RDFS.Literal.label #=> RDF::Literal("Literal")
RDF::RDFS.Literal.properties[:label] #=> RDF::Literal("Literal")
RDF::RDFS.Literal.properties[:"rdfs:label"] #=> RDF::Literal("Literal")
RDF::RDFS.Literal.properties[RDF::RDFS.label] #=> RDF::Literal("Literal")
RDF::RDFS.Literal.properties["http://www.w3.org/2000/01/rdf-schema#label"] #=> RDF::Literal("Literal")
RDF::RDFS.Literal.properties[:"http://www.w3.org/2000/01/rdf-schema#label"] #=> RDF::Literal("Literal")

@return [Hash{Symbol => Array<RDF::Value>}] @see attribute_value

# File lib/rdf/vocabulary.rb, line 1088
def properties
  Hash.new {|hash, key| attribute_value(key)}
end
property?() click to toggle source

Is this a class term? @return [Boolean]

# File lib/rdf/vocabulary.rb, line 1043
def property?
  Array(self.type).any? {|t| t.to_s.include?('Property')}
end
range_includes() click to toggle source

Accessor for ‘schema:rangeIncludes` @return [RDF::URI]

# File lib/rdf/vocabulary.rb, line 1229
def range_includes
  rangeIncludes
end
respond_to?(method, include_all = false) click to toggle source

Implement accessor to symbol attributes

Calls superclass method
# File lib/rdf/vocabulary.rb, line 1199
def respond_to?(method, include_all = false)
  case method
  when :comment, :notation, :note, :editorialNote, :definition,
       :label, :altLabel, :prefLabel, :type, :isDefinedBy
    true
  when :subClassOf, :subPropertyOf,
       :domainIncludes, :rangeIncludes,
       :equivalentClass, :intersectionOf, :unionOf
    self.class?
  when :domain, :range, :equivalentProperty, :inverseOf
    self.property?
  when :allValuesFrom, :cardinality,
       :maxCardinality, :minCardinality,
       :onProperty, :someValuesFrom
    self.restriction?
  when :broader, :exactMatch, :hasTopConcept, :inScheme, :member, :narrower, :related
    @attributes.key?(method)
  else
    super
  end
end
restriction?() click to toggle source

Is this a Restriction term? @return [Boolean]

# File lib/rdf/vocabulary.rb, line 1057
def restriction?
  Array(self.type).any? {|t| t.to_s.include?('Restriction')}
end
to_enum(method = :each_statement, *args)
Alias for: enum_for
to_ruby(indent: "") click to toggle source

Serialize back to a Ruby source initializer. This is used primarily by {RDF::Vocabulary::Writer}.

@param [String] indent @return [String]

# File lib/rdf/vocabulary.rb, line 1238
def to_ruby(indent: "")
  "term(" +
  (self.uri? ? self.to_s.inspect + ",\n" : "\n") +
  "#{indent}  " +
  attributes.keys.sort.map do |k|
    values = attribute_value(k)
    values = [values].compact unless values.is_a?(Array)
    values = values.map do |value|
      if value.is_a?(Literal) && %w(: comment definition notation note editorialNote).include?(k.to_s)
        "%(#{value.to_s.gsub('(', '\(').gsub(')', '\)')})"
      elsif value.node? && value.is_a?(RDF::Vocabulary::Term)
        "#{value.to_ruby(indent: indent + "  ")}"
      elsif value.is_a?(RDF::Term)
        "#{value.to_s.inspect}"
      elsif value.is_a?(RDF::List)
        list_elements = value.map do |u|
          if u.uri?
            "#{u.to_s.inspect}"
          elsif u.node? && u.respond_to?(:to_ruby)
            u.to_ruby(indent: indent + "  ")
          else
            "#{u.to_s.inspect}"
          end
        end
        "list(#{list_elements.join(', ')})"
      else
        "#{value.inspect}"
      end
    end
    "#{k.to_s.include?(':') ? k.to_s.inspect : k}: " +
    (values.length == 1 ? values.first : ('[' + values.join(',') + ']'))
  end.join(",\n#{indent}  ") + "\n#{indent})"

end
valid?() click to toggle source

Determine if the URI is a valid according to RFC3987

@return [Boolean] ‘true` or `false` @since 0.3.9

# File lib/rdf/vocabulary.rb, line 1028
def valid?
  # Validate relative to RFC3987
  node? || RDF::URI::IRI.match?(to_s) || false
end

Protected Instance Methods

method_missing(method, *args, &block) click to toggle source

Implement accessor to symbol attributes

Calls superclass method
# File lib/rdf/vocabulary.rb, line 1274
def method_missing(method, *args, &block)
  case method
  when :comment, :notation, :note, :editorialNote, :definition
    attribute_value(method)
  when :label, :altLabel, :prefLabel
    # Defaults to URI fragment or path tail
    begin
      attribute_value(method)
    rescue KeyError
      to_s.split(/[\/\#]/).last
    end
  when :type, :subClassOf, :subPropertyOf, :domain, :range, :isDefinedBy,
       :allValuesFrom, :cardinality, :equivalentClass, :equivalentProperty,
       :imports, :intersectionOf, :inverseOf, :maxCardinality, :minCardinality,
       :onProperty, :someValuesFrom, :unionOf,
       :domainIncludes, :rangeIncludes,
       :broader, :exactMatch, :hasTopConcept, :inScheme, :member, :narrower, :related

    # Return value as an Array, unless it is a list
    case value = attribute_value(method)
    when Array, RDF::List then value
    else [value].compact
    end
  else
    super
  end
end