class RDF::Vocabulary
A {Vocabulary} represents an RDFS or OWL vocabulary.
A {Vocabulary} can also serve as a Domain Specific Language (DSL) for generating an RDF
Graph
definition for the vocabulary (see {RDF::Vocabulary#to_enum}).
### Defining a vocabulary using the DSL Vocabularies can be defined based on {RDF::Vocabulary} or {RDF::StrictVocabulary} using a simple Domain Specific Language (DSL).
-
Ontology information for the vocabulary itself can be specified using the {ontology} method.
-
Terms of the vocabulary are specified using either ‘property` or `term` (alias), with the attributes of the term listed in a hash. See {property} for description of the hash.
Term
attributes become properties of the associated {RDF::Vocabulary::Term} (see {RDF::Vocabulary::Term#attributes}).
Note that, by default, the prefix associated with the vocabulary for forming and interpreting PNames is created from the class name of the vocabulary. See {_prefix_=} for overriding this at runtime.
The simplest way to generate a DSL representation of a vocabulary is using {RDF::Vocabulary::Writer} given an {RDF::Graph} representation of the vocabulary.
### Vocabularies:
The following vocabularies are pre-defined for your convenience:
-
{RDF::OWL} - Web Ontology Language (OWL)
-
{RDF::RDFS} -
RDF
Schema (RDFS) -
{RDF::XSD} - XML Schema (XSD)
Other vocabularies are defined in the [rdf-vocab](rubygems.org/gems/rdf-vocab) gem
@example Using pre-defined RDF
vocabularies
include RDF RDF.type #=> RDF::URI("http://www.w3.org/1999/02/22-rdf-syntax-ns#type") RDFS.seeAlso #=> RDF::URI("http://www.w3.org/2000/01/rdf-schema#seeAlso") OWL.sameAs #=> RDF::URI("http://www.w3.org/2002/07/owl#sameAs") XSD.dateTime #=> RDF::URI("http://www.w3.org/2001/XMLSchema#dateTime")
@example Using ad-hoc RDF
vocabularies
foaf = RDF::Vocabulary.new("http://xmlns.com/foaf/0.1/") foaf.knows #=> RDF::URI("http://xmlns.com/foaf/0.1/knows") foaf[:name] #=> RDF::URI("http://xmlns.com/foaf/0.1/name") foaf['mbox'] #=> RDF::URI("http://xmlns.com/foaf/0.1/mbox")
@example Defining a simple vocabulary
EX = Class.new(RDF::StrictVocabulay("http://example/ns#")) do # Ontology definition ontology :"http://example/ns#", label: "The RDF Example Vocablary", type: "http://www.w3.org/2002/07/owl#Ontology" # Class definitions term :Class, label: "My Class", comment: "Good to use as an example", type: "rdfs:Class", subClassOf: "http://example/SuperClass", "ex:prop": "Some annotation property not having a shortcut" # Property definitions property :prop, comment: "A description of the property", label: "property", domain: "http://example/ns#Class", range: "rdfs:Literal", isDefinedBy: %(ex:), type: "rdf:Property" end
@example Method calls are converted to the typical RDF
camelcase convention
foaf = RDF::Vocabulary.new("http://xmlns.com/foaf/0.1/") foaf.family_name #=> RDF::URI("http://xmlns.com/foaf/0.1/familyName") owl.same_as #=> RDF::URI("http://www.w3.org/2002/07/owl#sameAs") # []-style access is left as is foaf['family_name'] #=> RDF::URI("http://xmlns.com/foaf/0.1/family_name") foaf[:family_name] #=> RDF::URI("http://xmlns.com/foaf/0.1/family_name")
@example Generating RDF
from a vocabulary definition
graph = RDF::Graph.new << RDF::RDFS.to_enum graph.dump(:ntriples)
Public Class Methods
Returns the URI
for the term ‘property` in this vocabulary.
@param [#to_s] property @return [RDF::URI]
# File lib/rdf/vocabulary.rb, line 405 def [](property) if props.key?(property.to_sym) props[property.to_sym] else Term.intern([to_s, property.to_s].join(''), vocab: self, attributes: {}) end end
Returns a suggested vocabulary prefix for this vocabulary class.
@return [Symbol] @since 0.3.0
# File lib/rdf/vocabulary.rb, line 609 def __prefix__ instance_variable_defined?(:@__prefix__) ? @__prefix__ : __name__.split('::').last.downcase.to_sym end
Sets the vocabulary prefix to use for this vocabulary..
@example Overriding a standard vocabulary prefix.
RDF::Vocab::DC.__prefix__ = :dcterms RDF::Vocab::DC.title.pname #=> 'dcterms:title'
@param [Symbol] prefix @return [Symbol] @since 3.2.3
# File lib/rdf/vocabulary.rb, line 625 def __prefix__=(prefix) params = RDF::Vocabulary.vocab_map[__prefix__] @__prefix__ = prefix.to_sym RDF::Vocabulary.register(@__prefix__, self, **params) @__prefix__ end
# File lib/rdf/vocabulary.rb, line 734 def self.camelize(str) str.split('_').inject([]) do |buffer, e| buffer.push(buffer.empty? ? e : e.capitalize) end.join end
# File lib/rdf/vocabulary.rb, line 718 def self.create(uri) # @private @@uri = uri self end
Enumerates known RDF
vocabulary classes.
@yield [klass] @yieldparam [Class] klass @return [Enumerator]
# File lib/rdf/vocabulary.rb, line 93 def each(&block) if self.equal?(Vocabulary) if instance_variable_defined?(:@vocabs) && @vocabs @vocabs.select(&:name).each(&block) else # This is needed since all vocabulary classes are defined using # Ruby's autoloading facility, meaning that `@@subclasses` will be # empty until each subclass has been touched or require'd. RDF::VOCABS.each { |v, p| RDF.const_get(p[:class_name].to_sym) unless v == :rdf } @@subclasses.select(&:name).each(&block) end else __properties__.each(&block) end end
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 468 def each_statement(&block) props.each do |name, subject| subject.each_statement(&block) end # Also include the ontology, if it's not also a property @ontology.each_statement(&block) if self.ontology && self.ontology != self end
Return an enumerator over {RDF::Statement} defined for this vocabulary. @return [RDF::Enumerable::Enumerator] @see Object#enum_for
# File lib/rdf/vocabulary.rb, line 452 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
Attempt to expand a Compact IRI/PName using loaded vocabularies
@param [String, to_s
] pname The local-part of the PName will will have [reserved character escapes](www.w3.org/TR/turtle/#reserved) unescaped. @return [Term] @raise [KeyError] if pname suffix not found in identified vocabulary. @raise [ArgumentError] if resulting URI
is not valid
# File lib/rdf/vocabulary.rb, line 346 def expand_pname(pname) return pname unless pname.is_a?(String) || pname.is_a?(Symbol) prefix, suffix = pname.to_s.split(":", 2) # Unescape escaped PN_ESCAPE_CHARS if suffix.match?(RDF::URI::PN_ESCAPES) suffix = suffix.gsub(RDF::URI::PN_ESCAPES) {|matched| matched[1..-1]} end if prefix == "rdf" RDF[suffix] elsif vocab_detail = RDF::Vocabulary.vocab_map[prefix.to_sym] vocab = vocab_detail[:class] || RDF::Vocabulary.from_sym(vocab_detail[:class_name]) suffix.to_s.empty? ? vocab.to_uri : vocab[suffix] else (RDF::Vocabulary.find_term(pname) rescue nil) || RDF::URI(pname, validate: true) end end
Return the Vocabulary
associated with a URI
. Allows the trailing ‘/’ or ‘#’ to be excluded
@param [RDF::URI] uri @return [Vocabulary]
# File lib/rdf/vocabulary.rb, line 369 def find(uri) uri = RDF::URI(uri) if uri.is_a?(String) return nil unless uri.uri? && uri.valid? RDF::Vocabulary.detect do |v| if uri.length >= v.to_uri.length uri.start_with?(v.to_uri) else v.to_uri.to_s.sub(%r([/#]$), '') == uri.to_s end end end
Return the Vocabulary
term associated with a URI
@param [RDF::URI, String] uri
If `uri` has is a pname in a locded vocabulary, the suffix portion of the PName will have escape characters unescaped before resolving against the vocabulary.
@return [Vocabulary::Term]
# File lib/rdf/vocabulary.rb, line 387 def find_term(uri) uri = RDF::URI(uri) return uri if uri.is_a?(Vocabulary::Term) if vocab = find(uri) if vocab.ontology == uri vocab.ontology else suffix = uri.to_s[vocab.to_uri.to_s.length..-1].to_s vocab[suffix] end end end
Create a vocabulary from a graph or enumerable
@param [RDF::Enumerable] graph @param [URI, to_s
] url @param [RDF::Vocabulary, String] class_name
The class_name associated with the vocabulary, used for creating the class name of the vocabulary. This will create a new class named with a top-level constant based on `class_name`. If given an existing vocabulary, it replaces the existing definitions for that vocabulary with those from the graph.
@param [Array<Symbol>, Hash{Symbol => Hash}] extra
Extra terms to add to the vocabulary. In the first form, it is an array of symbols, for which terms are created. In the second, it is a Hash mapping symbols to property attributes, as described in {RDF::Vocabulary.property}.
@return [RDF::Vocabulary] the loaded vocabulary
# File lib/rdf/vocabulary.rb, line 495 def from_graph(graph, url: nil, class_name: nil, extra: nil) vocab = case class_name when RDF::Vocabulary class_name.instance_variable_set(:@ontology, nil) class_name.instance_variable_set(:@properties, nil) class_name when String Object.const_set(class_name, Class.new(self.create(url))) else Class.new(self.create(url)) end ont_url = url.to_s.sub(%r([/#]$), '') term_defs = {} embedded_defs = {} graph.each do |statement| #next unless statement.subject.uri? if statement.subject.start_with?(url) || statement.subject == ont_url name = statement.subject.to_s[url.to_s.length..-1].to_s term = (term_defs[name.to_sym] ||= {}) else # subject is not a URI or is not associated with the vocabulary term = (embedded_defs[statement.subject] ||= {}) end key = Term::URI_ATTRs.fetch(statement.predicate) do statement.predicate.to_s.to_sym end (term[key] ||= []) << statement.object end # Create extra terms term_defs = case extra when Array extra.inject({}) {|memo, s| memo[s.to_sym] = {}; memo}.merge(term_defs) when Hash extra.merge(term_defs) else term_defs end # Pass over embedded_defs with anonymous references, once embedded_defs.each do |term, attributes| attributes.each do |ak, avs| # Turn embedded BNodes into either their Term definition or a List avs = [avs] unless avs.is_a?(Array) attributes[ak] = avs.map do |av| l = RDF::List.new(subject: av, graph: graph) if l.valid? RDF::List.new(subject: av) do |nl| l.each do |lv| nl << (embedded_defs[lv] ? Term.new(vocab: vocab, attributes: embedded_defs[lv]) : lv) end end elsif av.is_a?(RDF::Node) Term.new(vocab: vocab, attributes: embedded_defs[av]) if embedded_defs[av] else av end end.compact end end term_defs.each do |term, attributes| # Turn embedded BNodes into either their Term definition or a List attributes.each do |ak, avs| attributes[ak] = avs.is_a?(Array) ? (avs.map do |av| l = RDF::List.new(subject: av, graph: graph) if l.valid? RDF::List.new(subject: av) do |nl| l.each do |lv| nl << (embedded_defs[lv] ? Term.new(vocab: vocab, attributes: embedded_defs[lv]) : lv) end end elsif av.is_a?(RDF::Node) Term.new(vocab: vocab, attributes: embedded_defs[av]) if embedded_defs[av] else av end end).compact : avs end if term == :"" vocab.__ontology__ vocab, attributes else vocab.__property__ term, attributes end end vocab end
Return the vocabulary based on it’s class_name symbol
@param [Symbol] sym @return [RDF::Vocabulary]
# File lib/rdf/vocabulary.rb, line 126 def from_sym(sym) RDF.const_get(sym.to_sym) end
List
of vocabularies which import this vocabulary @return [Array<RDF::Vocabulary>]
# File lib/rdf/vocabulary.rb, line 431 def imported_from @imported_from ||= begin RDF::Vocabulary.select {|v| v.__imports__.include?(self)} end end
List
of vocabularies this vocabulary ‘owl:imports`
@note the alias {__imports__} guards against ‘RDF::OWL.imports` returning a term, rather than an array of vocabularies @return [Array<RDF::Vocabulary>]
# File lib/rdf/vocabulary.rb, line 418 def imports return [] unless self.ontology @imports ||= begin Array(self.ontology.properties[:"http://www.w3.org/2002/07/owl#imports"]).compact rescue KeyError [] end end
Returns a developer-friendly representation of this vocabulary class.
@return [String]
# File lib/rdf/vocabulary.rb, line 592 def inspect if self == Vocabulary self.to_s else sprintf("%s(%s)", superclass.to_s, to_s) end end
Limits iteration over vocabularies to just those selected
@example limit to set of vocabularies by symbol
RDF::Vocabulary.limit_vocabs(:rdf, :rdfs RDF::Vocabulary.find_term('http://www.w3.org/2000/01/rdf-schema#Resource').pname # => 'rdfs:Resource'
@example limit to set of vocabularies by class name
RDF::Vocabulary.limit_vocabs(RDF::RDFV, RDF::RDFS) RDF::Vocabulary.find_term('http://www.w3.org/2000/01/rdf-schema#Resource').pname # => 'rdfs:Resource'
@param [Array<symbol, RDF::Vocabulary
>] vocabs
A list of vocabularies (symbols or classes) which may be returned by {Vocabulary.each}. Also limits vocabularies that will be inspeced for other methods. Set to nil, or an empty array to reset.
@return [Array<RDF::Vocabulary>]
# File lib/rdf/vocabulary.rb, line 179 def limit_vocabs(*vocabs) @vocabs = if Array(vocabs).empty? nil else vocabs.map do |vocab| vocab = :rdfv if vocab == :rdf vocab.is_a?(Symbol) && RDF::VOCABS.key?(vocab) ? RDF.const_get(RDF::VOCABS[vocab][:class_name].to_sym) : vocab end.compact end end
@param [RDF::URI, String, to_s
] uri
# File lib/rdf/vocabulary.rb, line 673 def initialize(uri) @uri = case uri when RDF::URI then uri.to_s else RDF::URI.parse(uri.to_s) ? uri.to_s : nil end end
@overload ontology
Returns the ontology definition of the current vocabulary as a term. @return [RDF::Vocabulary::Term]
@overload ontology(name, options)
Defines the vocabulary ontology. @param [String, #to_s] uri The URI of the ontology. @param [Hash{Symbol => Object}] options See {property} @param [Hash{Symbol=>String,Array<String,Term>}] options Any other values are expected to expands to a {URI} using built-in vocabulary prefixes. The value is a `String`, `Array<String>` or `Array<Term>` which is interpreted according to the `range` of the associated property. @option options [String, Array<String,Term>] :type Shortcut for `rdf:type`, values are interpreted as a {Term}. @option options [String, Array<String>] :comment Shortcut for `rdfs:comment`, values are interpreted as a {Literal}. @option options [String, Array<String,Term>] :isDefinedBy Shortcut for `rdfs:isDefinedBy`, values are interpreted as a {Term}. @option options [String, Array<String>] :label Shortcut for `rdfs:label`, values are interpreted as a {Literal}. @option options [String, Array<String>] :altLabel Shortcut for `skos:altLabel`, values are interpreted as a {Literal}. @option options [String, Array<String>] :definition Shortcut for `skos:definition`, values are interpreted as a {Literal}. @option options [String, Array<String>] :editorialNote Shortcut for `skos:editorialNote`, values are interpreted as a {Literal}. @option options [String, Array<String>] :note Shortcut for `skos:note`, values are interpreted as a {Literal}. @option options [String, Array<String>] :prefLabel Shortcut for `skos:prefLabel`, values are interpreted as a {Literal}. @return [RDF::Vocabulary::Term]
@note If the ontology URI
has the vocabulary namespace URI
as a prefix, it may also be defined using ‘#property` or `#term`
# File lib/rdf/vocabulary.rb, line 313 def ontology(*args) case args.length when 0 @ontology if instance_variable_defined?(:@ontology) else uri, options = args URI.cache.delete(uri.to_s.to_sym) # Clear any previous entry # Term attributes passed in a block for lazy evaluation. This helps to avoid load-time circular dependencies @ontology = Term.intern(uri.to_s, vocab: self, attributes: options || {}) # If the URI is the same as the vocabulary namespace, also define it as a term props[:""] ||= @ontology if self.to_s == uri.to_s @ontology end end
@return [Array<RDF::URI>] a list of properties in the current vocabulary
# File lib/rdf/vocabulary.rb, line 333 def properties props.values end
@overload property
Returns `property` in the current vocabulary @return [RDF::Vocabulary::Term]
@overload property(name, options)
Defines a new property or class in the vocabulary as a {RDF::Vocabulary::Term}. @example A simple term definition property :domain, comment: %(A domain of the subject property.), domain: "rdf:Property", label: "domain", range: "rdfs:Class", isDefinedBy: %(rdfs:), type: "rdf:Property" @example A term definition with tagged values property :actor, comment: {en: "Subproperty of as:attributedTo that identifies the primary actor"}, domain: "https://www.w3.org/ns/activitystreams#Activity", label: {en: "actor"}, range: term( type: "http://www.w3.org/2002/07/owl#Class", unionOf: list("https://www.w3.org/ns/activitystreams#Object", "https://www.w3.org/ns/activitystreams#Link") ), subPropertyOf: "https://www.w3.org/ns/activitystreams#attributedTo", type: "http://www.w3.org/2002/07/owl#ObjectProperty" @example A SKOS term with anonymous values term: :af, type: "jur:Country", isDefinedBy: "http://eulersharp.sourceforge.net/2003/03swap/countries#", "skos:exactMatch": [ Term.new( type: "skos:Concept", inScheme: "iso3166-1-alpha-2", notation: "ax"), Term.new( type: "skos:Concept", inScheme: "iso3166-1-alpha-3", notation: "ala") ], "foaf:name": "Aland Islands" @param [String, #to_s] name @param [Hash{Symbol=>String,Array<String>}] options Any other values are expected to expands to a {URI} using built-in vocabulary prefixes. The value is a `String`, 'Hash{Symbol=>String,Array<String>}' or `Array<String,Hash{Symbol=>Array<String>}>` which is interpreted according to the `range` of the associated property and by heuristically determining the value datatype. See `attributes` argument to {Term#initialize}. @return [RDF::Vocabulary::Term]
# File lib/rdf/vocabulary.rb, line 245 def property(*args) case args.length when 0 Term.intern("#{self}property", vocab: self, attributes: {}) else name = args.shift unless args.first.is_a?(Hash) options = args.last if name uri_str = [to_s, name.to_s].join('') URI.cache.delete(uri_str.to_sym) # Clear any previous entry # Term attributes passed in a block for lazy evaluation. This helps to avoid load-time circular dependencies prop = Term.intern(uri_str, vocab: self, attributes: options || {}) props[name.to_sym] = prop # If name is empty, also treat it as the ontology @ontology ||= prop if name.to_s.empty? # Define an accessor, except for problematic properties (class << self; self; end).send(:define_method, name) { prop } unless %w(property term hash).include?(name.to_s) else # Define the term without a name # Term attributes passed in a block for lazy evaluation. This helps to avoid load-time circular dependencies prop = Term.new(vocab: self, attributes: options) end prop end end
Register a vocabulary for internal prefix lookups. Parameters of interest include ‘:uri`, `:class_name`, `:source`, and `:skip`.
@param prefix [Symbol] the prefix to use @param vocab [String, Class] either the URI
or the vocab class @param params [Hash{Symbol => String}] Relevant parameters @return [Hash] The parameter hash, but frozen
# File lib/rdf/vocabulary.rb, line 138 def register(prefix, vocab, **params) # check the input raise ArgumentError, "#{prefix} must be symbol-able" unless [String, Symbol].any? { |c| prefix.is_a? c } # note an explicit uri: param overrides case vocab when String then params[:uri] ||= vocab when Class raise ArgumentError, 'vocab must be an RDF::(Strict)Vocabulary' unless vocab.ancestors.include? RDF::Vocabulary params[:class] = vocab params[:uri] ||= vocab.to_uri.to_s end # fill in the class name params[:class_name] ||= prefix.to_s.upcase # now freeze and assign vocab_map[prefix.to_s.to_sym] = params.freeze end
Is this a strict vocabulary, or a liberal vocabulary allowing arbitrary properties?
# File lib/rdf/vocabulary.rb, line 194 def strict?; false; end
Alternate use for vocabulary terms, functionally equivalent to {#property}.
Returns a string representation of this vocabulary class.
@return [String]
# File lib/rdf/vocabulary.rb, line 481 def to_s @@uris.key?(self) ? @@uris[self].to_s : super end
Returns the base URI
for this vocabulary class.
@return [RDF::URI]
# File lib/rdf/vocabulary.rb, line 441 def to_uri RDF::URI.intern(@@uris[self].to_s) end
A hash of all vocabularies by prefix showing relevant URI
and associated vocabulary Class Name
@return [Hash{Symbol => Hash{Symbol => String}}]
# File lib/rdf/vocabulary.rb, line 114 def vocab_map # Create an initial duplicate of RDF::VOCABS. We want to # ensure the hash itself is modifiable but the values are # frozen. @vocab_map ||= RDF::VOCABS.transform_values(&:freeze) end
Protected Class Methods
# File lib/rdf/vocabulary.rb, line 633 def inherited(subclass) # @private unless @@uri.nil? @@subclasses << subclass unless %w(http://www.w3.org/1999/02/22-rdf-syntax-ns#).include?(@@uri) subclass.send(:private_class_method, :new) @@uris[subclass] = @@uri @@uri = nil end super end
Create a list of terms @param [Array<String>] values
Each value treated as a URI or PName
@return [RDF::List]
# File lib/rdf/vocabulary.rb, line 656 def list(*values) RDF::List[*values.map {|v| expand_pname(v) rescue RDF::Literal(v)}] end
# File lib/rdf/vocabulary.rb, line 643 def method_missing(property, *args, &block) property = RDF::Vocabulary.camelize(property.to_s) if args.empty? && !to_s.empty? Term.intern([to_s, property.to_s].join(''), vocab: self, attributes: {}) else super end end
Private Class Methods
# File lib/rdf/vocabulary.rb, line 661 def props; @properties ||= {}; end
Public Instance Methods
Returns the URI
for the term ‘property` in this vocabulary.
@param [#to_s] property @return [URI]
# File lib/rdf/vocabulary.rb, line 685 def [](property) Term.intern([to_s, property.to_s].join(''), vocab: self, attributes: {}) end
Returns a developer-friendly representation of this vocabulary.
@return [String]
# File lib/rdf/vocabulary.rb, line 712 def inspect sprintf("#<%s:%#0x(%s)>", self.class.name, __id__, to_s) end
Returns a string representation of this vocabulary.
@return [String]
# File lib/rdf/vocabulary.rb, line 704 def to_s @uri.to_s end
Protected Instance Methods
# File lib/rdf/vocabulary.rb, line 723 def method_missing(property, *args, &block) property = self.class.camelize(property.to_s) if %w(to_ary).include?(property.to_s) super elsif args.empty? self[property] else super end end