rdf 2.2.12 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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