Skip to content

Commit

Permalink
* Add more OWL accessors.
Browse files Browse the repository at this point in the history
* Make sure `expand_pname` validates the resulting URI and raises an error if invalid.
  • Loading branch information
gkellogg committed Dec 24, 2017
1 parent f17e428 commit 871f6e6
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 39 deletions.
123 changes: 88 additions & 35 deletions lib/rdf/vocabulary.rb
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,28 @@ def strict?; false; end
# Shortcut for `rdfs:subClassOf`, values are interpreted as a {Term}.
# @option options [String, Array<String,Term>] :subPropertyOf
# Shortcut for `rdfs:subPropertyOf`, values are interpreted as a {Term}.
# @option options [String, Array<String,Term>] :allValuesFrom
# Shortcut for `owl:allValuesFrom`, values are interpreted as a {Term}.
# @option options [String, Array<String,Term>] :cardinality
# Shortcut for `owl:cardinality`, values are interpreted as a {Literal}.
# @option options [String, Array<String,Term>] :equivalentClass
# Shortcut for `owl:equivalentClass`, values are interpreted as a {Term}.
# @option options [String, Array<String,Term>] :equivalentProperty
# Shortcut for `owl:equivalentProperty`, values are interpreted as a {Term}.
# @option options [String, Array<String,Term>] :intersectionOf
# Shortcut for `owl:intersectionOf`, values are interpreted as a {Term}.
# @option options [String, Array<String,Term>] :inverseOf
# Shortcut for `owl:inverseOf`, values are interpreted as a {Term}.
# @option options [String, Array<String,Term>] :maxCardinality
# Shortcut for `owl:maxCardinality`, values are interpreted as a {Literal}.
# @option options [String, Array<String,Term>] :minCardinality
# Shortcut for `owl:minCardinality`, values are interpreted as a {Literal}.
# @option options [String, Array<String,Term>] :onProperty
# Shortcut for `owl:onProperty`, values are interpreted as a {Term}.
# @option options [String, Array<String,Term>] :someValuesFrom
# Shortcut for `owl:someValuesFrom`, values are interpreted as a {Term}.
# @option options [String, Array<String,Term>] :unionOf
# Shortcut for `owl:unionOf`, values are interpreted as a {Term}.
# @option options [String, Array<String,Term>] :domainIncludes
# Shortcut for `schema:domainIncludes`, values are interpreted as a {Term}.
# @option options [String, Array<String,Term>] :rangeIncludes
Expand Down Expand Up @@ -189,7 +209,7 @@ def property(*args)
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.intern(vocab: self) {expand_options(options)}
prop = Term.new(vocab: self) {expand_options(options)}
end
prop
end
Expand Down Expand Up @@ -265,6 +285,7 @@ def properties
# @param [String, #to_s] pname
# @return [Term]
# @raise [KeyError] if pname suffix not found in identified vocabulary
# @raise [ArgumentError] if resulting URI is not valid
def expand_pname(pname)
return pname unless pname.is_a?(String) || pname.is_a?(Symbol)
prefix, suffix = pname.to_s.split(":", 2)
Expand All @@ -273,7 +294,7 @@ def expand_pname(pname)
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) rescue nil) || RDF::URI(pname)
(RDF::Vocabulary.find_term(pname) rescue nil) || RDF::URI(pname, validate: true)
end
end

Expand Down Expand Up @@ -442,7 +463,17 @@ def from_graph(graph, url: nil, class_name: nil, extra: nil)
when RDF::RDFS.subPropertyOf then :subPropertyOf
when RDF::URI("http://schema.org/domainIncludes") then :domainIncludes
when RDF::URI("http://schema.org/rangeIncludes") then :rangeIncludes
when RDF::URI("http://www.w3.org/2002/07/owl#allValuesFrom") then :allValuesFrom
when RDF::URI("http://www.w3.org/2002/07/owl#cardinality") then :cardinality
when RDF::URI("http://www.w3.org/2002/07/owl#equivalentClass") then :equivalentClass
when RDF::URI("http://www.w3.org/2002/07/owl#equivalentProperty") then :equivalentProperty
when RDF::URI("http://www.w3.org/2002/07/owl#intersectionOf") then :intersectionOf
when RDF::URI("http://www.w3.org/2002/07/owl#inverseOf") then :inverseOf
when RDF::URI("http://www.w3.org/2002/07/owl#maxCardinality") then :maxCardinality
when RDF::URI("http://www.w3.org/2002/07/owl#minCardinality") then :minCardinality
when RDF::URI("http://www.w3.org/2002/07/owl#onProperty") then :onProperty
when RDF::URI("http://www.w3.org/2002/07/owl#someValuesFrom") then :someValuesFrom
when RDF::URI("http://www.w3.org/2002/07/owl#unionOf") then :unionOf
when RDF::URI("http://www.w3.org/2004/02/skos/core#altLabel") then :altLabel
when RDF::URI("http://www.w3.org/2004/02/skos/core#broader") then :broader
when RDF::URI("http://www.w3.org/2004/02/skos/core#definition") then :definition
Expand Down Expand Up @@ -573,7 +604,7 @@ def method_missing(property, *args, &block)
# Each value treated as a URI or PName
# @return [RDF::List]
def list(*values)
RDF::List[*values.map {|v| expand_pname(v)}]
RDF::List[*values.map {|v| expand_pname(v) rescue RDF::Literal(v)}]
end
private

Expand All @@ -588,36 +619,23 @@ def expand_options(options)
prop_values = []
values = [values] unless values.is_a?(Array)
values.each do |value|
case k
when :type, :subClassOf, :subPropertyof, :domain, :range, :isDefinedBy,
:inverseOf, :domainIncludes, :rangeIncludes,
:broader, :definition, :exactMatch, :hasTopConcept, :inScheme,
:member, :narrower, :related
# String value treated as URI
value = (RDF::Vocabulary.expand_pname(value) rescue value) if value.is_a?(String)
when :label, :comment, :altLabel, :definition, :editorialNote,
:notation, :note, :prefLabel
# String value treated as string literal
value = RDF::Literal(value)
else
v = value.is_a?(Symbol) ? value.to_s : value
value = (RDF::Vocabulary.expand_pname(v) rescue nil) if v.is_a?(String)
value = value.to_uri if value.respond_to?(:to_uri)
unless value.is_a?(RDF::Value) && value.valid?
# Use as most appropriate literal
value = [
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
v = value.is_a?(Symbol) ? value.to_s : value
value = (RDF::Vocabulary.expand_pname(v) rescue nil) if v.is_a?(String)
value = value.to_uri if value.respond_to?(:to_uri)
unless value.is_a?(RDF::Value) && value.valid?
# Use as most appropriate literal
value = [
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
Expand Down Expand Up @@ -744,9 +762,39 @@ module Term
# `rdfs:isDefinedBy` accessor
# @return [Array<Term>]

# @!attribute [r] allValuesFrom
# `owl:allValuesFrom` accessor
# @return [Array<Term>]
# @!attribute [r] cardinality
# `owl:cardinality` accessor
# @return [Array<Literal>]
# @!attribute [r] equivalentClass
# `owl:equivalentClass` accessor
# @return [Array<Term>]
# @!attribute [r] equivalentProperty
# `owl:equivalentProperty` accessor
# @return [Array<Term>]
# @!attribute [r] intersectionOf
# `owl:intersectionOf` accessor
# @return [Array<Term>]
# @!attribute [r] inverseOf
# `owl:inverseOf` accessor
# @return [Array<Term>]
# @!attribute [r] maxCardinality
# `owl:maxCardinality` accessor
# @return [Array<Literal>]
# @!attribute [r] minCardinality
# `owl:minCardinality` accessor
# @return [Array<Literal>]
# @!attribute [r] onProperty
# `owl:onProperty` accessor
# @return [Array<Term>]
# @!attribute [r] someValuesFrom
# `owl:someValuesFrom` accessor
# @return [Array<Term>]
# @!attribute [r] unionOf
# `owl:unionOf` accessor
# @return [Array<Term>]

# @!attribute [r] domainIncludes
# `schema:domainIncludes` accessor
Expand Down Expand Up @@ -927,7 +975,9 @@ def each_statement
RDF::RDFV[p]
when :subClassOf, :subPropertyOf, :domain, :range, :isDefinedBy, :label, :comment
RDF::RDFS[p]
when :inverseOf
when :allValuesFrom, :cardinality, :equivalentClass, :equivalentProperty,
:intersectionOf, :inverseOf, :maxCardinality, :minCardinality,
:onProperty, :someValuesFrom, :unionOf
RDF::OWL[p]
when :domainIncludes, :rangeIncludes
RDF::Vocabulary.find_term("http://schema.org/#{p}")
Expand Down Expand Up @@ -1037,7 +1087,10 @@ def method_missing(method, *args, &block)
# Defaults to URI fragment or path tail
attributes.fetch(method, to_s.split(/[\/\#]/).last)
when :type, :subClassOf, :subPropertyOf, :domain, :range, :isDefinedBy,
:inverseOf, :domainIncludes, :rangeIncludes,
:allValuesFrom, :cardinality, :equivalentClass,:equivalentProperty,
:intersectionOf, :inverseOf, :maxCardinality, :minCardinality,
:onProperty, :someValuesFrom, :unionOf,
:domainIncludes, :rangeIncludes,
:broader, :exactMatch, :hasTopConcept, :inScheme, :member, :narrower, :related
Array(attributes[method])
else
Expand Down
2 changes: 1 addition & 1 deletion spec/vocab_writer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
/term :ClassList/,
/label: "ClassList".freeze/,
/subClassOf: term\(/,
/"owl:unionOf": list\("foo:C1".freeze, "foo:C2".freeze\)/,
/unionOf: list\("foo:C1".freeze, "foo:C2".freeze\)/,
/type: "rdfs:Class".freeze/,
].each do |regexp,|
it "matches #{regexp}" do
Expand Down
91 changes: 88 additions & 3 deletions spec/vocabulary_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,44 @@
it "defines the named property" do
subject.property :foo, label: "Foo"
expect(subject.foo).to eq "http://www.w3.org/2001/XMLSchema#foo"
expect(subject.foo.label).to be_a_literal
expect(subject.foo.label).to eq "Foo"
end

it "defines a property with definition" do
subject.property :foo, definition: %(This class comprises people, either individually or in groups, who have the
potential to perform intentional actions for which they can be held responsible.).freeze
expect(subject.foo.definition).to be_a_literal
expect(subject.foo.definition).to eq %(This class comprises people, either individually or in groups, who have the
potential to perform intentional actions for which they can be held responsible.).freeze
end

it "defines a property with equivalentClass using anonymous term" do
subject.property :foo, "owl:equivalentClass": subject.term(
type: "owl:Restriction",
"owl:onProperty": "http://example/prop",
"owl:cardinality": "1"
)
expect(subject.foo.attributes[:"owl:equivalentClass"]).to be_a(RDF::Vocabulary::Term)
ec = subject.foo.attributes[:"owl:equivalentClass"]
expect(ec.type).to include RDF::OWL.Restriction
expect(ec.attributes[:"owl:onProperty"]).to eql RDF::URI("http://example/prop")
expect(ec.attributes[:"owl:cardinality"]).to eql RDF::Literal(1)
end

it "defines a property with equivalentClass using anonymous term" do
subject.property :foo, "owl:equivalentClass": subject.term(
type: "owl:Restriction",
"owl:onProperty": "http://example/prop",
"owl:cardinality": "1"
)
expect(subject.foo.attributes[:"owl:equivalentClass"]).to be_a(RDF::Vocabulary::Term)
ec = subject.foo.attributes[:"owl:equivalentClass"]
expect(ec.type).to include RDF::OWL.Restriction
expect(ec.attributes[:"owl:onProperty"]).to eql RDF::URI("http://example/prop")
expect(ec.attributes[:"owl:cardinality"]).to eql RDF::Literal(1)
end

it "defines an ontology if symbol is empty" do
subject.property :"", label: "Ontology"
expect(subject[:""]).to eq "http://www.w3.org/2001/XMLSchema#"
Expand Down Expand Up @@ -428,12 +463,12 @@
let(:sub_class_of) {klass.subClassOf.first}
it "has embedded unionOf" do
expect(sub_class_of).to be_a(RDF::Vocabulary::Term)
expect(sub_class_of.attributes[:"owl:unionOf"]).to be_a(Array)
expect(sub_class_of.attributes[:"owl:unionOf"].length).to eql 1
expect(sub_class_of.unionOf).to be_a(Array)
expect(sub_class_of.unionOf.length).to eql 1
end

context "unionOf" do
let(:union_of) {sub_class_of.attributes[:"owl:unionOf"].first}
let(:union_of) {sub_class_of.unionOf.first}

it "has embedded unionOf" do
expect(union_of).to be_a_list
Expand Down Expand Up @@ -559,11 +594,61 @@
"rdfs:range" => {term: RDF::RDFS.range, predicate: RDF::RDFS.range, value: RDF::RDFS.Class},
"rdfs:isDefinedBy" => {term: RDF::RDFS.Class, predicate: RDF::RDFS.isDefinedBy, value: RDF::RDFS.to_uri},

"owl:allValuesFrom" => {
term: RDF::Vocabulary::Term.new(:foo, attributes: {allValuesFrom: RDF::RDFS.Resource}),
predicate: RDF::OWL.allValuesFrom,
value: RDF::RDFS.Resource
},
"owl:cardinality" => {
term: RDF::Vocabulary::Term.new(:foo, attributes: {cardinality: RDF::Literal(1)}),
predicate: RDF::OWL.cardinality,
value: RDF::Literal(1)
},
"owl:equivalentClass" => {
term: RDF::Vocabulary::Term.new(:foo, attributes: {equivalentClass: RDF::RDFS.Resource}),
predicate: RDF::OWL.equivalentClass,
value: RDF::RDFS.Resource
},
"owl:equivalentProperty" => {
term: RDF::Vocabulary::Term.new(:foo, attributes: {equivalentProperty: RDF::RDFS.Resource}),
predicate: RDF::OWL.equivalentProperty,
value: RDF::RDFS.Resource
},
"owl:intersectionOf" => {
term: RDF::Vocabulary::Term.new(:foo, attributes: {intersectionOf: RDF::RDFS.Resource}),
predicate: RDF::OWL.intersectionOf,
value: RDF::RDFS.Resource
},
"owl:inverseOf" => {
term: RDF::Vocabulary::Term.new(:foo, attributes: {inverseOf: RDF::RDFS.Resource}),
predicate: RDF::OWL.inverseOf,
value: RDF::RDFS.Resource
},
"owl:maxCardinality" => {
term: RDF::Vocabulary::Term.new(:foo, attributes: {maxCardinality: RDF::Literal(1)}),
predicate: RDF::OWL.maxCardinality,
value: RDF::Literal(1)
},
"owl:minCardinality" => {
term: RDF::Vocabulary::Term.new(:foo, attributes: {minCardinality: RDF::Literal(1)}),
predicate: RDF::OWL.minCardinality,
value: RDF::Literal(1)
},
"owl:onProperty" => {
term: RDF::Vocabulary::Term.new(:foo, attributes: {onProperty: RDF::RDFS.Resource}),
predicate: RDF::OWL.onProperty,
value: RDF::RDFS.Resource
},
"owl:someValuesFrom" => {
term: RDF::Vocabulary::Term.new(:foo, attributes: {someValuesFrom: RDF::RDFS.Resource}),
predicate: RDF::OWL.someValuesFrom,
value: RDF::RDFS.Resource
},
"owl:unionOf" => {
term: RDF::Vocabulary::Term.new(:foo, attributes: {unionOf: RDF::RDFS.Resource}),
predicate: RDF::OWL.unionOf,
value: RDF::RDFS.Resource
},

"schema:domainIncludes" => {
term: RDF::Vocabulary::Term.new(:foo, attributes: {domainIncludes: RDF::RDFS.Resource}),
Expand Down

0 comments on commit 871f6e6

Please sign in to comment.