Skip to content

Commit 7bf8fa3

Browse files
committed
Document and parse datatype/language maps as part of Term attribute definitions.
1 parent 58391cb commit 7bf8fa3

3 files changed

Lines changed: 127 additions & 128 deletions

File tree

lib/rdf/vocabulary.rb

Lines changed: 64 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ module RDF
4343
# EX = Class.new(RDF::StrictVocabulay("http://example/ns#")) do
4444
# # Ontology definition
4545
# ontology :"http://example/ns#",
46-
# label: "The RDF Example Vocablary".freeze,
47-
# type: "http://www.w3.org/2002/07/owl#Ontology".freeze
46+
# label: "The RDF Example Vocablary",
47+
# type: "http://www.w3.org/2002/07/owl#Ontology"
4848
#
4949
# # Class definitions
5050
# term :Class,
@@ -56,12 +56,12 @@ module RDF
5656
#
5757
# # Property definitions
5858
# property :prop,
59-
# comment: "A description of the property".freeze,
60-
# label: "property".freeze,
61-
# domain: "http://example/ns#Class".freeze,
62-
# range: "rdfs:Literal".freeze,
63-
# isDefinedBy: %(ex:).freeze,
64-
# type: "rdf:Property".freeze
59+
# comment: "A description of the property",
60+
# label: "property",
61+
# domain: "http://example/ns#Class",
62+
# range: "rdfs:Literal",
63+
# isDefinedBy: %(ex:),
64+
# type: "rdf:Property"
6565
# end
6666
#
6767
# @example Method calls are converted to the typical RDF camelcase convention
@@ -203,12 +203,24 @@ def strict?; false; end
203203
#
204204
# @example A simple term definition
205205
# property :domain,
206-
# comment: %(A domain of the subject property.).freeze,
207-
# domain: "rdf:Property".freeze,
208-
# label: "domain".freeze,
209-
# range: "rdfs:Class".freeze,
210-
# isDefinedBy: %(rdfs:).freeze,
211-
# type: "rdf:Property".freeze
206+
# comment: %(A domain of the subject property.),
207+
# domain: "rdf:Property",
208+
# label: "domain",
209+
# range: "rdfs:Class",
210+
# isDefinedBy: %(rdfs:),
211+
# type: "rdf:Property"
212+
#
213+
# @example A term definition with tagged values
214+
# property :actor,
215+
# comment: {en: "Subproperty of as:attributedTo that identifies the primary actor"},
216+
# domain: "https://www.w3.org/ns/activitystreams#Activity",
217+
# label: {en: "actor"},
218+
# range: term(
219+
# type: "http://www.w3.org/2002/07/owl#Class",
220+
# unionOf: list("https://www.w3.org/ns/activitystreams#Object", "https://www.w3.org/ns/activitystreams#Link")
221+
# ),
222+
# subPropertyOf: "https://www.w3.org/ns/activitystreams#attributedTo",
223+
# type: "http://www.w3.org/2002/07/owl#ObjectProperty"
212224
#
213225
# @example A SKOS term with anonymous values
214226
# term: :af,
@@ -227,76 +239,8 @@ def strict?; false; end
227239
# "foaf:name": "Aland Islands"
228240
#
229241
# @param [String, #to_s] name
230-
# @param [Hash{Symbol=>String,Array<String,Term>}] options
231-
# 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.
232-
# @option options [String, Array<String,Term>] :type
233-
# Shortcut for `rdf:type`, values are interpreted as a {Term}.
234-
# @option options [String, Array<String>] :comment
235-
# Shortcut for `rdfs:comment`, values are interpreted as a {Literal}.
236-
# @option options [String, Array<String,Term>] :domain
237-
# Shortcut for `rdfs:domain`, values are interpreted as a {Term}.
238-
# @option options [String, Array<String,Term>] :isDefinedBy
239-
# Shortcut for `rdfs:isDefinedBy`, values are interpreted as a {Term}.
240-
# @option options [String, Array<String>] :label
241-
# Shortcut for `rdfs:label`, values are interpreted as a {Literal}.
242-
# @option options [String, Array<String,Term>] :range
243-
# Shortcut for `rdfs:range`, values are interpreted as a {Term}.
244-
# @option options [String, Array<String,Term>] :subClassOf
245-
# Shortcut for `rdfs:subClassOf`, values are interpreted as a {Term}.
246-
# @option options [String, Array<String,Term>] :subPropertyOf
247-
# Shortcut for `rdfs:subPropertyOf`, values are interpreted as a {Term}.
248-
# @option options [String, Array<String,Term>] :allValuesFrom
249-
# Shortcut for `owl:allValuesFrom`, values are interpreted as a {Term}.
250-
# @option options [String, Array<String,Term>] :cardinality
251-
# Shortcut for `owl:cardinality`, values are interpreted as a {Literal}.
252-
# @option options [String, Array<String,Term>] :equivalentClass
253-
# Shortcut for `owl:equivalentClass`, values are interpreted as a {Term}.
254-
# @option options [String, Array<String,Term>] :equivalentProperty
255-
# Shortcut for `owl:equivalentProperty`, values are interpreted as a {Term}.
256-
# @option options [String, Array<String,Term>] :intersectionOf
257-
# Shortcut for `owl:intersectionOf`, values are interpreted as a {Term}.
258-
# @option options [String, Array<String,Term>] :inverseOf
259-
# Shortcut for `owl:inverseOf`, values are interpreted as a {Term}.
260-
# @option options [String, Array<String,Term>] :maxCardinality
261-
# Shortcut for `owl:maxCardinality`, values are interpreted as a {Literal}.
262-
# @option options [String, Array<String,Term>] :minCardinality
263-
# Shortcut for `owl:minCardinality`, values are interpreted as a {Literal}.
264-
# @option options [String, Array<String,Term>] :onProperty
265-
# Shortcut for `owl:onProperty`, values are interpreted as a {Term}.
266-
# @option options [String, Array<String,Term>] :someValuesFrom
267-
# Shortcut for `owl:someValuesFrom`, values are interpreted as a {Term}.
268-
# @option options [String, Array<String,Term>] :unionOf
269-
# Shortcut for `owl:unionOf`, values are interpreted as a {Term}.
270-
# @option options [String, Array<String,Term>] :domainIncludes
271-
# Shortcut for `schema:domainIncludes`, values are interpreted as a {Term}.
272-
# @option options [String, Array<String,Term>] :rangeIncludes
273-
# Shortcut for `schema:rangeIncludes`, values are interpreted as a {Term}.
274-
# @option options [String, Array<String>] :altLabel
275-
# Shortcut for `skos:altLabel`, values are interpreted as a {Literal}.
276-
# @option options [String, Array<String,Term>] :broader
277-
# Shortcut for `skos:broader`, values are interpreted as a {Term}.
278-
# @option options [String, Array<String>] :definition
279-
# Shortcut for `skos:definition`, values are interpreted as a {Literal}.
280-
# @option options [String, Array<String>] :editorialNote
281-
# Shortcut for `skos:editorialNote`, values are interpreted as a {Literal}.
282-
# @option options [String, Array<String,Term>] :exactMatch
283-
# Shortcut for `skos:exactMatch`, values are interpreted as a {Term}.
284-
# @option options [String, Array<String,Term>] :hasTopConcept
285-
# Shortcut for `skos:hasTopConcept`, values are interpreted as a {Term}.
286-
# @option options [String, Array<String,Term>] :inScheme
287-
# Shortcut for `skos:inScheme`, values are interpreted as a {Term}.
288-
# @option options [String, Array<String,Term>] :member
289-
# Shortcut for `skos:member`, values are interpreted as a {Term}.
290-
# @option options [String, Array<String,Term>] :narrower
291-
# Shortcut for `skos:narrower`, values are interpreted as a {Term}.
292-
# @option options [String, Array<String>] :notation
293-
# Shortcut for `skos:notation`, values are interpreted as a {Literal}.
294-
# @option options [String, Array<String>] :note
295-
# Shortcut for `skos:note`, values are interpreted as a {Literal}.
296-
# @option options [String, Array<String>] :prefLabel
297-
# Shortcut for `skos:prefLabel`, values are interpreted as a {Literal}.
298-
# @option options [String, Array<String,Term>] :related
299-
# Shortcut for `skos:related`, values are interpreted as a {Term}.
242+
# @param [Hash{Symbol=>String,Array<String>}] options
243+
# 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}.
300244
# @return [RDF::Vocabulary::Term]
301245
def property(*args)
302246
case args.length
@@ -309,15 +253,6 @@ def property(*args)
309253
uri_str = [to_s, name.to_s].join('')
310254
URI.cache.delete(uri_str.to_sym) # Clear any previous entry
311255

312-
# Transform attribute keys that are PNames with a warning
313-
# FIXME: add back later
314-
#if !@is_deprecated && options.is_a?(Hash) &&
315-
# options.keys.map(&:to_s).any? {|k| k.include?(':') && !k.match?(/^https?:/)}
316-
#
317-
# @is_deprecated = true
318-
# warn "[DEPRECATION] Vocabulary #{to_uri} includes pname attribute keys, regenerate"
319-
#end
320-
321256
# Term attributes passed in a block for lazy evaluation. This helps to avoid load-time circular dependencies
322257
prop = Term.intern(uri_str, vocab: self, attributes: options || {})
323258
props[name.to_sym] = prop
@@ -985,6 +920,10 @@ module Term
985920
#
986921
# Symbols which are accessors may also be looked up by their associated URI.
987922
#
923+
# 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}.
924+
#
925+
# In general, this accessor is used internally. The {#properties} method interprets these values as {RDF::Value}.
926+
#
988927
# @note lookup by PName is DEPRECATED and will be removed in a future version.
989928
#
990929
# @example looking up term label
@@ -993,23 +932,23 @@ module Term
993932
# RDF::RDFS.Literal.attributes[RDF::RDFS.label] #=> "Literal"
994933
# RDF::RDFS.Literal.attributes["http://www.w3.org/2000/01/rdf-schema#label"] #=> "Literal"
995934
# RDF::RDFS.Literal.attributes[:"http://www.w3.org/2000/01/rdf-schema#label"] #=> "Literal"
996-
# @return [Hash{Symbol,Resource => Term, #to_s}]
935+
# @return [Hash{Symbol => String, Term, Hash{Symbol => String}, Array<String, Term, Hash{Symbol => String}>}]
936+
# @see #properties
997937
attr_reader :attributes
998938

999939
##
1000-
# @overload new(uri, attributes:, **options)
940+
# @overload new(uri, attributes:, vocab:, **options)
1001941
# @param [URI, String, #to_s] uri
1002942
# @param [Vocabulary] vocab Vocabulary of this term.
1003-
# @param [Hash{Symbol => Symbol,Array<String,Term>}] attributes ({})
943+
# @param [Hash{Symbol => String,Term,Hash{Symbol=>String,Array<String>},Array<String>}] attributes ({})
1004944
# 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.
1005945
# @param [Hash{Symbol => Object}] options
1006946
# Options from {URI#initialize}
1007947
#
1008-
# @overload new(attributes:, **options)
1009-
# @param [Hash{Symbol => Object}] options
948+
# @overload new(attributes:, vocab:, **options)
1010949
# @param [Vocabulary] vocab Vocabulary of this term.
1011-
# @param [Hash{Symbol => Symbol,Array<String,Term>}] attributes ({})
1012-
# 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.
950+
# @param [Hash{Symbol => String,Term,Hash{Symbol=>String,Array<String>},Array<String>}] attributes ({})
951+
# 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.
1013952
# @param [Hash{Symbol => Object}] options
1014953
# Options from {URI#initialize}
1015954
def self.new(*args, vocab: nil, attributes: {}, **options)
@@ -1149,6 +1088,7 @@ def other?
11491088
# RDF::RDFS.Literal.properties[:"http://www.w3.org/2000/01/rdf-schema#label"] #=> RDF::Literal("Literal")
11501089
#
11511090
# @return [Hash{Symbol => Array<RDF::Value>}]
1091+
# @see #attribute_value
11521092
def properties
11531093
Hash.new {|hash, key| attribute_value(key)}
11541094
end
@@ -1161,7 +1101,8 @@ def properties
11611101
# Attribute values which are not already a {RDF::Value} (including strings and symbols) are converted by a heuristic loookup as follows:
11621102
#
11631103
# * 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.
1164-
# * {RDF::Literal::Date} if valud,
1104+
# * 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.
1105+
# * {RDF::Literal::Date} if valid,
11651106
# * {RDF::Literal::DateTime} if valid,
11661107
# * {RDF::Literal::Integer} if valid,
11671108
# * {RDF::Literal::Decimal} if valid,
@@ -1179,9 +1120,22 @@ def attribute_value(prop)
11791120
v = value.is_a?(Symbol) ? value.to_s : value
11801121
value = (RDF::Vocabulary.expand_pname(v) rescue nil) if v.is_a?(String) && v.include?(':')
11811122
value = value.to_uri if value.respond_to?(:to_uri)
1182-
unless value.is_a?(RDF::Value) && value.valid?
1123+
value = if value.is_a?(RDF::Value) && value.valid?
1124+
value
1125+
elsif value.is_a?(Hash)
1126+
# type/language map
1127+
value.inject([]) do |memo, (k,v)|
1128+
vv = [v] unless v.is_a?(Array)
1129+
memo << if k.to_s.include?(':')
1130+
dt = RDF::Vocabulary.expand_pname(v) rescue nil
1131+
vv.map {|val| RDF::Literal(val, datatype: dt)}
1132+
else
1133+
vv.map {|val| RDF::Literal(val, language: k)}
1134+
end
1135+
end.flatten.compact.select(&:valid?)
1136+
else
11831137
# Use as most appropriate literal
1184-
value = [
1138+
[
11851139
RDF::Literal::Date,
11861140
RDF::Literal::DateTime,
11871141
RDF::Literal::Integer,
@@ -1196,9 +1150,7 @@ def attribute_value(prop)
11961150
end
11971151
end
11981152
end
1199-
1200-
value
1201-
end
1153+
end.flatten
12021154

12031155
prop_values.length <= 1 ? prop_values.first : prop_values
12041156
end
@@ -1296,24 +1248,24 @@ def to_ruby(indent: "")
12961248
values = [values].compact unless values.is_a?(Array)
12971249
values = values.map do |value|
12981250
if value.is_a?(Literal) && %w(: comment definition notation note editorialNote).include?(k.to_s)
1299-
"%(#{value.to_s.gsub('(', '\(').gsub(')', '\)')}).freeze"
1251+
"%(#{value.to_s.gsub('(', '\(').gsub(')', '\)')})"
13001252
elsif value.node? && value.is_a?(RDF::Vocabulary::Term)
1301-
"#{value.to_ruby(indent: indent + " ")}.freeze"
1253+
"#{value.to_ruby(indent: indent + " ")}"
13021254
elsif value.is_a?(RDF::Term)
1303-
"#{value.to_s.inspect}.freeze"
1255+
"#{value.to_s.inspect}"
13041256
elsif value.is_a?(RDF::List)
13051257
list_elements = value.map do |u|
13061258
if u.uri?
1307-
"#{u.to_s.inspect}.freeze"
1259+
"#{u.to_s.inspect}"
13081260
elsif u.node? && u.respond_to?(:to_ruby)
13091261
u.to_ruby(indent: indent + " ")
13101262
else
1311-
"#{u.to_s.inspect}.freeze"
1263+
"#{u.to_s.inspect}"
13121264
end
13131265
end
13141266
"list(#{list_elements.join(', ')})"
13151267
else
1316-
"#{value.inspect}.freeze"
1268+
"#{value.inspect}"
13171269
end
13181270
end
13191271
"#{k.to_s.include?(':') ? k.to_s.inspect : k}: " +

spec/vocab_writer_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@
8484
/term :ClassList/,
8585
/label: "ClassList"/.freeze,
8686
/subClassOf: term\(/,
87-
%r{unionOf: list\("http://example.org/C1".freeze, "http://example.org/C2".freeze\)},
87+
%r{unionOf: list\("http://example.org/C1", "http://example.org/C2"\)},
8888
%r{type: "http://www.w3.org/2000/01/rdf-schema#Class"}.freeze,
8989
].each do |regexp,|
9090
it "matches #{regexp}" do

0 commit comments

Comments
 (0)