rdf 2.2.12 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,46 +11,46 @@ module RDF
11
11
 
12
12
  # Ontology definition
13
13
  ontology :"http://www.w3.org/2000/01/rdf-schema#",
14
- :"dc11:title" => %(The RDF Schema vocabulary \(RDFS\)).freeze,
15
- :"rdfs:seeAlso" => %(http://www.w3.org/2000/01/rdf-schema-more).freeze,
14
+ "dc11:title": %(The RDF Schema vocabulary \(RDFS\)).freeze,
15
+ "rdfs:seeAlso": %(http://www.w3.org/2000/01/rdf-schema-more).freeze,
16
16
  type: "owl:Ontology".freeze
17
17
 
18
18
  # Class definitions
19
19
  term :Class,
20
20
  comment: %(The class of classes.).freeze,
21
21
  label: "Class".freeze,
22
- :"rdfs:isDefinedBy" => %(rdfs:).freeze,
22
+ isDefinedBy: %(rdfs:).freeze,
23
23
  subClassOf: "rdfs:Resource".freeze,
24
24
  type: "rdfs:Class".freeze
25
25
  term :Container,
26
26
  comment: %(The class of RDF containers.).freeze,
27
27
  label: "Container".freeze,
28
- :"rdfs:isDefinedBy" => %(rdfs:).freeze,
28
+ isDefinedBy: %(rdfs:).freeze,
29
29
  subClassOf: "rdfs:Resource".freeze,
30
30
  type: "rdfs:Class".freeze
31
31
  term :ContainerMembershipProperty,
32
32
  comment: %(The class of container membership properties, rdf:_1, rdf:_2, ...,
33
33
  all of which are sub-properties of 'member'.).freeze,
34
34
  label: "ContainerMembershipProperty".freeze,
35
- :"rdfs:isDefinedBy" => %(rdfs:).freeze,
35
+ isDefinedBy: %(rdfs:).freeze,
36
36
  subClassOf: "rdf:Property".freeze,
37
37
  type: "rdfs:Class".freeze
38
38
  term :Datatype,
39
39
  comment: %(The class of RDF datatypes.).freeze,
40
40
  label: "Datatype".freeze,
41
- :"rdfs:isDefinedBy" => %(rdfs:).freeze,
41
+ isDefinedBy: %(rdfs:).freeze,
42
42
  subClassOf: "rdfs:Class".freeze,
43
43
  type: "rdfs:Class".freeze
44
44
  term :Literal,
45
45
  comment: %(The class of literal values, eg. textual strings and integers.).freeze,
46
46
  label: "Literal".freeze,
47
- :"rdfs:isDefinedBy" => %(rdfs:).freeze,
47
+ isDefinedBy: %(rdfs:).freeze,
48
48
  subClassOf: "rdfs:Resource".freeze,
49
49
  type: "rdfs:Class".freeze
50
50
  term :Resource,
51
51
  comment: %(The class resource, everything.).freeze,
52
52
  label: "Resource".freeze,
53
- :"rdfs:isDefinedBy" => %(rdfs:).freeze,
53
+ isDefinedBy: %(rdfs:).freeze,
54
54
  type: "rdfs:Class".freeze
55
55
 
56
56
  # Property definitions
@@ -59,21 +59,21 @@ module RDF
59
59
  domain: "rdfs:Resource".freeze,
60
60
  label: "comment".freeze,
61
61
  range: "rdfs:Literal".freeze,
62
- :"rdfs:isDefinedBy" => %(rdfs:).freeze,
62
+ isDefinedBy: %(rdfs:).freeze,
63
63
  type: "rdf:Property".freeze
64
64
  property :domain,
65
65
  comment: %(A domain of the subject property.).freeze,
66
66
  domain: "rdf:Property".freeze,
67
67
  label: "domain".freeze,
68
68
  range: "rdfs:Class".freeze,
69
- :"rdfs:isDefinedBy" => %(rdfs:).freeze,
69
+ isDefinedBy: %(rdfs:).freeze,
70
70
  type: "rdf:Property".freeze
71
71
  property :isDefinedBy,
72
72
  comment: %(The defininition of the subject resource.).freeze,
73
73
  domain: "rdfs:Resource".freeze,
74
74
  label: "isDefinedBy".freeze,
75
75
  range: "rdfs:Resource".freeze,
76
- :"rdfs:isDefinedBy" => %(rdfs:).freeze,
76
+ isDefinedBy: %(rdfs:).freeze,
77
77
  subPropertyOf: "rdfs:seeAlso".freeze,
78
78
  type: "rdf:Property".freeze
79
79
  property :label,
@@ -81,42 +81,42 @@ module RDF
81
81
  domain: "rdfs:Resource".freeze,
82
82
  label: "label".freeze,
83
83
  range: "rdfs:Literal".freeze,
84
- :"rdfs:isDefinedBy" => %(rdfs:).freeze,
84
+ isDefinedBy: %(rdfs:).freeze,
85
85
  type: "rdf:Property".freeze
86
86
  property :member,
87
87
  comment: %(A member of the subject resource.).freeze,
88
88
  domain: "rdfs:Resource".freeze,
89
89
  label: "member".freeze,
90
90
  range: "rdfs:Resource".freeze,
91
- :"rdfs:isDefinedBy" => %(rdfs:).freeze,
91
+ isDefinedBy: %(rdfs:).freeze,
92
92
  type: "rdf:Property".freeze
93
93
  property :range,
94
94
  comment: %(A range of the subject property.).freeze,
95
95
  domain: "rdf:Property".freeze,
96
96
  label: "range".freeze,
97
97
  range: "rdfs:Class".freeze,
98
- :"rdfs:isDefinedBy" => %(rdfs:).freeze,
98
+ isDefinedBy: %(rdfs:).freeze,
99
99
  type: "rdf:Property".freeze
100
100
  property :seeAlso,
101
101
  comment: %(Further information about the subject resource.).freeze,
102
102
  domain: "rdfs:Resource".freeze,
103
103
  label: "seeAlso".freeze,
104
104
  range: "rdfs:Resource".freeze,
105
- :"rdfs:isDefinedBy" => %(rdfs:).freeze,
105
+ isDefinedBy: %(rdfs:).freeze,
106
106
  type: "rdf:Property".freeze
107
107
  property :subClassOf,
108
108
  comment: %(The subject is a subclass of a class.).freeze,
109
109
  domain: "rdfs:Class".freeze,
110
110
  label: "subClassOf".freeze,
111
111
  range: "rdfs:Class".freeze,
112
- :"rdfs:isDefinedBy" => %(rdfs:).freeze,
112
+ isDefinedBy: %(rdfs:).freeze,
113
113
  type: "rdf:Property".freeze
114
114
  property :subPropertyOf,
115
115
  comment: %(The subject is a subproperty of a property.).freeze,
116
116
  domain: "rdf:Property".freeze,
117
117
  label: "subPropertyOf".freeze,
118
118
  range: "rdf:Property".freeze,
119
- :"rdfs:isDefinedBy" => %(rdfs:).freeze,
119
+ isDefinedBy: %(rdfs:).freeze,
120
120
  type: "rdf:Property".freeze
121
121
  end
122
122
  end
@@ -16,45 +16,45 @@ module RDF
16
16
 
17
17
  # Ontology definition
18
18
  ontology :"http://www.w3.org/1999/02/22-rdf-syntax-ns#",
19
- :"dc11:description" => %(This is the RDF Schema for the RDF vocabulary terms in the RDF Namespace, defined in RDF 1.1 Concepts.).freeze,
20
- :"dc11:title" => %(The RDF Concepts Vocabulary \(RDF\)).freeze,
19
+ "dc11:description": %(This is the RDF Schema for the RDF vocabulary terms in the RDF Namespace, defined in RDF 1.1 Concepts.).freeze,
20
+ "dc11:title": %(The RDF Concepts Vocabulary \(RDF\)).freeze,
21
21
  type: "owl:Ontology".freeze
22
22
 
23
23
  # Class definitions
24
24
  term :Alt,
25
25
  comment: %(The class of containers of alternatives.).freeze,
26
26
  label: "Alt".freeze,
27
- :"rdfs:isDefinedBy" => %(rdf:).freeze,
27
+ isDefinedBy: %(rdf:).freeze,
28
28
  subClassOf: "rdfs:Container".freeze,
29
29
  type: "rdfs:Class".freeze
30
30
  term :Bag,
31
31
  comment: %(The class of unordered containers.).freeze,
32
32
  label: "Bag".freeze,
33
- :"rdfs:isDefinedBy" => %(rdf:).freeze,
33
+ isDefinedBy: %(rdf:).freeze,
34
34
  subClassOf: "rdfs:Container".freeze,
35
35
  type: "rdfs:Class".freeze
36
36
  term :List,
37
37
  comment: %(The class of RDF Lists.).freeze,
38
38
  label: "List".freeze,
39
- :"rdfs:isDefinedBy" => %(rdf:).freeze,
39
+ isDefinedBy: %(rdf:).freeze,
40
40
  subClassOf: "rdfs:Resource".freeze,
41
41
  type: "rdfs:Class".freeze
42
42
  term :Property,
43
43
  comment: %(The class of RDF properties.).freeze,
44
44
  label: "Property".freeze,
45
- :"rdfs:isDefinedBy" => %(rdf:).freeze,
45
+ isDefinedBy: %(rdf:).freeze,
46
46
  subClassOf: "rdfs:Resource".freeze,
47
47
  type: "rdfs:Class".freeze
48
48
  term :Seq,
49
49
  comment: %(The class of ordered containers.).freeze,
50
50
  label: "Seq".freeze,
51
- :"rdfs:isDefinedBy" => %(rdf:).freeze,
51
+ isDefinedBy: %(rdf:).freeze,
52
52
  subClassOf: "rdfs:Container".freeze,
53
53
  type: "rdfs:Class".freeze
54
54
  term :Statement,
55
55
  comment: %(The class of RDF statements.).freeze,
56
56
  label: "Statement".freeze,
57
- :"rdfs:isDefinedBy" => %(rdf:).freeze,
57
+ isDefinedBy: %(rdf:).freeze,
58
58
  subClassOf: "rdfs:Resource".freeze,
59
59
  type: "rdfs:Class".freeze
60
60
 
@@ -64,77 +64,77 @@ module RDF
64
64
  domain: "rdf:List".freeze,
65
65
  label: "first".freeze,
66
66
  range: "rdfs:Resource".freeze,
67
- :"rdfs:isDefinedBy" => %(rdf:).freeze,
67
+ isDefinedBy: %(rdf:).freeze,
68
68
  type: "rdf:Property".freeze
69
69
  property :object,
70
70
  comment: %(The object of the subject RDF statement.).freeze,
71
71
  domain: "rdf:Statement".freeze,
72
72
  label: "object".freeze,
73
73
  range: "rdfs:Resource".freeze,
74
- :"rdfs:isDefinedBy" => %(rdf:).freeze,
74
+ isDefinedBy: %(rdf:).freeze,
75
75
  type: "rdf:Property".freeze
76
76
  property :predicate,
77
77
  comment: %(The predicate of the subject RDF statement.).freeze,
78
78
  domain: "rdf:Statement".freeze,
79
79
  label: "predicate".freeze,
80
80
  range: "rdfs:Resource".freeze,
81
- :"rdfs:isDefinedBy" => %(rdf:).freeze,
81
+ isDefinedBy: %(rdf:).freeze,
82
82
  type: "rdf:Property".freeze
83
83
  property :rest,
84
84
  comment: %(The rest of the subject RDF list after the first item.).freeze,
85
85
  domain: "rdf:List".freeze,
86
86
  label: "rest".freeze,
87
87
  range: "rdf:List".freeze,
88
- :"rdfs:isDefinedBy" => %(rdf:).freeze,
88
+ isDefinedBy: %(rdf:).freeze,
89
89
  type: "rdf:Property".freeze
90
90
  property :subject,
91
91
  comment: %(The subject of the subject RDF statement.).freeze,
92
92
  domain: "rdf:Statement".freeze,
93
93
  label: "subject".freeze,
94
94
  range: "rdfs:Resource".freeze,
95
- :"rdfs:isDefinedBy" => %(rdf:).freeze,
95
+ isDefinedBy: %(rdf:).freeze,
96
96
  type: "rdf:Property".freeze
97
97
  property :type,
98
98
  comment: %(The subject is an instance of a class.).freeze,
99
99
  domain: "rdfs:Resource".freeze,
100
100
  label: "type".freeze,
101
101
  range: "rdfs:Class".freeze,
102
- :"rdfs:isDefinedBy" => %(rdf:).freeze,
102
+ isDefinedBy: %(rdf:).freeze,
103
103
  type: "rdf:Property".freeze
104
104
  property :value,
105
105
  comment: %(Idiomatic property used for structured values.).freeze,
106
106
  domain: "rdfs:Resource".freeze,
107
107
  label: "value".freeze,
108
108
  range: "rdfs:Resource".freeze,
109
- :"rdfs:isDefinedBy" => %(rdf:).freeze,
109
+ isDefinedBy: %(rdf:).freeze,
110
110
  type: "rdf:Property".freeze
111
111
 
112
112
  # Datatype definitions
113
113
  term :HTML,
114
114
  comment: %(The datatype of RDF literals storing fragments of HTML content).freeze,
115
115
  label: "HTML".freeze,
116
- :"rdfs:isDefinedBy" => %(rdf:).freeze,
117
- :"rdfs:seeAlso" => %(http://www.w3.org/TR/rdf11-concepts/#section-html).freeze,
116
+ isDefinedBy: %(rdf:).freeze,
117
+ "rdfs:seeAlso": %(http://www.w3.org/TR/rdf11-concepts/#section-html).freeze,
118
118
  subClassOf: "rdfs:Literal".freeze,
119
119
  type: "rdfs:Datatype".freeze
120
120
  term :PlainLiteral,
121
121
  comment: %(The class of plain \(i.e. untyped\) literal values, as used in RIF and OWL 2).freeze,
122
122
  label: "PlainLiteral".freeze,
123
- :"rdfs:isDefinedBy" => %(rdf:).freeze,
124
- :"rdfs:seeAlso" => %(http://www.w3.org/TR/rdf-plain-literal/).freeze,
123
+ isDefinedBy: %(rdf:).freeze,
124
+ "rdfs:seeAlso": %(http://www.w3.org/TR/rdf-plain-literal/).freeze,
125
125
  subClassOf: "rdfs:Literal".freeze,
126
126
  type: "rdfs:Datatype".freeze
127
127
  term :XMLLiteral,
128
128
  comment: %(The datatype of XML literal values.).freeze,
129
129
  label: "XMLLiteral".freeze,
130
- :"rdfs:isDefinedBy" => %(rdf:).freeze,
130
+ isDefinedBy: %(rdf:).freeze,
131
131
  subClassOf: "rdfs:Literal".freeze,
132
132
  type: "rdfs:Datatype".freeze
133
133
  term :langString,
134
134
  comment: %(The datatype of language-tagged string values).freeze,
135
135
  label: "langString".freeze,
136
- :"rdfs:isDefinedBy" => %(rdf:).freeze,
137
- :"rdfs:seeAlso" => %(http://www.w3.org/TR/rdf11-concepts/#section-Graph-Literal).freeze,
136
+ isDefinedBy: %(rdf:).freeze,
137
+ "rdfs:seeAlso": %(http://www.w3.org/TR/rdf11-concepts/#section-Graph-Literal).freeze,
138
138
  subClassOf: "rdfs:Literal".freeze,
139
139
  type: "rdfs:Datatype".freeze
140
140
 
@@ -157,7 +157,7 @@ module RDF
157
157
  term :nil,
158
158
  comment: %(The empty list, with no items in it. If the rest of a list is nil then the list has no more items in it.).freeze,
159
159
  label: "nil".freeze,
160
- :"rdfs:isDefinedBy" => %(rdf:).freeze,
160
+ isDefinedBy: %(rdf:).freeze,
161
161
  type: "rdf:List".freeze
162
162
  term :nodeID,
163
163
  comment: %(RDF/XML Blank Node identifier).freeze,
@@ -5,6 +5,44 @@ module RDF
5
5
  ##
6
6
  # Vocabulary format specification. This can be used to generate a Ruby class definition from a loaded vocabulary.
7
7
  #
8
+ # Definitions can include recursive term definitions, when the value of a property is a blank-node term. They can also include list definitions, to provide a reasonable way to represent `owl:unionOf`-type relationships.
9
+ #
10
+ # @example a simple term definition
11
+ # property :comment,
12
+ # comment: %(A description of the subject resource.).freeze,
13
+ # domain: "rdfs:Resource".freeze,
14
+ # label: "comment".freeze,
15
+ # range: "rdfs:Literal".freeze,
16
+ # isDefinedBy: %(rdfs:).freeze,
17
+ # type: "rdf:Property".freeze
18
+ #
19
+ # @example an embedded skos:Concept
20
+ # term :ad,
21
+ # exactMatch: [term(
22
+ # type: "skos:Concept".freeze,
23
+ # inScheme: "country:iso3166-1-alpha-2".freeze,
24
+ # notation: %(ad).freeze
25
+ # ), term(
26
+ # type: "skos:Concept".freeze,
27
+ # inScheme: "country:iso3166-1-alpha-3".freeze,
28
+ # notation: %(and).freeze
29
+ # )],
30
+ # "foaf:name": "Andorra".freeze,
31
+ # isDefinedBy: "country:".freeze,
32
+ # type: "http://sweet.jpl.nasa.gov/2.3/humanJurisdiction.owl#Country".freeze
33
+ #
34
+ # @example owl:unionOf
35
+ # property :duration,
36
+ # comment: %(The duration of a track or a signal in ms).freeze,
37
+ # domain: term(
38
+ # "owl:unionOf": list("mo:Track".freeze, "mo:Signal".freeze),
39
+ # type: "owl:Class".freeze
40
+ # ),
41
+ # isDefinedBy: "mo:".freeze,
42
+ # "mo:level": "1".freeze,
43
+ # range: "xsd:float".freeze,
44
+ # type: "owl:DatatypeProperty".freeze,
45
+ # "vs:term_status": "testing".freeze
8
46
  class Vocabulary
9
47
  class Format < RDF::Format
10
48
  content_encoding 'utf-8'
@@ -171,24 +209,28 @@ module RDF
171
209
 
172
210
  components = [" #{op} #{name.to_sym.inspect}"]
173
211
  attributes.keys.sort_by(&:to_s).map(&:to_sym).each do |key|
174
- next if key == :vocab
175
212
  value = Array(attributes[key])
176
- component = key.inspect.start_with?(':"') ? "#{key.inspect} => " : "#{key.to_s}: "
213
+ component = key.inspect.start_with?(':"') ? "#{key.to_s.inspect}: " : "#{key}: "
177
214
  value = value.first if value.length == 1
178
215
  component << if value.is_a?(Array)
179
- '[' + value.map {|v| serialize_value(v, key)}.sort.join(", ") + "]"
216
+ '[' + value.map {|v| serialize_value(v, key, indent: " ")}.sort.join(", ") + "]"
180
217
  else
181
- serialize_value(value, key)
218
+ serialize_value(value, key, indent: " ")
182
219
  end
183
220
  components << component
184
221
  end
185
222
  @output.puts components.join(",\n ")
186
223
  end
187
224
 
188
- def serialize_value(value, key)
189
- case key.to_s
190
- when "comment", /:/
191
- "%(#{value.gsub('(', '\(').gsub(')', '\)')}).freeze"
225
+ def serialize_value(value, key, indent: "")
226
+ if value.is_a?(Literal) && %w(: comment definition notation note editorialNote).include?(key.to_s)
227
+ "%(#{value.to_s.gsub('(', '\(').gsub(')', '\)')}).freeze"
228
+ elsif value.is_a?(RDF::URI)
229
+ "#{value.pname.inspect}.freeze"
230
+ elsif value.is_a?(RDF::Vocabulary::Term)
231
+ value.to_ruby(indent: indent + " ")
232
+ elsif value.is_a?(RDF::Term)
233
+ "#{value.to_s.inspect}.freeze"
192
234
  else
193
235
  "#{value.inspect}.freeze"
194
236
  end
@@ -40,8 +40,6 @@ module RDF
40
40
  # foaf['family_name'] #=> RDF::URI("http://xmlns.com/foaf/0.1/family_name")
41
41
  # foaf[:family_name] #=> RDF::URI("http://xmlns.com/foaf/0.1/family_name")
42
42
  #
43
- #
44
- #
45
43
  # @example Generating RDF from a vocabulary definition
46
44
  # graph = RDF::Graph.new << RDF::RDFS.to_enum
47
45
  # graph.dump(:ntriples)
@@ -91,41 +89,128 @@ module RDF
91
89
  # @overload property(name, options)
92
90
  # Defines a new property or class in the vocabulary.
93
91
  #
92
+ # @example A simple term definition
93
+ # property :domain,
94
+ # comment: %(A domain of the subject property.).freeze,
95
+ # domain: "rdf:Property".freeze,
96
+ # label: "domain".freeze,
97
+ # range: "rdfs:Class".freeze,
98
+ # isDefinedBy: %(rdfs:).freeze,
99
+ # type: "rdf:Property".freeze
100
+ #
101
+ # @example A SKOS term with anonymous values
102
+ # term: :af,
103
+ # type: "jur:Country",
104
+ # isDefinedBy: "http://eulersharp.sourceforge.net/2003/03swap/countries#",
105
+ # "skos:exactMatch": [
106
+ # Term.new(
107
+ # type: "skos:Concept",
108
+ # inScheme: "iso3166-1-alpha-2",
109
+ # notation: "ax"),
110
+ # Term.new(
111
+ # type: "skos:Concept",
112
+ # inScheme: "iso3166-1-alpha-3",
113
+ # notation: "ala")
114
+ # ],
115
+ # "foaf:name": "Aland Islands"
116
+ #
94
117
  # @param [String, #to_s] name
95
- # @param [Hash{Symbol => Object}] options
96
- # 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.
97
- # @option options [String, Array<String>] :label
98
- # Shortcut for `rdfs:label`, values are String interpreted as a {Literal}.
118
+ # @param [Hash{Symbol=>String,Array<String,Term>}] options
119
+ # 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.
120
+ # @option options [String, Array<String,Term>] :type
121
+ # Shortcut for `rdf:type`, values are interpreted as a {Term}.
99
122
  # @option options [String, Array<String>] :comment
100
- # Shortcut for `rdfs:comment`, values are String interpreted as a {Literal}.
101
- # @option options [String, Array<String>] :subClassOf
102
- # Shortcut for `rdfs:subClassOf`, values are String interpreted as a {URI}.
103
- # @option options [String, Array<String>] :subPropertyOf
104
- # Shortcut for `rdfs:subPropertyOf`, values are String interpreted as a {URI}.
105
- # @option options [String, Array<String>] :domain
106
- # Shortcut for `rdfs:domain`, values are String interpreted as a {URI}.
107
- # @option options [String, Array<String>] :range
108
- # Shortcut for `rdfs:range`, values are String interpreted as a {URI}.
109
- # @option options [String, Array<String>] :type
110
- # Shortcut for `rdf:type`, values are String interpreted as a {URI}.
123
+ # Shortcut for `rdfs:comment`, values are interpreted as a {Literal}.
124
+ # @option options [String, Array<String,Term>] :domain
125
+ # Shortcut for `rdfs:domain`, values are interpreted as a {Term}.
126
+ # @option options [String, Array<String,Term>] :isDefinedBy
127
+ # Shortcut for `rdfs:isDefinedBy`, values are interpreted as a {Term}.
128
+ # @option options [String, Array<String>] :label
129
+ # Shortcut for `rdfs:label`, values are interpreted as a {Literal}.
130
+ # @option options [String, Array<String,Term>] :range
131
+ # Shortcut for `rdfs:range`, values are interpreted as a {Term}.
132
+ # @option options [String, Array<String,Term>] :subClassOf
133
+ # Shortcut for `rdfs:subClassOf`, values are interpreted as a {Term}.
134
+ # @option options [String, Array<String,Term>] :subPropertyOf
135
+ # Shortcut for `rdfs:subPropertyOf`, values are interpreted as a {Term}.
136
+ # @option options [String, Array<String,Term>] :allValuesFrom
137
+ # Shortcut for `owl:allValuesFrom`, values are interpreted as a {Term}.
138
+ # @option options [String, Array<String,Term>] :cardinality
139
+ # Shortcut for `owl:cardinality`, values are interpreted as a {Literal}.
140
+ # @option options [String, Array<String,Term>] :equivalentClass
141
+ # Shortcut for `owl:equivalentClass`, values are interpreted as a {Term}.
142
+ # @option options [String, Array<String,Term>] :equivalentProperty
143
+ # Shortcut for `owl:equivalentProperty`, values are interpreted as a {Term}.
144
+ # @option options [String, Array<String,Term>] :intersectionOf
145
+ # Shortcut for `owl:intersectionOf`, values are interpreted as a {Term}.
146
+ # @option options [String, Array<String,Term>] :inverseOf
147
+ # Shortcut for `owl:inverseOf`, values are interpreted as a {Term}.
148
+ # @option options [String, Array<String,Term>] :maxCardinality
149
+ # Shortcut for `owl:maxCardinality`, values are interpreted as a {Literal}.
150
+ # @option options [String, Array<String,Term>] :minCardinality
151
+ # Shortcut for `owl:minCardinality`, values are interpreted as a {Literal}.
152
+ # @option options [String, Array<String,Term>] :onProperty
153
+ # Shortcut for `owl:onProperty`, values are interpreted as a {Term}.
154
+ # @option options [String, Array<String,Term>] :someValuesFrom
155
+ # Shortcut for `owl:someValuesFrom`, values are interpreted as a {Term}.
156
+ # @option options [String, Array<String,Term>] :unionOf
157
+ # Shortcut for `owl:unionOf`, values are interpreted as a {Term}.
158
+ # @option options [String, Array<String,Term>] :domainIncludes
159
+ # Shortcut for `schema:domainIncludes`, values are interpreted as a {Term}.
160
+ # @option options [String, Array<String,Term>] :rangeIncludes
161
+ # Shortcut for `schema:rangeIncludes`, values are interpreted as a {Term}.
162
+ # @option options [String, Array<String>] :altLabel
163
+ # Shortcut for `skos:altLabel`, values are interpreted as a {Literal}.
164
+ # @option options [String, Array<String,Term>] :broader
165
+ # Shortcut for `skos:broader`, values are interpreted as a {Term}.
166
+ # @option options [String, Array<String>] :definition
167
+ # Shortcut for `skos:definition`, values are interpreted as a {Literal}.
168
+ # @option options [String, Array<String>] :editorialNote
169
+ # Shortcut for `skos:editorialNote`, values are interpreted as a {Literal}.
170
+ # @option options [String, Array<String,Term>] :exactMatch
171
+ # Shortcut for `skos:exactMatch`, values are interpreted as a {Term}.
172
+ # @option options [String, Array<String,Term>] :hasTopConcept
173
+ # Shortcut for `skos:hasTopConcept`, values are interpreted as a {Term}.
174
+ # @option options [String, Array<String,Term>] :inScheme
175
+ # Shortcut for `skos:inScheme`, values are interpreted as a {Term}.
176
+ # @option options [String, Array<String,Term>] :member
177
+ # Shortcut for `skos:member`, values are interpreted as a {Term}.
178
+ # @option options [String, Array<String,Term>] :narrower
179
+ # Shortcut for `skos:narrower`, values are interpreted as a {Term}.
180
+ # @option options [String, Array<String>] :notation
181
+ # Shortcut for `skos:notation`, values are interpreted as a {Literal}.
182
+ # @option options [String, Array<String>] :note
183
+ # Shortcut for `skos:note`, values are interpreted as a {Literal}.
184
+ # @option options [String, Array<String>] :prefLabel
185
+ # Shortcut for `skos:prefLabel`, values are interpreted as a {Literal}.
186
+ # @option options [String, Array<String,Term>] :related
187
+ # Shortcut for `skos:related`, values are interpreted as a {Term}.
111
188
  # @return [RDF::Vocabulary::Term]
112
189
  def property(*args)
113
190
  case args.length
114
191
  when 0
115
- Term.intern("#{self}property", attributes: {label: "property", vocab: self})
192
+ Term.intern("#{self}property", vocab: self, attributes: {})
116
193
  else
117
- name, options = args
118
- options = {label: name.to_s, vocab: self}.merge(options || {})
119
- uri_str = [to_s, name.to_s].join('')
120
- Term.cache.delete(uri_str.to_sym) # Clear any previous entry
121
- prop = Term.intern(uri_str, attributes: options)
122
- props[name.to_sym] = prop
123
-
124
- # If name is empty, also treat it as the ontology
125
- @ontology ||= prop if name.to_s.empty?
126
-
127
- # Define an accessor, except for problematic properties
128
- (class << self; self; end).send(:define_method, name) { prop } unless %w(property hash).include?(name.to_s)
194
+ name = args.shift unless args.first.is_a?(Hash)
195
+ options = args.last
196
+ if name
197
+ uri_str = [to_s, name.to_s].join('')
198
+ URI.cache.delete(uri_str.to_sym) # Clear any previous entry
199
+
200
+ # Term attributes passed in a block for lazy evaluation. This helps to avoid load-time circular dependencies
201
+ prop = Term.intern(uri_str, vocab: self, attributes: options)
202
+ props[name.to_sym] = prop
203
+
204
+ # If name is empty, also treat it as the ontology
205
+ @ontology ||= prop if name.to_s.empty?
206
+
207
+ # Define an accessor, except for problematic properties
208
+ (class << self; self; end).send(:define_method, name) { prop } unless %w(property hash).include?(name.to_s)
209
+ else
210
+ # Define the term without a name
211
+ # Term attributes passed in a block for lazy evaluation. This helps to avoid load-time circular dependencies
212
+ prop = Term.new(vocab: self, attributes: options)
213
+ end
129
214
  prop
130
215
  end
131
216
  end
@@ -145,13 +230,27 @@ module RDF
145
230
  # @param [String, #to_s] uri
146
231
  # The URI of the ontology.
147
232
  # @param [Hash{Symbol => Object}] options
148
- # 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.
149
- # @option options [String, Array<String>] :label
150
- # Shortcut for `rdfs:label`, values are String interpreted as a {Literal}.
233
+ # See {property}
234
+ # @param [Hash{Symbol=>String,Array<String,Term>}] options
235
+ # 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.
236
+ # @option options [String, Array<String,Term>] :type
237
+ # Shortcut for `rdf:type`, values are interpreted as a {Term}.
151
238
  # @option options [String, Array<String>] :comment
152
- # Shortcut for `rdfs:comment`, values are String interpreted as a {Literal}.
153
- # @option options [String, Array<String>] :type
154
- # Shortcut for `rdf:type`, values are String interpreted as a {URI}.
239
+ # Shortcut for `rdfs:comment`, values are interpreted as a {Literal}.
240
+ # @option options [String, Array<String,Term>] :isDefinedBy
241
+ # Shortcut for `rdfs:isDefinedBy`, values are interpreted as a {Term}.
242
+ # @option options [String, Array<String>] :label
243
+ # Shortcut for `rdfs:label`, values are interpreted as a {Literal}.
244
+ # @option options [String, Array<String>] :altLabel
245
+ # Shortcut for `skos:altLabel`, values are interpreted as a {Literal}.
246
+ # @option options [String, Array<String>] :definition
247
+ # Shortcut for `skos:definition`, values are interpreted as a {Literal}.
248
+ # @option options [String, Array<String>] :editorialNote
249
+ # Shortcut for `skos:editorialNote`, values are interpreted as a {Literal}.
250
+ # @option options [String, Array<String>] :note
251
+ # Shortcut for `skos:note`, values are interpreted as a {Literal}.
252
+ # @option options [String, Array<String>] :prefLabel
253
+ # Shortcut for `skos:prefLabel`, values are interpreted as a {Literal}.
155
254
  # @return [RDF::Vocabulary::Term]
156
255
  #
157
256
  # @note If the ontology URI has the vocabulary namespace URI as a prefix, it may also be defined using `#property` or `#term`
@@ -161,9 +260,9 @@ module RDF
161
260
  @ontology
162
261
  else
163
262
  uri, options = args
164
- options = {vocab: self}.merge(options || {})
165
- Term.cache.delete(uri.to_s.to_sym) # Clear any previous entry
166
- @ontology = Term.intern(uri.to_s, attributes: options)
263
+ URI.cache.delete(uri.to_s.to_sym) # Clear any previous entry
264
+ # Term attributes passed in a block for lazy evaluation. This helps to avoid load-time circular dependencies
265
+ @ontology = Term.intern(uri.to_s, vocab: self, attributes: options)
167
266
 
168
267
  # If the URI is the same as the vocabulary namespace, also define it as a term
169
268
  props[:""] ||= @ontology if self.to_s == uri.to_s
@@ -184,16 +283,18 @@ module RDF
184
283
  # Attempt to expand a Compact IRI/PName/QName using loaded vocabularies
185
284
  #
186
285
  # @param [String, #to_s] pname
187
- # @return [RDF::URI]
286
+ # @return [Term]
188
287
  # @raise [KeyError] if pname suffix not found in identified vocabulary
288
+ # @raise [ArgumentError] if resulting URI is not valid
189
289
  def expand_pname(pname)
290
+ return pname unless pname.is_a?(String) || pname.is_a?(Symbol)
190
291
  prefix, suffix = pname.to_s.split(":", 2)
191
292
  if prefix == "rdf"
192
293
  RDF[suffix]
193
294
  elsif vocab = RDF::Vocabulary.each.detect {|v| v.__name__ && v.__prefix__ == prefix.to_sym}
194
295
  suffix.to_s.empty? ? vocab.to_uri : vocab[suffix]
195
296
  else
196
- (RDF::Vocabulary.find_term(pname) rescue nil) || RDF::URI(pname)
297
+ (RDF::Vocabulary.find_term(pname) rescue nil) || RDF::URI(pname, validate: true)
197
298
  end
198
299
  end
199
300
 
@@ -238,7 +339,7 @@ module RDF
238
339
  if props.has_key?(property.to_sym)
239
340
  props[property.to_sym]
240
341
  else
241
- Term.intern([to_s, property.to_s].join(''), attributes: {vocab: self})
342
+ Term.intern([to_s, property.to_s].join(''), vocab: self, attributes: {})
242
343
  end
243
344
  end
244
345
 
@@ -340,50 +441,111 @@ module RDF
340
441
 
341
442
  ont_url = url.to_s.sub(%r([/#]$), '')
342
443
  term_defs = {}
444
+ embedded_defs = {}
343
445
  graph.each do |statement|
344
- next unless statement.subject.uri?
345
- next unless statement.subject.start_with?(url) || statement.subject == ont_url
346
- name = statement.subject.to_s[url.to_s.length..-1].to_s
347
- term = (term_defs[name.to_sym] ||= {})
348
- term[:uri] = statement.subject if name.empty?
446
+ #next unless statement.subject.uri?
447
+ if statement.subject.start_with?(url) || statement.subject == ont_url
448
+ name = statement.subject.to_s[url.to_s.length..-1].to_s
449
+ term = (term_defs[name.to_sym] ||= {})
450
+ else
451
+ # subject is not a URI or is not associated with the vocabulary
452
+ term = (embedded_defs[statement.subject] ||= {})
453
+ end
349
454
 
350
455
  key = case statement.predicate
351
456
  when RDF.type then :type
352
- when RDF::RDFS.subClassOf then :subClassOf
353
- when RDF::RDFS.subPropertyOf then :subPropertyOf
354
- when RDF::RDFS.range then :range
355
- when RDF::RDFS.domain then :domain
356
457
  when RDF::RDFS.comment then :comment
458
+ when RDF::RDFS.domain then :domain
459
+ when RDF::RDFS.isDefinedBy then :isDefinedBy
357
460
  when RDF::RDFS.label then :label
358
- when RDF::URI("http://schema.org/inverseOf") then :inverseOf
461
+ when RDF::RDFS.range then :range
462
+ when RDF::RDFS.subClassOf then :subClassOf
463
+ when RDF::RDFS.subPropertyOf then :subPropertyOf
359
464
  when RDF::URI("http://schema.org/domainIncludes") then :domainIncludes
360
465
  when RDF::URI("http://schema.org/rangeIncludes") then :rangeIncludes
466
+ when RDF::URI("http://www.w3.org/2002/07/owl#allValuesFrom") then :allValuesFrom
467
+ when RDF::URI("http://www.w3.org/2002/07/owl#cardinality") then :cardinality
468
+ when RDF::URI("http://www.w3.org/2002/07/owl#equivalentClass") then :equivalentClass
469
+ when RDF::URI("http://www.w3.org/2002/07/owl#equivalentProperty") then :equivalentProperty
470
+ when RDF::URI("http://www.w3.org/2002/07/owl#intersectionOf") then :intersectionOf
471
+ when RDF::URI("http://www.w3.org/2002/07/owl#inverseOf") then :inverseOf
472
+ when RDF::URI("http://www.w3.org/2002/07/owl#maxCardinality") then :maxCardinality
473
+ when RDF::URI("http://www.w3.org/2002/07/owl#minCardinality") then :minCardinality
474
+ when RDF::URI("http://www.w3.org/2002/07/owl#onProperty") then :onProperty
475
+ when RDF::URI("http://www.w3.org/2002/07/owl#someValuesFrom") then :someValuesFrom
476
+ when RDF::URI("http://www.w3.org/2002/07/owl#unionOf") then :unionOf
477
+ when RDF::URI("http://www.w3.org/2004/02/skos/core#altLabel") then :altLabel
478
+ when RDF::URI("http://www.w3.org/2004/02/skos/core#broader") then :broader
479
+ when RDF::URI("http://www.w3.org/2004/02/skos/core#definition") then :definition
480
+ when RDF::URI("http://www.w3.org/2004/02/skos/core#editorialNote") then :editorialNote
481
+ when RDF::URI("http://www.w3.org/2004/02/skos/core#exactMatch") then :exactMatch
482
+ when RDF::URI("http://www.w3.org/2004/02/skos/core#hasTopConcept") then :hasTopConcept
483
+ when RDF::URI("http://www.w3.org/2004/02/skos/core#inScheme") then :inScheme
484
+ when RDF::URI("http://www.w3.org/2004/02/skos/core#member") then :member
485
+ when RDF::URI("http://www.w3.org/2004/02/skos/core#narrower") then :narrower
486
+ when RDF::URI("http://www.w3.org/2004/02/skos/core#notation") then :notation
487
+ when RDF::URI("http://www.w3.org/2004/02/skos/core#note") then :note
488
+ when RDF::URI("http://www.w3.org/2004/02/skos/core#prefLabel") then :prefLabel
489
+ when RDF::URI("http://www.w3.org/2004/02/skos/core#related") then :related
361
490
  else statement.predicate.pname.to_sym
362
491
  end
363
492
 
364
- value = if statement.object.uri?
365
- statement.object.pname
366
- elsif statement.object.literal? && (statement.object.language || :en).to_s =~ /^en-?/
367
- statement.object.to_s
368
- end
369
-
370
- (term[key] ||= []) << value if value
493
+ (term[key] ||= []) << statement.object
371
494
  end
372
495
 
373
496
  # Create extra terms
374
497
  term_defs = case extra
375
498
  when Array
376
- extra.inject({}) {|memo, s| memo[s.to_sym] = {label: s.to_s}; memo}.merge(term_defs)
499
+ extra.inject({}) {|memo, s| memo[s.to_sym] = {}; memo}.merge(term_defs)
377
500
  when Hash
378
501
  extra.merge(term_defs)
379
502
  else
380
503
  term_defs
381
504
  end
382
505
 
506
+ # Pass over embedded_defs with anonymous references, once
507
+ embedded_defs.each do |term, attributes|
508
+ attributes.each do |ak, avs|
509
+ # Turn embedded BNodes into either their Term definition or a List
510
+ avs = [avs] unless avs.is_a?(Array)
511
+ attributes[ak] = avs.map do |av|
512
+ l = RDF::List.new(subject: av, graph: graph)
513
+ if l.valid?
514
+ RDF::List.new(subject: av) do |nl|
515
+ l.each do |lv|
516
+ nl << (embedded_defs[lv] ? Term.new(vocab: vocab, attributes: embedded_defs[lv]) : lv)
517
+ end
518
+ end
519
+ elsif av.is_a?(RDF::Node)
520
+ Term.new(vocab: vocab, attributes: embedded_defs[av]) if embedded_defs[av]
521
+ else
522
+ av
523
+ end
524
+ end.compact
525
+ end
526
+ end
527
+
383
528
  term_defs.each do |term, attributes|
529
+ # Turn embedded BNodes into either their Term definition or a List
530
+ attributes.each do |ak, avs|
531
+ attributes[ak] = avs.is_a?(Array) ? avs.map do |av|
532
+ l = RDF::List.new(subject: av, graph: graph)
533
+ if l.valid?
534
+ RDF::List.new(subject: av) do |nl|
535
+ l.each do |lv|
536
+ nl << (embedded_defs[lv] ? Term.new(vocab: vocab, attributes: embedded_defs[lv]) : lv)
537
+ end
538
+ end
539
+ elsif av.is_a?(RDF::Node)
540
+ Term.new(vocab: vocab, attributes: embedded_defs[av]) if embedded_defs[av]
541
+ else
542
+ av
543
+ end
544
+ end.compact : avs
545
+ end
546
+
384
547
  if term == :""
385
- uri = attributes.delete(:uri)
386
- vocab.__ontology__ uri, attributes
548
+ vocab.__ontology__ vocab, attributes
387
549
  else
388
550
  vocab.__property__ term, attributes
389
551
  end
@@ -430,15 +592,20 @@ module RDF
430
592
 
431
593
  def method_missing(property, *args, &block)
432
594
  property = RDF::Vocabulary.camelize(property.to_s)
433
- if %w(to_ary).include?(property.to_s)
434
- super
435
- elsif args.empty? && !to_s.empty?
436
- Term.intern([to_s, property.to_s].join(''), attributes: {vocab: self})
595
+ if args.empty? && !to_s.empty?
596
+ Term.intern([to_s, property.to_s].join(''), vocab: self, attributes: {})
437
597
  else
438
598
  super
439
599
  end
440
600
  end
441
601
 
602
+ # Create a list of terms
603
+ # @param [Array<String>] values
604
+ # Each value treated as a URI or PName
605
+ # @return [RDF::List]
606
+ def list(*values)
607
+ RDF::List[*values.map {|v| expand_pname(v) rescue RDF::Literal(v)}]
608
+ end
442
609
  private
443
610
 
444
611
  def props; @properties ||= {}; end
@@ -466,7 +633,7 @@ module RDF
466
633
  # @param [#to_s] property
467
634
  # @return [URI]
468
635
  def [](property)
469
- Term.intern([to_s, property.to_s].join(''), attributes: {vocab: self.class})
636
+ Term.intern([to_s, property.to_s].join(''), vocab: self.class, attributes: {})
470
637
  end
471
638
 
472
639
  ##
@@ -526,94 +693,190 @@ module RDF
526
693
  @@uris = {} # @private
527
694
  @@uri = nil # @private
528
695
 
529
- # A Vocabulary Term is a URI that can also act as an {Enumerable} to generate the RDF definition of vocabulary terms as defined within the vocabulary definition.
530
- class Term < RDF::URI
531
- # @!method comment
696
+ # 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.
697
+ #
698
+ # Terms include `attributes` where values a embedded resources, lists or other terms. This allows, for example, navigation of a concept heirarchy.
699
+ module Term
700
+ include RDF::Resource
701
+
702
+ # @!attribute [r] comment
532
703
  # `rdfs:comment` accessor
533
- # @return [String]
534
- # @!method label
704
+ # @return [Literal, Array<Literal>]
705
+ # @!attribute [r] label
535
706
  # `rdfs:label` accessor
536
- # @return [String]
537
- # @!method type
707
+ # @return [Literal]
708
+ # @!attribute [r] type
538
709
  # `rdf:type` accessor
539
- # @return [RDF::URI]
540
- # @!method subClassOf
710
+ # @return [Array<Term>]
711
+ # @!attribute [r] subClassOf
541
712
  # `rdfs:subClassOf` accessor
542
- # @return [RDF::URI]
543
- # @!method subPropertyOf
713
+ # @return [Array<Term>]
714
+ # @!attribute [r] subPropertyOf
544
715
  # `rdfs:subPropertyOf` accessor
545
- # @return [RDF::URI]
546
- # @!method domain
716
+ # @return [Array<Term>]
717
+ # @!attribute [r] domain
547
718
  # `rdfs:domain` accessor
548
- # @return [RDF::URI]
549
- # @!method range
719
+ # @return [Array<Term>]
720
+ # @!attribute [r] range
550
721
  # `rdfs:range` accessor
551
- # @return [RDF::URI]
552
- # @!method inverseOf
722
+ # @return [Array<Term>]
723
+ # @!attribute [r] isDefinedBy
724
+ # `rdfs:isDefinedBy` accessor
725
+ # @return [Array<Term>]
726
+
727
+ # @!attribute [r] allValuesFrom
728
+ # `owl:allValuesFrom` accessor
729
+ # @return [Array<Term>]
730
+ # @!attribute [r] cardinality
731
+ # `owl:cardinality` accessor
732
+ # @return [Array<Literal>]
733
+ # @!attribute [r] equivalentClass
734
+ # `owl:equivalentClass` accessor
735
+ # @return [Array<Term>]
736
+ # @!attribute [r] equivalentProperty
737
+ # `owl:equivalentProperty` accessor
738
+ # @return [Array<Term>]
739
+ # @!attribute [r] intersectionOf
740
+ # `owl:intersectionOf` accessor
741
+ # @return [Array<Term>]
742
+ # @!attribute [r] inverseOf
553
743
  # `owl:inverseOf` accessor
554
- # @return [RDF::URI]
555
- # @!method domainIncludes
744
+ # @return [Array<Term>]
745
+ # @!attribute [r] maxCardinality
746
+ # `owl:maxCardinality` accessor
747
+ # @return [Array<Literal>]
748
+ # @!attribute [r] minCardinality
749
+ # `owl:minCardinality` accessor
750
+ # @return [Array<Literal>]
751
+ # @!attribute [r] onProperty
752
+ # `owl:onProperty` accessor
753
+ # @return [Array<Term>]
754
+ # @!attribute [r] someValuesFrom
755
+ # `owl:someValuesFrom` accessor
756
+ # @return [Array<Term>]
757
+ # @!attribute [r] unionOf
758
+ # `owl:unionOf` accessor
759
+ # @return [List<Term>, Array<Term>]
760
+
761
+ # @!attribute [r] domainIncludes
556
762
  # `schema:domainIncludes` accessor
557
- # @return [RDF::URI]
558
- # @!method rangeIncludes
559
- # `schema:rangeIncludes` accoessor
560
- # @return [RDF::URI]
561
- # @!attribute [rw] attributes
763
+ # @return [Array<Term>]
764
+ # @!attribute [r] rangeIncludes
765
+ # `schema:rangeIncludes` accessor
766
+ # @return [Array<Term>]
767
+
768
+ # @!attribute [r] altLabel
769
+ # `skos:altLabel` accessor
770
+ # @return [Literal, Array<Literal>]
771
+ # @!attribute [r] broader
772
+ # `skos:broader` accessor
773
+ # @return [Array<Term>]
774
+ # @!attribute [r] definition
775
+ # `skos:definition` accessor
776
+ # @return [Literal, Array<Literal>]
777
+ # @!attribute [r] editorialNote
778
+ # `skos:editorialNote` accessor
779
+ # @return [Literal, Array<Literal>]
780
+ # @!attribute [r] exactMatch
781
+ # `skos:exactMatch` accessor
782
+ # @return [Array<Term>]
783
+ # @!attribute [r] hasTopConcept
784
+ # `skos:hasTopConcept` accessor
785
+ # @return [Array<Term>]
786
+ # @!attribute [r] inScheme
787
+ # `skos:inScheme` accessor
788
+ # @return [Array<Term>]
789
+ # @!attribute [r] member
790
+ # `skos:member` accessor
791
+ # @return [Array<Term>]
792
+ # @!attribute [r] narrower
793
+ # `skos:narrower` accessor
794
+ # @return [Array<Term>]
795
+ # @!attribute [r] notation
796
+ # `skos:notation` accessor
797
+ # @return [Literal, Array<Literal>]
798
+ # @!attribute [r] note
799
+ # `skos:note` accessor
800
+ # @return [Literal, Array<Literal>]
801
+ # @!attribute [r] prefLabel
802
+ # `skos:prefLabel` accessor
803
+ # @return [Literal]
804
+ # @!attribute [r] related
805
+ # `skos:related` accessor
806
+ # @return [Array<Term>]
807
+
808
+ ##
809
+ # Vocabulary of this term.
810
+ #
811
+ # @return [RDF::Vocabulary]
812
+ attr_reader :vocab
813
+
562
814
  # Attributes of this vocabulary term, used for finding `label` and `comment` and to serialize the term back to RDF.
563
815
  # @return [Hash{Symbol,Resource => Term, #to_s}]
564
- attr_accessor :attributes
816
+ attr_reader :attributes
817
+
565
818
 
566
819
  ##
567
- # @overload URI(uri, **options)
820
+ # @overload new(uri, attributes:, **options)
568
821
  # @param [URI, String, #to_s] uri
822
+ # @param [Vocabulary] vocab Vocabulary of this term.
569
823
  # @param [Hash{Symbol,Resource => Term, #to_s}] attributes
570
824
  # Attributes of this vocabulary term, used for finding `label` and `comment` and to serialize the term back to RDF
571
825
  # @param [Hash{Symbol => Object}] options
572
- # @option options [Boolean] :validate (false)
573
- # @option options [Boolean] :canonicalize (false)
826
+ # Options from {URI#initialize}
574
827
  #
575
- # @overload URI(**options)
828
+ # @overload new(attributes:, **options)
576
829
  # @param [Hash{Symbol => Object}] options
577
- # @param [Hash{Symbol,Resource => Term, #to_s}] attributes
578
- # Attributes of this vocabulary term, used for finding `label` and `comment` and to serialize the term back to RDF
579
- # @option options options [Boolean] :validate (false)
580
- # @option options options [Boolean] :canonicalize (false)
581
- # @option options [Vocabulary] :vocab The {Vocabulary} associated with this term.
582
- # @option options [String, #to_s] :scheme The scheme component.
583
- # @option options [String, #to_s] :user The user component.
584
- # @option options [String, #to_s] :password The password component.
585
- # @option options [String, #to_s] :userinfo
586
- # The u optionsserinfo component. If this is supplied, the user and password
587
- # compo optionsnents must be omitted.
588
- # @option options [String, #to_s] :host The host component.
589
- # @option options [String, #to_s] :port The port component.
590
- # @option options [String, #to_s] :authority
591
- # The a optionsuthority component. If this is supplied, the user, password,
592
- # useri optionsnfo, host, and port components must be omitted.
593
- # @option options [String, #to_s] :path The path component.
594
- # @option options [String, #to_s] :query The query component.
595
- # @option options [String, #to_s] :fragment The fragment component.
596
- def initialize(*args, attributes:, **options)
597
- @attributes = attributes
598
- if RUBY_ENGINE == "rbx"
599
- super(*args, **options)
600
- else
601
- super
830
+ # @param [Vocabulary] vocab Vocabulary of this term.
831
+ # @param [Hash{Symbol => String,Array<String,Term>}] attributes
832
+ # Attributes of this vocabulary term, used for finding `label` and `comment` and to serialize the term back to RDF.
833
+ # @param [Hash{Symbol => Object}] options
834
+ # Options from {URI#initialize}
835
+ def self.new(*args, vocab: nil, attributes: {}, **options)
836
+ klass = if args.first.nil?
837
+ RDF::Node
838
+ elsif args.first.is_a?(Hash)
839
+ args.unshift(nil)
840
+ RDF::Node
841
+ elsif args.first.to_s.start_with?("_:")
842
+ args = args[1..-1].unshift($1)
843
+ RDF::Node
844
+ else RDF::URI
602
845
  end
846
+ term = klass.allocate.extend(Term)
847
+ term.send(:initialize, *args)
848
+ term.instance_variable_set(:@vocab, vocab)
849
+ term.instance_variable_set(:@attributes, attributes)
850
+ term
603
851
  end
604
852
 
605
853
  ##
606
- # Vocabulary of this term.
854
+ # Returns an interned `RDF::URI` instance based on the given `uri`
855
+ # string.
607
856
  #
608
- # @return [RDF::Vocabulary]
609
- def vocab; @attributes.fetch(:vocab); end
857
+ # The maximum number of cached interned URI references is given by the
858
+ # `CACHE_SIZE` constant. This value is unlimited by default, in which
859
+ # case an interned URI object will be purged only when the last strong
860
+ # reference to it is garbage collected (i.e., when its finalizer runs).
861
+ #
862
+ # Excepting special memory-limited circumstances, it should always be
863
+ # safe and preferred to construct new URI references using
864
+ # `RDF::URI.intern` instead of `RDF::URI.new`, since if an interned
865
+ # object can't be returned for some reason, this method will fall back
866
+ # to returning a freshly-allocated one.
867
+ #
868
+ # @param (see #initialize)
869
+ # @return [RDF::URI] an immutable, frozen URI object
870
+ def self.intern(str, *args)
871
+ (URI.cache[(str = str.to_s).to_sym] ||= self.new(str, *args)).freeze
872
+ end
610
873
 
611
874
  ##
612
875
  # Returns a duplicate copy of `self`.
613
876
  #
614
877
  # @return [RDF::URI]
615
878
  def dup
616
- self.class.new((@value || @object).dup, attributes: @attributes)
879
+ self.class.new((@value || @object).dup, attributes: attributes).extend(Term)
617
880
  end
618
881
 
619
882
  ##
@@ -623,35 +886,88 @@ module RDF
623
886
  # @since 0.3.9
624
887
  def valid?
625
888
  # Validate relative to RFC3987
626
- RDF::URI::IRI.match(to_s) || false
889
+ node? || RDF::URI::IRI.match(to_s) || false
627
890
  end
628
891
 
629
892
  ##
630
893
  # Is this a class term?
631
894
  # @return [Boolean]
632
895
  def class?
633
- !!(self.type.to_s =~ /Class/)
896
+ Array(self.type).any? {|t| t.to_s.include?('Class')}
634
897
  end
635
898
 
636
899
  ##
637
900
  # Is this a class term?
638
901
  # @return [Boolean]
639
902
  def property?
640
- !!(self.type.to_s =~ /Property/)
903
+ Array(self.type).any? {|t| t.to_s.include?('Property')}
641
904
  end
642
905
 
643
906
  ##
644
907
  # Is this a class term?
645
908
  # @return [Boolean]
646
909
  def datatype?
647
- !!(self.type.to_s =~ /Datatype/)
910
+ Array(self.type).any? {|t| t.to_s.include?('Datatype')}
911
+ end
912
+
913
+ ##
914
+ # Is this a Restriction term?
915
+ # @return [Boolean]
916
+ def restriction?
917
+ Array(self.type).any? {|t| t.to_s.include?('Restriction')}
648
918
  end
649
919
 
650
920
  ##
651
921
  # Is this neither a class, property or datatype term?
652
922
  # @return [Boolean]
653
923
  def other?
654
- !!(self.type.to_s !~ /(Class|Property|Datatype)/)
924
+ Array(self.type).none? {|t| t.to_s =~ /(Class|Property|Datatype|Restriction)/}
925
+ end
926
+
927
+ ##
928
+ # Enumerate attributes with values transformed into {RDF::Value} instances
929
+ #
930
+ # @return [Hash{Symbol => Array<RDF::Value>}]
931
+ def properties
932
+ attributes.keys.inject({}) do |memo, p|
933
+ memo.merge(p => attribute_value(p))
934
+ end
935
+ end
936
+
937
+ ##
938
+ # Values of an attributes as {RDF::Value}
939
+ #
940
+ # @property [Symbol] prop
941
+ # @return [RDF::Value, Array<RDF::Value>]
942
+ def attribute_value(prop)
943
+ values = attributes[prop]
944
+ values = [values].compact unless values.is_a?(Array)
945
+ prop_values = values.map do |value|
946
+ v = value.is_a?(Symbol) ? value.to_s : value
947
+ value = (RDF::Vocabulary.expand_pname(v) rescue nil) if v.is_a?(String) && v.include?(':')
948
+ value = value.to_uri if value.respond_to?(:to_uri)
949
+ unless value.is_a?(RDF::Value) && value.valid?
950
+ # Use as most appropriate literal
951
+ value = [
952
+ RDF::Literal::Date,
953
+ RDF::Literal::DateTime,
954
+ RDF::Literal::Integer,
955
+ RDF::Literal::Decimal,
956
+ RDF::Literal::Double,
957
+ RDF::Literal::Boolean,
958
+ RDF::Literal
959
+ ].inject(nil) do |m, klass|
960
+ m || begin
961
+ l = klass.new(v)
962
+ l if l.valid?
963
+ end
964
+ end
965
+ end
966
+
967
+ value
968
+ end
969
+
970
+ prop_values.length <= 1 ? prop_values.first : prop_values
655
971
  end
656
972
 
657
973
  ##
@@ -662,61 +978,36 @@ module RDF
662
978
  # @yield statement
663
979
  # @yieldparam [RDF::Statement]
664
980
  def each_statement
665
- attributes.reject {|p| p == :vocab}.each do |prop, values|
666
- Array(values).each do |value|
981
+ attributes.keys.each do |p|
982
+ values = attribute_value(p)
983
+ values = [values].compact unless values.is_a?(Array)
984
+ values.each do |value|
667
985
  begin
668
- case prop
986
+ prop = case p
669
987
  when :type
670
- prop = RDF.type
671
- value = RDF::Vocabulary.expand_pname(value)
672
- when :subClassOf
673
- prop = RDFS.subClassOf
674
- value = RDF::Vocabulary.expand_pname(value)
675
- when :subPropertyOf
676
- prop = RDFS.subPropertyOf
677
- value = RDF::Vocabulary.expand_pname(value)
678
- when :domain
679
- prop = RDFS.domain
680
- value = RDF::Vocabulary.expand_pname(value)
681
- when :range
682
- prop = RDFS.range
683
- value = RDF::Vocabulary.expand_pname(value)
684
- when :inverseOf
685
- prop = RDF::URI("http://schema.org/inverseOf")
686
- value = RDF::Vocabulary.expand_pname(value)
687
- when :domainIncludes
688
- prop = RDF::URI("http://schema.org/domainIncludes")
689
- value = RDF::Vocabulary.expand_pname(value)
690
- when :rangeIncludes
691
- prop = RDF::URI("http://schema.org/rangeIncludes")
692
- value = RDF::Vocabulary.expand_pname(value)
693
- when :label
694
- prop = RDF::RDFS.label
695
- when :comment
696
- prop = RDF::RDFS.comment
988
+ RDF::RDFV[p]
989
+ when :subClassOf, :subPropertyOf, :domain, :range, :isDefinedBy, :label, :comment
990
+ RDF::RDFS[p]
991
+ when :allValuesFrom, :cardinality, :equivalentClass, :equivalentProperty,
992
+ :intersectionOf, :inverseOf, :maxCardinality, :minCardinality,
993
+ :onProperty, :someValuesFrom, :unionOf
994
+ RDF::OWL[p]
995
+ when :domainIncludes, :rangeIncludes
996
+ RDF::Vocabulary.find_term("http://schema.org/#{p}")
997
+ when :broader, :definition, :exactMatch, :hasTopConcept, :inScheme,
998
+ :member, :narrower, :related, :altLabel, :definition, :editorialNote,
999
+ :notation, :note, :prefLabel
1000
+ RDF::Vocabulary.find_term("http://www.w3.org/2004/02/skos/core##{p}")
697
1001
  else
698
- prop = RDF::Vocabulary.expand_pname(prop.to_s)
699
- next unless prop
700
-
701
- v = value.to_s
702
- value = RDF::Vocabulary.expand_pname(v)
703
- unless value && value.valid?
704
- # Use as most appropriate literal
705
- value = [
706
- RDF::Literal::Date,
707
- RDF::Literal::DateTime,
708
- RDF::Literal::Integer,
709
- RDF::Literal::Decimal,
710
- RDF::Literal::Double,
711
- RDF::Literal::Boolean,
712
- RDF::Literal
713
- ].inject(nil) do |memo, klass|
714
- l = klass.new(v)
715
- memo || (l if l.valid?)
716
- end
717
- end
1002
+ RDF::Vocabulary.expand_pname(p)
1003
+ end
1004
+
1005
+ yield RDF::Statement(self, prop, value) if prop.is_a?(RDF::URI)
1006
+
1007
+ # Enumerate over value statements, if enumerable
1008
+ if value.is_a?(RDF::Enumerable) || (value.is_a?(Term) && value.node?)
1009
+ value.each_statement {|s| yield s}
718
1010
  end
719
- yield RDF::Statement(self, prop, value)
720
1011
  rescue KeyError
721
1012
  # Skip things eroneously defined in the vocabulary
722
1013
  end
@@ -742,36 +1033,108 @@ module RDF
742
1033
  #
743
1034
  # @return [String] The URI object's state, as a <code>String</code>.
744
1035
  def inspect
745
- sprintf("#<%s:%#0x URI:%s>", Term.to_s, self.object_id, self.to_s)
1036
+ sprintf("#<%s:%#0x ID:%s>", Term.to_s, self.object_id, self.to_s)
746
1037
  end
747
1038
 
748
1039
  # Implement accessor to symbol attributes
749
1040
  def respond_to?(method, include_all = false)
750
- @attributes.has_key?(method) || super
1041
+ case method
1042
+ when :comment, :notation, :note, :editorialNote, :definition,
1043
+ :label, :altLabel, :prefLabel, :type, :isDefinedBy
1044
+ true
1045
+ when :subClassOf, :subPropertyOf,
1046
+ :domainIncludes, :rangeIncludes,
1047
+ :equivalentClass, :intersectionOf, :unionOf
1048
+ self.class?
1049
+ when :domain, :range, :equivalentProperty, :inverseOf
1050
+ self.property?
1051
+ when :allValuesFrom, :cardinality,
1052
+ :maxCardinality, :minCardinality,
1053
+ :onProperty, :someValuesFrom
1054
+ self.restriction?
1055
+ when :broader, :exactMatch, :hasTopConcept, :inScheme, :member, :narrower, :related
1056
+ @attributes.has_key?(method)
1057
+ else
1058
+ super
1059
+ end
751
1060
  end
752
1061
 
753
1062
  # Accessor for `schema:domainIncludes`
754
1063
  # @return [RDF::URI]
755
1064
  def domain_includes
756
- Array(@attributes[:domainIncludes]).map {|v| RDF::Vocabulary.expand_pname(v)}
1065
+ domainIncludes
757
1066
  end
758
1067
 
759
1068
  # Accessor for `schema:rangeIncludes`
760
1069
  # @return [RDF::URI]
761
1070
  def range_includes
762
- Array(@attributes[:rangeIncludes]).map {|v| RDF::Vocabulary.expand_pname(v)}
1071
+ rangeIncludes
763
1072
  end
764
1073
 
1074
+ # Serialize back to a Ruby source initializer
1075
+ # @param [String] indent
1076
+ # @return [String]
1077
+ def to_ruby(indent: "")
1078
+ "term(" +
1079
+ (self.uri? ? self.to_s.inspect + ",\n" : "\n") +
1080
+ "#{indent} " +
1081
+ attributes.keys.map do |k|
1082
+ values = attribute_value(k)
1083
+ values = [values].compact unless values.is_a?(Array)
1084
+ values = values.map do |value|
1085
+ if value.is_a?(Literal) && %w(: comment definition notation note editorialNote).include?(k.to_s)
1086
+ "%(#{value.to_s.gsub('(', '\(').gsub(')', '\)')}).freeze"
1087
+ elsif value.is_a?(RDF::URI)
1088
+ "#{value.pname.inspect}.freeze"
1089
+ elsif value.is_a?(RDF::Vocabulary::Term)
1090
+ value.to_ruby(indent: indent + " ")
1091
+ elsif value.is_a?(RDF::Term)
1092
+ "#{value.to_s.inspect}.freeze"
1093
+ elsif value.is_a?(RDF::List)
1094
+ list_elements = value.map do |u|
1095
+ if u.uri?
1096
+ "#{u.pname.inspect}.freeze"
1097
+ elsif u.respond_to?(:to_ruby)
1098
+ u.to_ruby(indent: indent + " ")
1099
+ else
1100
+ "#{u.to_s.inspect}.freeze"
1101
+ end
1102
+ end
1103
+ "list(#{list_elements.join(', ')})"
1104
+ else
1105
+ "#{value.inspect}.freeze"
1106
+ end
1107
+ end
1108
+ "#{k.to_s.include?(':') ? k.to_s.inspect : k}: " +
1109
+ (values.length == 1 ? values.first : ('[' + values.join(',') + ']'))
1110
+ end.join(",\n#{indent} ") + "\n#{indent})"
1111
+
1112
+ end
765
1113
  protected
766
1114
  # Implement accessor to symbol attributes
767
1115
  def method_missing(method, *args, &block)
768
1116
  case method
769
- when :comment
770
- @attributes.fetch(method, "")
771
- when :label
772
- @attributes.fetch(method, to_s.split(/[\/\#]/).last)
773
- when :type, :subClassOf, :subPropertyOf, :domain, :range, :inverseOf, :domainIncludes, :rangeIncludes
774
- Array(@attributes[method]).map {|v| RDF::Vocabulary.expand_pname(v)}
1117
+ when :comment, :notation, :note, :editorialNote, :definition
1118
+ attribute_value(method)
1119
+ when :label, :altLabel, :prefLabel
1120
+ # Defaults to URI fragment or path tail
1121
+ begin
1122
+ attribute_value(method)
1123
+ rescue KeyError
1124
+ to_s.split(/[\/\#]/).last
1125
+ end
1126
+ when :type, :subClassOf, :subPropertyOf, :domain, :range, :isDefinedBy,
1127
+ :allValuesFrom, :cardinality, :equivalentClass, :equivalentProperty,
1128
+ :intersectionOf, :inverseOf, :maxCardinality, :minCardinality,
1129
+ :onProperty, :someValuesFrom, :unionOf,
1130
+ :domainIncludes, :rangeIncludes,
1131
+ :broader, :exactMatch, :hasTopConcept, :inScheme, :member, :narrower, :related
1132
+
1133
+ # Return value as an Array, unless it is a list
1134
+ case value = attribute_value(method)
1135
+ when Array, RDF::List then value
1136
+ else [value].compact
1137
+ end
775
1138
  else
776
1139
  super
777
1140
  end