class RDF::Vocabulary
Vocabulary format specification. This can be used to generate a Ruby class definition from a loaded 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). 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.
### Vocabularies:
The following vocabularies are pre-defined for your convenience:
-
{RDF} - Resource Description Framework (RDF)
-
{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 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)
@example Defining a simple vocabulary
class EX < RDF::StrictVocabulay("http://example/ns#") term :Class, label: "My Class", comment: "Good to use as an example", "rdf:type" => "rdfs:Class", "rdfs:subClassOf" => "http://example/SuperClass" end
@see www.w3.org/TR/curie/ @see en.wikipedia.org/wiki/QName
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 189 def [](property) if props.has_key?(property.to_sym) props[property.to_sym] else Term.intern([to_s, property.to_s].join(''), attributes: {vocab: self}) end end
Returns a suggested CURIE/PName prefix for this vocabulary class.
@return [Symbol] @since 0.3.0
# File lib/rdf/vocabulary.rb, line 348 def __prefix__ __name__.split('::').last.downcase.to_sym end
Enumerates known RDF vocabulary classes.
@yield [klass] @yieldparam [Class] klass @return [Enumerator]
# File lib/rdf/vocabulary.rb, line 70 def each(&block) if self.equal?(Vocabulary) # 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| require "rdf/vocab/#{v}" unless v == :rdf } @@subclasses.select(&:name).each(&block) 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 251 def each_statement(&block) props.each do |name, subject| subject.each_statement(&block) end 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 235 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/QName using loaded vocabularies
@param [String, to_s] pname @return [RDF::URI] @raise [KeyError] if pname suffix not found in identified vocabulary
# File lib/rdf/vocabulary.rb, line 146 def expand_pname(pname) prefix, suffix = pname.to_s.split(":", 2) if prefix == "rdf" RDF[suffix] elsif vocab = RDF::Vocabulary.each.detect {|v| v.__name__ && v.__prefix__ == prefix.to_sym} suffix.to_s.empty? ? vocab.to_uri : vocab[suffix] else RDF::Vocabulary.find_term(pname) || RDF::URI(pname) 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 162 def find(uri) RDF::Vocabulary.detect do |v| if uri.length >= v.to_uri.length RDF::URI(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] uri @return [Vocabulary::Term]
# File lib/rdf/vocabulary.rb, line 177 def find_term(uri) uri = RDF::URI(uri) return uri if uri.is_a?(Vocabulary::Term) vocab = RDF::Vocabulary.detect {|v| uri.start_with?(v.to_uri)} vocab[uri.to_s[vocab.to_uri.to_s.length..-1]] if vocab end
Create a vocabulary from a graph or enumerable
@param [RDF::Enumerable] graph @param [URI, to_s] url @param [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`.
@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 275 def from_graph(graph, url: nil, class_name: nil, extra: nil) vocab = if class_name Object.const_set(class_name, Class.new(self.create(url))) else Class.new(self.create(url)) end term_defs = {} graph.each do |statement| next unless statement.subject.uri? && statement.subject.start_with?(url) name = statement.subject.to_s[url.to_s.length..-1] term = (term_defs[name.to_sym] ||= {}) key = case statement.predicate when RDF.type then :type when RDF::RDFS.subClassOf then :subClassOf when RDF::RDFS.subPropertyOf then :subPropertyOf when RDF::RDFS.range then :range when RDF::RDFS.domain then :domain when RDF::RDFS.comment then :comment when RDF::RDFS.label then :label when RDF::URI("http://schema.org/inverseOf") then :inverseOf when RDF::URI("http://schema.org/domainIncludes") then :domainIncludes when RDF::URI("http://schema.org/rangeIncludes") then :rangeIncludes else statement.predicate.pname end value = if statement.object.uri? statement.object.pname elsif statement.object.literal? && (statement.object.language || :en).to_s =~ /^en-?/ statement.object.to_s end (term[key] ||= []) << value if value end # Create extra terms term_defs = case extra when Array extra.inject({}) {|memo, s| memo[s.to_sym] = {label: s.to_s}; memo}.merge(term_defs) when Hash extra.merge(term_defs) else term_defs end term_defs.each do |term, attributes| vocab.__property__ term, attributes end vocab end
List of vocabularies which import this vocabulary @return [Array<RDF::Vocabulary>]
# File lib/rdf/vocabulary.rb, line 214 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 202 def imports @imports ||= begin Array(self[""].attributes[:"owl:imports"]).map {|pn|find(expand_pname(pn)) rescue nil}.compact rescue KeyError [] end end
Returns a developer-friendly representation of this vocabulary class.
@return [String]
# File lib/rdf/vocabulary.rb, line 331 def inspect if self == Vocabulary self.to_s else sprintf("%s(%s)", superclass.to_s, to_s) end end
@param [RDF::URI, String, to_s] uri
# File lib/rdf/vocabulary.rb, line 388 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
@return [Array<RDF::URI>] a list of properties in the current vocabulary
# File lib/rdf/vocabulary.rb, line 135 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. @param [String, #to_s] name @param [Hash{Symbol => Object}] options Any other values are expected to be String which expands to a {URI} using built-in vocabulary prefixes. The value is a `String` or `Array<String>` which is interpreted according to the `range` of the associated property. @option options [String, Array<String>] :label Shortcut for `rdfs:label`, values are String interpreted as a {Literal}. @option options [String, Array<String>] :comment Shortcut for `rdfs:comment`, values are String interpreted as a {Literal}. @option options [String, Array<String>] :subClassOf Shortcut for `rdfs:subClassOf`, values are String interpreted as a {URI}. @option options [String, Array<String>] :subPropertyOf Shortcut for `rdfs:subPropertyOf`, values are String interpreted as a {URI}. @option options [String, Array<String>] :domain Shortcut for `rdfs:domain`, values are String interpreted as a {URI}. @option options [String, Array<String>] :range Shortcut for `rdfs:range`, values are String interpreted as a {URI}. @option options [String, Array<String>] :type Shortcut for `rdf:type`, values are String interpreted as a {URI}. @return [RDF::Vocabulary::Term]
# File lib/rdf/vocabulary.rb, line 112 def property(*args) case args.length when 0 Term.intern("#{self}property", attributes: {label: "property", vocab: self}) else name, options = args options = {label: name.to_s, vocab: self}.merge(options || {}) uri_str = [to_s, name.to_s].join('') Term.cache.delete(uri_str) # Clear any previous entry prop = Term.intern(uri_str, attributes: options) props[name.to_sym] = prop # Define an accessor, except for problematic properties (class << self; self; end).send(:define_method, name) { prop } unless %w(property hash).include?(name.to_s) prop end end
Is this a strict vocabulary, or a liberal vocabulary allowing arbitrary properties?
# File lib/rdf/vocabulary.rb, line 84 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 261 def to_s @@uris.has_key?(self) ? @@uris[self].to_s : super end
Protected Class Methods
# File lib/rdf/vocabulary.rb, line 449 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 433 def self.create(uri) # @private @@uri = uri self end
# File lib/rdf/vocabulary.rb, line 353 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
# File lib/rdf/vocabulary.rb, line 363 def method_missing(property, *args, &block) property = RDF::Vocabulary.camelize(property.to_s) if %w(to_ary).include?(property.to_s) super elsif args.empty? && !to_s.empty? Term.intern([to_s, property.to_s].join(''), attributes: {vocab: self}) else super end end
Private Class Methods
# File lib/rdf/vocabulary.rb, line 376 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 400 def [](property) Term.intern([to_s, property.to_s].join(''), attributes: {vocab: self.class}) end
Returns a developer-friendly representation of this vocabulary.
@return [String]
# File lib/rdf/vocabulary.rb, line 427 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 419 def to_s @uri.to_s end
Protected Instance Methods
# File lib/rdf/vocabulary.rb, line 438 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