rdf 1.1.3 → 1.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/CREDITS +1 -0
  3. data/VERSION +1 -1
  4. data/lib/rdf.rb +10 -48
  5. data/lib/rdf/cli/vocab-loader.rb +78 -142
  6. data/lib/rdf/mixin/enumerable.rb +25 -0
  7. data/lib/rdf/mixin/mutable.rb +3 -0
  8. data/lib/rdf/model/graph.rb +1 -1
  9. data/lib/rdf/model/node.rb +25 -2
  10. data/lib/rdf/model/statement.rb +20 -12
  11. data/lib/rdf/model/uri.rb +11 -2
  12. data/lib/rdf/nquads.rb +8 -6
  13. data/lib/rdf/ntriples/writer.rb +14 -10
  14. data/lib/rdf/query/pattern.rb +10 -8
  15. data/lib/rdf/reader.rb +11 -2
  16. data/lib/rdf/repository.rb +1 -1
  17. data/lib/rdf/vocab.rb +396 -96
  18. data/lib/rdf/vocab/cc.rb +117 -25
  19. data/lib/rdf/vocab/cert.rb +230 -117
  20. data/lib/rdf/vocab/dc.rb +930 -233
  21. data/lib/rdf/vocab/dc11.rb +151 -37
  22. data/lib/rdf/vocab/doap.rb +326 -114
  23. data/lib/rdf/vocab/exif.rb +930 -533
  24. data/lib/rdf/vocab/foaf.rb +602 -346
  25. data/lib/rdf/vocab/geo.rb +139 -33
  26. data/lib/rdf/vocab/gr.rb +1551 -1084
  27. data/lib/rdf/vocab/ht.rb +319 -0
  28. data/lib/rdf/vocab/ical.rb +507 -349
  29. data/lib/rdf/vocab/ma.rb +504 -280
  30. data/lib/rdf/vocab/mo.rb +2425 -876
  31. data/lib/rdf/vocab/og.rb +178 -90
  32. data/lib/rdf/vocab/owl.rb +513 -219
  33. data/lib/rdf/vocab/prov.rb +1557 -479
  34. data/lib/rdf/vocab/rdfs.rb +107 -31
  35. data/lib/rdf/vocab/rdfv.rb +165 -0
  36. data/lib/rdf/vocab/rsa.rb +61 -18
  37. data/lib/rdf/vocab/rss.rb +55 -22
  38. data/lib/rdf/vocab/schema.rb +8590 -3995
  39. data/lib/rdf/vocab/sioc.rb +657 -218
  40. data/lib/rdf/vocab/skos.rb +227 -134
  41. data/lib/rdf/vocab/skosxl.rb +47 -33
  42. data/lib/rdf/vocab/vcard.rb +693 -327
  43. data/lib/rdf/vocab/void.rb +175 -132
  44. data/lib/rdf/vocab/vs.rb +27 -0
  45. data/lib/rdf/vocab/wdrs.rb +123 -119
  46. data/lib/rdf/vocab/wot.rb +155 -45
  47. data/lib/rdf/vocab/xhtml.rb +2 -1
  48. data/lib/rdf/vocab/xhv.rb +496 -231
  49. data/lib/rdf/vocab/xsd.rb +382 -53
  50. data/lib/rdf/writer.rb +8 -4
  51. metadata +5 -4
  52. data/lib/rdf/vocab/http.rb +0 -84
  53. data/lib/rdf/vocab/v.rb +0 -154
@@ -6,19 +6,25 @@ module RDF
6
6
  # s = RDF::URI.new("http://rubygems.org/gems/rdf")
7
7
  # p = RDF::DC.creator
8
8
  # o = RDF::URI.new("http://ar.to/#self")
9
- # RDF::Statement.new(s, p, o)
9
+ # RDF::Statement(s, p, o)
10
10
  #
11
11
  # @example Creating an RDF statement with a context
12
12
  # uri = RDF::URI("http://example/")
13
- # RDF::Statement.new(s, p, o, :context => uri)
13
+ # RDF::Statement(s, p, o, :context => uri)
14
14
  #
15
15
  # @example Creating an RDF statement from a `Hash`
16
- # RDF::Statement.new({
16
+ # RDF::Statement({
17
17
  # :subject => RDF::URI.new("http://rubygems.org/gems/rdf"),
18
18
  # :predicate => RDF::DC.creator,
19
19
  # :object => RDF::URI.new("http://ar.to/#self"),
20
20
  # })
21
21
  #
22
+ # @example Creating an RDF statement with interned nodes
23
+ # RDF::Statement(:s, p, :o)
24
+ #
25
+ # @example Creating an RDF statement with a string
26
+ # RDF::Statement(s, p, "o")
27
+ #
22
28
  class Statement
23
29
  include RDF::Value
24
30
 
@@ -54,21 +60,23 @@ module RDF
54
60
  ##
55
61
  # @overload initialize(options = {})
56
62
  # @param [Hash{Symbol => Object}] options
57
- # @option options [RDF::Resource] :subject (nil)
63
+ # @option options [RDF::Term] :subject (nil)
64
+ # A symbol is converted to an interned {Node}.
58
65
  # @option options [RDF::URI] :predicate (nil)
59
- # @option options [RDF::Term] :object (nil)
60
- # if not an `RDF::Term`, it is coerced to `RDF::Literal`.
61
- # @option options [RDF::Resource] :context (nil)
62
- # Note, in RDF 1.1, a context MUST be an IRI.
66
+ # @option options [RDF::Resource] :object (nil)
67
+ # if not a {Resource}, it is coerced to {Literal} or {Node} depending on if it is a symbol or something other than a {Term}.
68
+ # @option options [RDF::Term] :context (nil)
69
+ # Note, in RDF 1.1, a context MUST be an {Resource}.
63
70
  # @return [RDF::Statement]
64
71
  #
65
72
  # @overload initialize(subject, predicate, object, options = {})
66
- # @param [RDF::Resource] subject
73
+ # @param [RDF::Term] subject
74
+ # A symbol is converted to an interned {Node}.
67
75
  # @param [RDF::URI] predicate
68
- # @param [RDF::Term] object
69
- # if not an `RDF::Term`, it is coerced to `RDF::Literal`.
76
+ # @param [RDF::Resource] object
77
+ # if not a {Resource}, it is coerced to {Literal} or {Node} depending on if it is a symbol or something other than a {Term}.
70
78
  # @param [Hash{Symbol => Object}] options
71
- # @option options [RDF::Resource] :context (nil)
79
+ # @option options [RDF::Term] :context (nil)
72
80
  # @return [RDF::Statement]
73
81
  def initialize(subject = nil, predicate = nil, object = nil, options = {})
74
82
  case subject
data/lib/rdf/model/uri.rb CHANGED
@@ -140,8 +140,9 @@ module RDF
140
140
  #
141
141
  # @param (see #initialize)
142
142
  # @return [RDF::URI] an immutable, frozen URI object
143
- def self.intern(str)
144
- (cache[str = str.to_s] ||= self.new(str)).freeze
143
+ def self.intern(*args)
144
+ str = args.first
145
+ (cache[str = str.to_s] ||= self.new(*args)).freeze
145
146
  end
146
147
 
147
148
  ##
@@ -632,6 +633,14 @@ module RDF
632
633
  return nil # no QName found
633
634
  end
634
635
 
636
+ ##
637
+ # Returns a string version of the QName or the full IRI
638
+ #
639
+ # @return [String] or `nil`
640
+ def pname
641
+ (q = self.qname) ? q.join(":") : to_s
642
+ end
643
+
635
644
  ##
636
645
  # Returns a duplicate copy of `self`.
637
646
  #
data/lib/rdf/nquads.rb CHANGED
@@ -114,17 +114,18 @@ module RDF
114
114
  # @param [RDF::Term] object
115
115
  # @return [void]
116
116
  def write_quad(subject, predicate, object, context)
117
- puts format_quad(subject, predicate, object, context)
117
+ puts format_quad(subject, predicate, object, context, @options)
118
118
  end
119
119
 
120
120
  ##
121
121
  # Returns the N-Quads representation of a statement.
122
122
  #
123
123
  # @param [RDF::Statement] statement
124
+ # @param [Hash{Symbol => Object}] options = ({})
124
125
  # @return [String]
125
126
  # @since 0.4.0
126
- def format_statement(statement)
127
- format_quad(*statement.to_quad)
127
+ def format_statement(statement, options = {})
128
+ format_quad(*statement.to_quad, options)
128
129
  end
129
130
 
130
131
  ##
@@ -134,10 +135,11 @@ module RDF
134
135
  # @param [RDF::URI] predicate
135
136
  # @param [RDF::Term] object
136
137
  # @param [RDF::Term] context
138
+ # @param [Hash{Symbol => Object}] options = ({})
137
139
  # @return [String]
138
- def format_quad(subject, predicate, object, context)
139
- s = "%s %s %s " % [subject, predicate, object].map { |value| format_term(value) }
140
- s += format_term(context) + " " if context
140
+ def format_quad(subject, predicate, object, context, options = {})
141
+ s = "%s %s %s " % [subject, predicate, object].map { |value| format_term(value, options) }
142
+ s += format_term(context, options) + " " if context
141
143
  s + "."
142
144
  end
143
145
  end # Writer
@@ -179,7 +179,7 @@ module RDF::NTriples
179
179
  #
180
180
  # @param [IO, File] output
181
181
  # the output stream
182
- # @param [Hash{Symbol => Object}] options
182
+ # @param [Hash{Symbol => Object}] options = ({})
183
183
  # any additional options. See {RDF::Writer#initialize}
184
184
  # @option options [Boolean] :validate (true)
185
185
  # whether to validate terms when serializing
@@ -208,16 +208,17 @@ module RDF::NTriples
208
208
  # @param [RDF::Term] object
209
209
  # @return [void]
210
210
  def write_triple(subject, predicate, object)
211
- puts format_triple(subject, predicate, object)
211
+ puts format_triple(subject, predicate, object, @options)
212
212
  end
213
213
 
214
214
  ##
215
215
  # Returns the N-Triples representation of a statement.
216
216
  #
217
217
  # @param [RDF::Statement] statement
218
+ # @param [Hash{Symbol => Object}] options = ({})
218
219
  # @return [String]
219
- def format_statement(statement)
220
- format_triple(*statement.to_triple)
220
+ def format_statement(statement, options = {})
221
+ format_triple(*statement.to_triple, options)
221
222
  end
222
223
 
223
224
  ##
@@ -226,26 +227,29 @@ module RDF::NTriples
226
227
  # @param [RDF::Resource] subject
227
228
  # @param [RDF::URI] predicate
228
229
  # @param [RDF::Term] object
230
+ # @param [Hash{Symbol => Object}] options = ({})
229
231
  # @return [String]
230
- def format_triple(subject, predicate, object)
231
- "%s %s %s ." % [subject, predicate, object].map { |value| format_term(value) }
232
+ def format_triple(subject, predicate, object, options = {})
233
+ "%s %s %s ." % [subject, predicate, object].map { |value| format_term(value, options) }
232
234
  end
233
235
 
234
236
  ##
235
237
  # Returns the N-Triples representation of a blank node.
236
238
  #
237
239
  # @param [RDF::Node] node
238
- # @param [Hash{Symbol => Object}] options
240
+ # @param [Hash{Symbol => Object}] options = ({})
241
+ # @option options [Boolean] :unique_bnodes (false)
242
+ # Serialize node using unique identifier, rather than any used to create the node.
239
243
  # @return [String]
240
244
  def format_node(node, options = {})
241
- node.to_base
245
+ options[:unique_bnodes] ? node.to_unique_base : node.to_base
242
246
  end
243
247
 
244
248
  ##
245
249
  # Returns the N-Triples representation of a URI reference.
246
250
  #
247
251
  # @param [RDF::URI] uri
248
- # @param [Hash{Symbol => Object}] options
252
+ # @param [Hash{Symbol => Object}] options = ({})
249
253
  # @return [String]
250
254
  def format_uri(uri, options = {})
251
255
  uri.to_base
@@ -255,7 +259,7 @@ module RDF::NTriples
255
259
  # Returns the N-Triples representation of a literal.
256
260
  #
257
261
  # @param [RDF::Literal, String, #to_s] literal
258
- # @param [Hash{Symbol => Object}] options
262
+ # @param [Hash{Symbol => Object}] options = ({})
259
263
  # @return [String]
260
264
  def format_literal(literal, options = {})
261
265
  case literal
@@ -18,21 +18,23 @@ module RDF; class Query
18
18
  ##
19
19
  # @overload initialize(options = {})
20
20
  # @param [Hash{Symbol => Object}] options
21
- # @option options [Variable, Resource, nil] :subject (nil)
22
- # @option options [Variable, URI, nil] :predicate (nil)
23
- # @option options [Variable, Term, nil] :object (nil)
24
- # @option options [Variable, Resource, nil, false] :context (nil)
21
+ # @option options [Variable, Resource, Symbol, nil] :subject (nil)
22
+ # @option options [Variable, URI, Symbol, nil] :predicate (nil)
23
+ # @option options [Variable, Term, Symbol, nil] :object (nil)
24
+ # @option options [Variable, Resource, Symbol, nil, false] :context (nil)
25
25
  # A context of nil matches any context, a context of false, matches only the default context.
26
26
  # @option options [Boolean] :optional (false)
27
27
  #
28
28
  # @overload initialize(subject, predicate, object, options = {})
29
- # @param [Variable, Resource, nil] subject
30
- # @param [Variable, URI, nil] predicate
31
- # @param [Variable, Termm, nil] object
29
+ # @param [Variable, Resource, Symbol, nil] subject
30
+ # @param [Variable, URI, Symbol, nil] predicate
31
+ # @param [Variable, Termm, Symbol, nil] object
32
32
  # @param [Hash{Symbol => Object}] options
33
- # @option options [Variable, Resource, nil, false] :context (nil)
33
+ # @option options [Variable, Resource, Symbol, nil, false] :context (nil)
34
34
  # A context of nil matches any context, a context of false, matches only the default context.
35
35
  # @option options [Boolean] :optional (false)
36
+ #
37
+ # @note {Statement} treats symbols as interned {Node} instances, in a {Pattern}, they are treated as {Variable}.
36
38
  def initialize(subject = nil, predicate = nil, object = nil, options = {})
37
39
  super
38
40
  end
data/lib/rdf/reader.rb CHANGED
@@ -115,9 +115,18 @@ module RDF
115
115
  ##
116
116
  # Parses input from the given file name or URL.
117
117
  #
118
+ # @note A reader returned via this method may not be readable depending on the processing model of the specific reader, as the file is only open during the scope of `open`. The reader is intended to be accessed through a block.
119
+ #
120
+ # @example Parsing RDF statements from a file
121
+ # RDF::Reader.open("etc/doap.nt") do |reader|
122
+ # reader.each_statement do |statement|
123
+ # puts statement.inspect
124
+ # end
125
+ # end
126
+ #
118
127
  # @param [String, #to_s] filename
119
128
  # @param [Hash{Symbol => Object}] options
120
- # any additional options (see {RDF::Reader#initialize} and {RDF::Format.for})
129
+ # any additional options (see {RDF::Util::File.open_file}, {RDF::Reader#initialize} and {RDF::Format.for})
121
130
  # @option options [Symbol] :format (:ntriples)
122
131
  # @yield [reader]
123
132
  # @yieldparam [RDF::Reader] reader
@@ -138,7 +147,7 @@ module RDF
138
147
  if reader
139
148
  reader.new(file, options, &block)
140
149
  else
141
- raise FormatError, "unknown RDF format: #{format_options.inspect}"
150
+ raise FormatError, "unknown RDF format: #{format_options.inspect}\nThis may be resolved with a require of the 'linkeddata' gem."
142
151
  end
143
152
  end
144
153
  end
@@ -71,7 +71,7 @@ module RDF
71
71
  #
72
72
  # @param [String, Array<String>] filenames
73
73
  # @param [Hash{Symbol => Object}] options
74
- # Options from {RDF::Reader#initialize}, {RDF::Format.for} and {RDF::Repository#initialize}
74
+ # Options from {RDF::Repository#initialize} and {RDF::Mutable#load}
75
75
  # @yield [repository]
76
76
  # @yieldparam [Repository]
77
77
  # @return [void]
data/lib/rdf/vocab.rb CHANGED
@@ -2,6 +2,11 @@ module RDF
2
2
  ##
3
3
  # A {Vocabulary} represents an RDFS or OWL vocabulary.
4
4
  #
5
+ # A {Vocabulary} can also serve as a Domain Specific Language (DSL) for generating an RDF Graph definition for the vocabulary (see {RDF::Vocabulary#to_graph}).
6
+ #
7
+ # ### Defining a vocabulary using the DSL
8
+ # Vocabularies can be defined based on {RDF::Vocabulary} or {RDF::StrictVocabulary} using a simple Domain Specific Language (DSL). Terms of the vocabulary are specified using either `property` or `term` (alias), with the attributes of the term listed in a hash. See {property} for description of the hash.
9
+ #
5
10
  # ### Vocabularies:
6
11
  #
7
12
  # The following vocabularies are pre-defined for your convenience:
@@ -16,7 +21,7 @@ module RDF
16
21
  # * {RDF::FOAF} - Friend of a Friend (FOAF)
17
22
  # * {RDF::GEO} - WGS84 Geo Positioning (GEO)
18
23
  # * {RDF::GR} - Good Relations
19
- # * {RDF::HTTP} - Hypertext Transfer Protocol (HTTP)
24
+ # * {RDF::HT} - Hypertext Transfer Protocol (HTTP)
20
25
  # * {RDF::ICAL} - iCal
21
26
  # * {RDF::MA} - W3C Meda Annotations
22
27
  # * {RDF::OG} - FaceBook OpenGraph
@@ -55,6 +60,19 @@ module RDF
55
60
  # foaf[:name] #=> RDF::URI("http://xmlns.com/foaf/0.1/name")
56
61
  # foaf['mbox'] #=> RDF::URI("http://xmlns.com/foaf/0.1/mbox")
57
62
  #
63
+ # @example Generating RDF from a vocabulary definition
64
+ # graph = RDF::RDFS.to_graph
65
+ # graph.dump(:ntriples)
66
+ #
67
+ # @example Defining a simple vocabulary
68
+ # class EX < RDF::StrictVocabulay("http://example/ns#")
69
+ # term :Class,
70
+ # label: "My Class",
71
+ # comment: "Good to use as an example",
72
+ # "rdf:type" => "rdfs:Class",
73
+ # "rdfs:subClassOf" => "http://example/SuperClass"
74
+ # end
75
+ #
58
76
  # @see http://www.w3.org/TR/curie/
59
77
  # @see http://en.wikipedia.org/wiki/QName
60
78
  class Vocabulary
@@ -73,56 +91,125 @@ module RDF
73
91
  # Ruby's autoloading facility, meaning that `@@subclasses` will be
74
92
  # empty until each subclass has been touched or require'd.
75
93
  RDF::VOCABS.each { |v| require "rdf/vocab/#{v}" unless v == :rdf }
76
- @@subclasses.each(&block)
94
+ @@subclasses.select(&:name).each(&block)
77
95
  else
78
- # TODO: should enumerate vocabulary-specific defined properties.
96
+ __properties__.each(&block)
79
97
  end
80
98
  end
81
99
 
100
+ ##
101
+ # Is this a strict vocabulary, or a liberal vocabulary allowing arbitrary properties?
102
+ def strict?; false; end
103
+
82
104
  ##
83
105
  # @overload property
84
106
  # Returns `property` in the current vocabulary
85
- # @return [RDF::URI]
107
+ # @return [RDF::Vocabulary::Term]
86
108
  #
87
109
  # @overload property(name, options)
88
110
  # Defines a new property or class in the vocabulary.
89
- # Optional labels and comments are stripped of unnecessary whitespace.
90
111
  #
91
112
  # @param [String, #to_s] name
92
113
  # @param [Hash{Symbol => Object}] options
93
- # @option options [String, #to_s] :label
94
- # @option options [String, #to_s] :comment
114
+ # 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.
115
+ # @option options [String, Array<String>] :label
116
+ # Shortcut for `rdfs:label`, values are String interpreted as a {Literal}.
117
+ # @option options [String, Array<String>] :comment
118
+ # Shortcut for `rdfs:comment`, values are String interpreted as a {Literal}.
119
+ # @option options [String, Array<String>] :subClassOf
120
+ # Shortcut for `rdfs:subClassOf`, values are String interpreted as a {URI}.
121
+ # @option options [String, Array<String>] :subPropertyOf
122
+ # Shortcut for `rdfs:subPropertyOf`, values are String interpreted as a {URI}.
123
+ # @option options [String, Array<String>] :domain
124
+ # Shortcut for `rdfs:domain`, values are String interpreted as a {URI}.
125
+ # @option options [String, Array<String>] :range
126
+ # Shortcut for `rdfs:range`, values are String interpreted as a {URI}.
127
+ # @option options [String, Array<String>] :type
128
+ # Shortcut for `rdf:type`, values are String interpreted as a {URI}.
129
+ # @return [RDF::Vocabulary::Term]
95
130
  def property(*args)
96
131
  case args.length
97
132
  when 0
98
- RDF::URI.intern("#{self}property")
133
+ Term.intern("#{self}property", attributes: {label: "property", vocab: self})
99
134
  else
100
135
  name, options = args
101
- options ||= {}
102
- prop = RDF::URI.intern([to_s, name.to_s].join(''))
103
- @@labels[prop] = options[:label].to_s.strip.gsub(/\s+/m, ' ') if options[:label]
104
- @@comments[prop] = options[:comment].to_s.strip.gsub(/\s+/m, ' ') if options[:comment]
136
+ options = {:label => name.to_s, vocab: self}.merge(options || {})
137
+ prop = Term.intern([to_s, name.to_s].join(''), attributes: options)
138
+ props[prop] = options
105
139
  (class << self; self; end).send(:define_method, name) { prop } unless name.to_s == "property"
140
+ prop
141
+ end
142
+ end
143
+
144
+ # Alternate use for vocabulary terms, functionally equivalent to {#property}.
145
+ alias_method :term, :property
146
+ alias_method :__property__, :property
147
+
148
+ ##
149
+ # @return [Array<RDF::URI>] a list of properties in the current vocabulary
150
+ def properties
151
+ props.keys
152
+ end
153
+ alias_method :__properties__, :properties
154
+
155
+ ##
156
+ # Attempt to expand a Compact IRI/PName/QName using loaded vocabularies
157
+ #
158
+ # @param [String, #to_s] pname
159
+ # @return [RDF::URI]
160
+ def expand_pname(pname)
161
+ prefix, suffix = pname.to_s.split(":", 2)
162
+ if prefix == "rdf"
163
+ RDF[suffix]
164
+ elsif vocab = RDF::Vocabulary.each.detect {|v| v.__name__ && v.__prefix__ == prefix.to_sym}
165
+ suffix.to_s.empty? ? vocab.to_uri : vocab[suffix]
166
+ else
167
+ RDF::Vocabulary.find_term(pname) || RDF::URI(pname)
106
168
  end
107
169
  end
108
170
 
171
+ ##
172
+ # Return the Vocabulary associated with a URI
173
+ #
174
+ # @param [RDF::URI] uri
175
+ # @return [Vocabulary]
176
+ def find(uri)
177
+ RDF::Vocabulary.detect {|v| RDF::URI(uri).start_with?(v.to_uri)}
178
+ end
179
+
180
+ ##
181
+ # Return the Vocabulary term associated with a URI
182
+ #
183
+ # @param [RDF::URI] uri
184
+ # @return [Vocabulary::Term]
185
+ def find_term(uri)
186
+ uri = RDF::URI(uri)
187
+ return uri if uri.is_a?(Vocabulary::Term)
188
+ vocab = RDF::Vocabulary.detect {|v| uri.start_with?(v.to_uri)}
189
+ term = vocab[uri.to_s[vocab.to_uri.to_s.length..-1]] if vocab
190
+ end
191
+
109
192
  ##
110
193
  # Returns the URI for the term `property` in this vocabulary.
111
194
  #
112
195
  # @param [#to_s] property
113
196
  # @return [RDF::URI]
114
197
  def [](property)
115
- RDF::URI.intern([to_s, property.to_s].join(''))
198
+ if self.respond_to?(property.to_sym)
199
+ self.send(property.to_sym)
200
+ else
201
+ Term.intern([to_s, property.to_s].join(''), attributes: {vocab: self})
202
+ end
116
203
  end
117
204
 
118
205
  # @return [String] The label for the named property
119
206
  def label_for(name)
120
- @@labels[self[name]]
207
+ props.fetch(self[name], {}).fetch(:label, "")
121
208
  end
122
209
 
123
210
  # @return [String] The comment for the named property
124
211
  def comment_for(name)
125
- @@comments[self[name]]
212
+ props.fetch(self[name], {}).fetch(:comment, "")
126
213
  end
127
214
 
128
215
  ##
@@ -133,6 +220,65 @@ module RDF
133
220
  RDF::URI.intern(to_s)
134
221
  end
135
222
 
223
+ # For IRI compatibility
224
+ alias_method :to_iri, :to_uri
225
+
226
+ ##
227
+ # Return an enumerator over {RDF::Statement} defined for this vocabulary.
228
+ # @return [RDF::Enumerable::Enumerator]
229
+ # @see Object#enum_for
230
+ def enum_for(method = :each_statement, *args)
231
+ # Ensure that enumerators are, themselves, queryable
232
+ this = self
233
+ Enumerable::Enumerator.new do |yielder|
234
+ this.send(method, *args) {|*y| yielder << (y.length > 1 ? y : y.first)}
235
+ end
236
+ end
237
+ alias_method :to_enum, :enum_for
238
+
239
+ ##
240
+ # Enumerate each statement constructed from the defined vocabulary terms
241
+ #
242
+ # If a property value is known to be a {URI}, or expands to a {URI}, the `object` is a URI, otherwise, it will be a {Literal}.
243
+ #
244
+ # @yield statement
245
+ # @yieldparam [RDF::Statement]
246
+ def each_statement(&block)
247
+ props.each do |subject, attributes|
248
+ attributes.each do |prop, values|
249
+ prop = RDF::Vocabulary.expand_pname(prop) unless prop.is_a?(Symbol)
250
+ next unless prop
251
+ Array(values).each do |value|
252
+ case prop
253
+ when :type
254
+ prop = RDF.type
255
+ value = expand_pname(value)
256
+ when :subClassOf
257
+ prop = RDFS.subClassOf
258
+ value = expand_pname(value)
259
+ when :subPropertyOf
260
+ prop = RDFS.subPropertyOf
261
+ value = expand_pname(value)
262
+ when :domain
263
+ prop = RDFS.domain
264
+ value = expand_pname(value)
265
+ when :range
266
+ prop = RDFS.range
267
+ value = expand_pname(value)
268
+ when :label
269
+ prop = RDFS.label
270
+ when :comment
271
+ prop = RDFS.comment
272
+ else
273
+ value = RDF::Vocabulary.expand_pname(value)
274
+ end
275
+
276
+ block.call RDF::Statement(subject, prop, value)
277
+ end
278
+ end
279
+ end
280
+ end
281
+
136
282
  ##
137
283
  # Returns a string representation of this vocabulary class.
138
284
  #
@@ -141,6 +287,71 @@ module RDF
141
287
  @@uris.has_key?(self) ? @@uris[self].to_s : super
142
288
  end
143
289
 
290
+ ##
291
+ # Load a vocabulary, optionally from a separate location.
292
+ #
293
+ # @param [URI, #to_s] uri
294
+ # @param [Hash{Symbol => Object}] options
295
+ # @option options [String] class_name
296
+ # The class_name associated with the vocabulary, used for creating the class name of the vocabulary. This will create a new class named with a top-level constant based on `class_name`.
297
+ # @option options [URI, #to_s] :location
298
+ # Location from which to load the vocabulary, if not from `uri`.
299
+ # @option options [Array<Symbol>, Hash{Symbol => Hash}] :extra
300
+ # Extra terms to add to the vocabulary. In the first form, it is an array of symbols, for which terms are created. In the second, it is a Hash mapping symbols to property attributes, as described in {#property}.
301
+ # @return [RDF::Vocabulary] the loaded vocabulary
302
+ def load(uri, options = {})
303
+ source = options.fetch(:location, uri)
304
+ class_name = options[:class_name]
305
+ vocab = if class_name
306
+ Object.const_set(class_name, Class.new(self.create(uri)))
307
+ else
308
+ Class.new(self.create(uri))
309
+ end
310
+
311
+ graph = RDF::Graph.load(source)
312
+ term_defs = {}
313
+ graph.each do |statement|
314
+ next unless statement.subject.uri? && statement.subject.start_with?(uri)
315
+ name = statement.subject.to_s[uri.to_s.length..-1]
316
+ term = (term_defs[name.to_sym] ||= {})
317
+ key = case statement.predicate
318
+ when RDF.type then :type
319
+ when RDF::RDFS.subClassOf then :subClassOf
320
+ when RDF::RDFS.subPropertyOf then :subPropertyOf
321
+ when RDF::RDFS.range then :range
322
+ when RDF::RDFS.domain then :domain
323
+ when RDF::RDFS.comment then :comment
324
+ when RDF::RDFS.label then :label
325
+ else statement.predicate.pname
326
+ end
327
+
328
+ value = if statement.object.uri?
329
+ statement.object.pname
330
+ elsif statement.object.literal? && (statement.object.language || :en) == :en
331
+ statement.object.to_s
332
+ end
333
+
334
+ (term[key] ||= []) << value if value
335
+ end
336
+
337
+ # Create extra terms
338
+ term_defs = case options[:extra]
339
+ when Array
340
+ options[:extra].inject({}) {|memo, s| memo[s.to_sym] = {label: s.to_s}; memo}.merge(term_defs)
341
+ when Hash
342
+ options[:extra].merge(term_defs)
343
+ else
344
+ term_defs
345
+ end
346
+
347
+ # Create each term
348
+ term_defs.each do |term, attributes|
349
+ vocab.term term, attributes
350
+ end
351
+
352
+ vocab
353
+ end
354
+
144
355
  ##
145
356
  # Returns a developer-friendly representation of this vocabulary class.
146
357
  #
@@ -156,15 +367,39 @@ module RDF
156
367
  # Preserve the class name so that it can be obtained even for
157
368
  # vocabularies that define a `name` property:
158
369
  alias_method :__name__, :name
159
- end
160
370
 
161
- ##
162
- # Returns a suggested CURIE/QName prefix for this vocabulary class.
163
- #
164
- # @return [Symbol]
165
- # @since 0.3.0
166
- def self.__prefix__
167
- self.__name__.split('::').last.downcase.to_sym
371
+ ##
372
+ # Returns a suggested CURIE/PName prefix for this vocabulary class.
373
+ #
374
+ # @return [Symbol]
375
+ # @since 0.3.0
376
+ def __prefix__
377
+ __name__.split('::').last.downcase.to_sym
378
+ end
379
+
380
+ protected
381
+ def inherited(subclass) # @private
382
+ unless @@uri.nil?
383
+ @@subclasses << subclass unless %w(http://www.w3.org/1999/02/22-rdf-syntax-ns#).include?(@@uri)
384
+ subclass.send(:private_class_method, :new)
385
+ @@uris[subclass] = @@uri
386
+ @@uri = nil
387
+ end
388
+ super
389
+ end
390
+
391
+ def method_missing(property, *args, &block)
392
+ if %w(to_ary).include?(property.to_s)
393
+ super
394
+ elsif args.empty? && !to_s.empty?
395
+ Term.intern([to_s, property.to_s].join(''), attributes: {vocab: self})
396
+ else
397
+ super
398
+ end
399
+ end
400
+
401
+ private
402
+ def props; @properties ||= {}; end
168
403
  end
169
404
 
170
405
  # Undefine all superfluous instance methods:
@@ -189,7 +424,7 @@ module RDF
189
424
  # @param [#to_s] property
190
425
  # @return [URI]
191
426
  def [](property)
192
- RDF::URI.intern([to_s, property.to_s].join(''))
427
+ Term.intern([to_s, property.to_s].join(''), attributes: {vocab: self.class})
193
428
  end
194
429
 
195
430
  ##
@@ -200,6 +435,9 @@ module RDF
200
435
  RDF::URI.intern(to_s)
201
436
  end
202
437
 
438
+ # For IRI compatibility
439
+ alias_method :to_iri, :to_uri
440
+
203
441
  ##
204
442
  # Returns a string representation of this vocabulary.
205
443
  #
@@ -223,29 +461,13 @@ module RDF
223
461
  self
224
462
  end
225
463
 
226
- def self.inherited(subclass) # @private
227
- @@subclasses << subclass
228
- unless @@uri.nil?
229
- subclass.send(:private_class_method, :new)
230
- @@uris[subclass] = @@uri
231
- @@uri = nil
232
- end
233
- super
234
- end
235
-
236
- def self.method_missing(property, *args, &block)
237
- if args.empty? && @@uris.has_key?(self)
238
- self[property]
239
- else
240
- super
241
- end
242
- end
243
-
244
464
  def method_missing(property, *args, &block)
245
- if args.empty?
465
+ if %w(to_ary).include?(property.to_s)
466
+ super
467
+ elsif args.empty?
246
468
  self[property]
247
469
  else
248
- raise ArgumentError.new("wrong number of arguments (#{args.size} for 0)")
470
+ super
249
471
  end
250
472
  end
251
473
 
@@ -254,73 +476,151 @@ module RDF
254
476
  @@subclasses = [::RDF] # @private
255
477
  @@uris = {} # @private
256
478
  @@uri = nil # @private
257
- @@labels = {}
258
- @@comments = {}
259
- end # Vocabulary
260
479
 
261
- # Represents an RDF Vocabulary. The difference from {RDF::Vocabulary} is that
262
- # that every concept in the vocabulary is required to be declared. To assist
263
- # in this, an existing RDF representation of the vocabulary can be loaded as
264
- # the basis for concepts being available
265
- class StrictVocabulary < Vocabulary
266
- class << self
267
- begin
268
- # Redefines method_missing to the original definition
269
- # By remaining a subclass of Vocabulary, we remain available to
270
- # Vocabulary::each etc.
271
- define_method(:method_missing, BasicObject.instance_method(:method_missing))
272
- rescue NameError
273
- define_method(:method_missing, Kernel.instance_method(:method_missing))
480
+ # 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.
481
+ class Term < RDF::URI
482
+ # Attributes of this vocabulary term, used for finding `label` and `comment` and to serialize the term back to RDF.
483
+ # @return [Hash{Symbol,Resource => Term, #to_s}]
484
+ attr_accessor :attributes
485
+
486
+ ##
487
+ # @overload URI(uri, options = {})
488
+ # @param [URI, String, #to_s] uri
489
+ # @param [Hash{Symbol => Object}] options
490
+ # @option options [Boolean] :validate (false)
491
+ # @option options [Boolean] :canonicalize (false)
492
+ # @option options [Hash{Symbol,Resource => Term, #to_s}] :attributes
493
+ # Attributes of this vocabulary term, used for finding `label` and `comment` and to serialize the term back to RDF
494
+ #
495
+ # @overload URI(options = {})
496
+ # @param [Hash{Symbol => Object}] options
497
+ # @option options [Boolean] :validate (false)
498
+ # @option options [Boolean] :canonicalize (false)
499
+ # @option [Vocabulary] :vocab The {Vocabulary} associated with this term.
500
+ # @option [String, #to_s] :scheme The scheme component.
501
+ # @option [String, #to_s] :user The user component.
502
+ # @option [String, #to_s] :password The password component.
503
+ # @option [String, #to_s] :userinfo
504
+ # The userinfo component. If this is supplied, the user and password
505
+ # components must be omitted.
506
+ # @option [String, #to_s] :host The host component.
507
+ # @option [String, #to_s] :port The port component.
508
+ # @option [String, #to_s] :authority
509
+ # The authority component. If this is supplied, the user, password,
510
+ # userinfo, host, and port components must be omitted.
511
+ # @option [String, #to_s] :path The path component.
512
+ # @option [String, #to_s] :query The query component.
513
+ # @option [String, #to_s] :fragment The fragment component.
514
+ # @option options [Hash{Symbol,Resource => Term, #to_s}] :attributes
515
+ # Attributes of this vocabulary term, used for finding `label` and `comment` and to serialize the term back to RDF
516
+ def initialize(*args)
517
+ options = args.last.is_a?(Hash) ? args.last : {}
518
+ @attributes = options.fetch(:attributes)
519
+ super
274
520
  end
275
521
 
276
522
  ##
277
- # @overload property
278
- # Returns `property` in the current vocabulary
279
- # @return [RDF::URI]
523
+ # Vocabulary of this term.
280
524
  #
281
- # @overload property(name, options)
282
- # Defines a new property or class in the vocabulary.
283
- # Optional labels and comments are stripped of unnecessary whitespace.
525
+ # @return [RDF::Vocabulary]
526
+ def vocab; @attributes.fetch(:vocab); end
527
+
528
+ ##
529
+ # Returns a duplicate copy of `self`.
284
530
  #
285
- # @param [String, #to_s] name
286
- # @param [Hash{Symbol => Object}] options
287
- # @option options [String, #to_s] :label
288
- # @option options [String, #to_s] :comment
289
- def property(*args)
290
- case args.length
291
- when 0
292
- RDF::URI.intern("#{self}property")
293
- else
294
- name, options = args
295
- options ||= {}
296
- prop = RDF::URI.intern([to_s, name.to_s].join(''))
297
- @@properties[prop] = true
298
- @@labels[prop] = options[:label].to_s.strip.gsub(/\s+/m, ' ') if options[:label]
299
- @@comments[prop] = options[:comment].to_s.strip.gsub(/\s+/m, ' ') if options[:comment]
300
- (class << self; self; end).send(:define_method, name) { prop } unless name.to_s == "property"
301
- end
531
+ # @return [RDF::URI]
532
+ def dup
533
+ self.class.new((@value || @object).dup, attributes: @attributes)
302
534
  end
303
535
 
304
536
  ##
305
- # @return [Array<RDF::URI>] a list of properties in the current vocabulary
306
- def properties
307
- @@properties.keys
537
+ # Determine if the URI is a valid according to RFC3987
538
+ #
539
+ # @return [Boolean] `true` or `false`
540
+ # @since 0.3.9
541
+ def valid?
542
+ # Validate relative to RFC3987
543
+ to_s.match(RDF::URI::IRI) || false
308
544
  end
309
545
 
310
- def [](name)
311
- prop = RDF::URI.intern([to_s, name.to_s].join(''))
312
- @@properties.fetch(prop) #raises KeyError on missing value
313
- return prop
546
+ ##
547
+ # Is this a class term?
548
+ # @return [Boolean]
549
+ def class?
550
+ !!(self.type.to_s =~ /Class/)
551
+ end
552
+
553
+ ##
554
+ # Is this a class term?
555
+ # @return [Boolean]
556
+ def property?
557
+ !!(self.type.to_s =~ /Property/)
558
+ end
559
+
560
+ ##
561
+ # Is this a class term?
562
+ # @return [Boolean]
563
+ def datatype?
564
+ !!(self.type.to_s =~ /Datatype/)
565
+ end
566
+
567
+ ##
568
+ # Is this neither a class, property or datatype term?
569
+ # @return [Boolean]
570
+ def other?
571
+ !!(self.type.to_s !~ /(Class|Property|Datatype)/)
572
+ end
573
+
574
+ ##
575
+ # Returns a <code>String</code> representation of the URI object's state.
576
+ #
577
+ # @return [String] The URI object's state, as a <code>String</code>.
578
+ def inspect
579
+ sprintf("#<%s:%#0x URI:%s>", Term.to_s, self.object_id, self.to_s)
580
+ end
581
+
582
+ # Implement accessor to symbol attributes
583
+ def respond_to?(method)
584
+ @attributes.has_key?(method) || super
585
+ end
586
+
587
+ protected
588
+ # Implement accessor to symbol attributes
589
+ def method_missing(method, *args, &block)
590
+ case method
591
+ when :comment
592
+ @attributes.fetch(method, "")
593
+ when :label
594
+ @attributes.fetch(method, to_s.split(/[\/\#]/).last)
595
+ when :type, :subClassOf, :subPropertyOf, :domain, :range
596
+ Array(@attributes[method]).map {|v| RDF::Vocabulary.expand_pname(v)}
597
+ else
598
+ super
599
+ end
314
600
  end
315
601
  end
602
+ end # Vocabulary
316
603
 
317
- begin
604
+ # Represents an RDF Vocabulary. The difference from {RDF::Vocabulary} is that
605
+ # that every concept in the vocabulary is required to be declared. To assist
606
+ # in this, an existing RDF representation of the vocabulary can be loaded as
607
+ # the basis for concepts being available
608
+ class StrictVocabulary < Vocabulary
609
+ class << self
610
+ # Redefines method_missing to the original definition
611
+ # By remaining a subclass of Vocabulary, we remain available to
612
+ # Vocabulary::each etc.
318
613
  define_method(:method_missing, BasicObject.instance_method(:method_missing))
319
- rescue NameError
320
- define_method(:method_missing, Kernel.instance_method(:method_missing))
321
- end
322
614
 
323
- private
324
- @@properties = {}
615
+ ##
616
+ # Is this a strict vocabulary, or a liberal vocabulary allowing arbitrary properties?
617
+ def strict?; true; end
618
+
619
+ def [](name)
620
+ prop = super
621
+ props.fetch(prop) #raises KeyError on missing value
622
+ return prop
623
+ end
624
+ end
325
625
  end # StrictVocabulary
326
626
  end # RDF