rdf 1.1.3 → 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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