openlogic-rdf 0.3.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. data/AUTHORS +3 -0
  2. data/CREDITS +9 -0
  3. data/README +361 -0
  4. data/UNLICENSE +24 -0
  5. data/VERSION +1 -0
  6. data/bin/rdf +18 -0
  7. data/etc/doap.nt +62 -0
  8. data/lib/df.rb +1 -0
  9. data/lib/rdf/cli.rb +200 -0
  10. data/lib/rdf/format.rb +383 -0
  11. data/lib/rdf/mixin/countable.rb +39 -0
  12. data/lib/rdf/mixin/durable.rb +31 -0
  13. data/lib/rdf/mixin/enumerable.rb +637 -0
  14. data/lib/rdf/mixin/indexable.rb +26 -0
  15. data/lib/rdf/mixin/inferable.rb +5 -0
  16. data/lib/rdf/mixin/mutable.rb +191 -0
  17. data/lib/rdf/mixin/queryable.rb +265 -0
  18. data/lib/rdf/mixin/readable.rb +15 -0
  19. data/lib/rdf/mixin/type_check.rb +21 -0
  20. data/lib/rdf/mixin/writable.rb +152 -0
  21. data/lib/rdf/model/graph.rb +263 -0
  22. data/lib/rdf/model/list.rb +731 -0
  23. data/lib/rdf/model/literal/boolean.rb +121 -0
  24. data/lib/rdf/model/literal/date.rb +73 -0
  25. data/lib/rdf/model/literal/datetime.rb +72 -0
  26. data/lib/rdf/model/literal/decimal.rb +86 -0
  27. data/lib/rdf/model/literal/double.rb +189 -0
  28. data/lib/rdf/model/literal/integer.rb +126 -0
  29. data/lib/rdf/model/literal/numeric.rb +184 -0
  30. data/lib/rdf/model/literal/time.rb +87 -0
  31. data/lib/rdf/model/literal/token.rb +47 -0
  32. data/lib/rdf/model/literal/xml.rb +39 -0
  33. data/lib/rdf/model/literal.rb +373 -0
  34. data/lib/rdf/model/node.rb +156 -0
  35. data/lib/rdf/model/resource.rb +28 -0
  36. data/lib/rdf/model/statement.rb +296 -0
  37. data/lib/rdf/model/term.rb +77 -0
  38. data/lib/rdf/model/uri.rb +570 -0
  39. data/lib/rdf/model/value.rb +133 -0
  40. data/lib/rdf/nquads.rb +152 -0
  41. data/lib/rdf/ntriples/format.rb +48 -0
  42. data/lib/rdf/ntriples/reader.rb +239 -0
  43. data/lib/rdf/ntriples/writer.rb +219 -0
  44. data/lib/rdf/ntriples.rb +104 -0
  45. data/lib/rdf/query/pattern.rb +329 -0
  46. data/lib/rdf/query/solution.rb +252 -0
  47. data/lib/rdf/query/solutions.rb +237 -0
  48. data/lib/rdf/query/variable.rb +221 -0
  49. data/lib/rdf/query.rb +404 -0
  50. data/lib/rdf/reader.rb +511 -0
  51. data/lib/rdf/repository.rb +389 -0
  52. data/lib/rdf/transaction.rb +161 -0
  53. data/lib/rdf/util/aliasing.rb +63 -0
  54. data/lib/rdf/util/cache.rb +139 -0
  55. data/lib/rdf/util/file.rb +38 -0
  56. data/lib/rdf/util/uuid.rb +36 -0
  57. data/lib/rdf/util.rb +6 -0
  58. data/lib/rdf/version.rb +19 -0
  59. data/lib/rdf/vocab/cc.rb +18 -0
  60. data/lib/rdf/vocab/cert.rb +13 -0
  61. data/lib/rdf/vocab/dc.rb +63 -0
  62. data/lib/rdf/vocab/dc11.rb +23 -0
  63. data/lib/rdf/vocab/doap.rb +45 -0
  64. data/lib/rdf/vocab/exif.rb +168 -0
  65. data/lib/rdf/vocab/foaf.rb +69 -0
  66. data/lib/rdf/vocab/geo.rb +13 -0
  67. data/lib/rdf/vocab/http.rb +26 -0
  68. data/lib/rdf/vocab/owl.rb +59 -0
  69. data/lib/rdf/vocab/rdfs.rb +17 -0
  70. data/lib/rdf/vocab/rsa.rb +12 -0
  71. data/lib/rdf/vocab/rss.rb +14 -0
  72. data/lib/rdf/vocab/sioc.rb +93 -0
  73. data/lib/rdf/vocab/skos.rb +36 -0
  74. data/lib/rdf/vocab/wot.rb +21 -0
  75. data/lib/rdf/vocab/xhtml.rb +9 -0
  76. data/lib/rdf/vocab/xsd.rb +58 -0
  77. data/lib/rdf/vocab.rb +215 -0
  78. data/lib/rdf/writer.rb +475 -0
  79. data/lib/rdf.rb +192 -0
  80. metadata +173 -0
@@ -0,0 +1,104 @@
1
+ module RDF
2
+ ##
3
+ # **`RDF::NTriples`** provides support for the N-Triples serialization
4
+ # format.
5
+ #
6
+ # N-Triples is a line-based plain-text format for encoding an RDF graph.
7
+ # It is a very restricted, explicit and well-defined subset of both
8
+ # [Turtle](http://www.w3.org/TeamSubmission/turtle/) and
9
+ # [Notation3](http://www.w3.org/TeamSubmission/n3/) (N3).
10
+ #
11
+ # The MIME content type for N-Triples files is `text/plain` and the
12
+ # recommended file extension is `.nt`.
13
+ #
14
+ # An example of an RDF statement in N-Triples format:
15
+ #
16
+ # <http://rubyforge.org/> <http://purl.org/dc/terms/title> "RubyForge" .
17
+ #
18
+ # Installation
19
+ # ------------
20
+ #
21
+ # This is the only RDF serialization format that is directly supported by
22
+ # RDF.rb. Support for other formats is available in the form of add-on
23
+ # gems, e.g. 'rdf-xml' or 'rdf-json'.
24
+ #
25
+ # Documentation
26
+ # -------------
27
+ #
28
+ # * {RDF::NTriples::Format}
29
+ # * {RDF::NTriples::Reader}
30
+ # * {RDF::NTriples::Writer}
31
+ #
32
+ # @example Requiring the `RDF::NTriples` module explicitly
33
+ # require 'rdf/ntriples'
34
+ #
35
+ # @see http://www.w3.org/TR/rdf-testcases/#ntriples
36
+ # @see http://en.wikipedia.org/wiki/N-Triples
37
+ # @see http://librdf.org/ntriples/
38
+ #
39
+ # @author [Arto Bendiken](http://ar.to/)
40
+ module NTriples
41
+ require 'iconv' unless "".respond_to?(:encode )# needed on Ruby 1.8.x
42
+ require 'rdf/ntriples/format'
43
+ autoload :Reader, 'rdf/ntriples/reader'
44
+ autoload :Writer, 'rdf/ntriples/writer'
45
+
46
+ ##
47
+ # Reconstructs an RDF value from its serialized N-Triples
48
+ # representation.
49
+ #
50
+ # @param [String] data
51
+ # @return [RDF::Value]
52
+ # @see RDF::NTriples::Reader.unserialize
53
+ # @since 0.1.5
54
+ def self.unserialize(data)
55
+ Reader.unserialize(data)
56
+ end
57
+
58
+ ##
59
+ # Returns the serialized N-Triples representation of the given RDF
60
+ # value.
61
+ #
62
+ # @param [RDF::Value] value
63
+ # @return [String]
64
+ # @see RDF::NTriples::Writer.serialize
65
+ # @since 0.1.5
66
+ def self.serialize(value)
67
+ Writer.serialize(value)
68
+ end
69
+
70
+ ##
71
+ # @param [String] string
72
+ # @return [String]
73
+ # @see RDF::NTriples::Reader.unescape
74
+ # @since 0.2.2
75
+ def self.unescape(string)
76
+ Reader.unescape(string)
77
+ end
78
+
79
+ ##
80
+ # @param [String] string
81
+ # @return [String]
82
+ # @see RDF::NTriples::Writer.escape
83
+ # @since 0.2.2
84
+ def self.escape(string)
85
+ Writer.escape(string)
86
+ end
87
+ end # NTriples
88
+
89
+ ##
90
+ # Extensions for `RDF::Value`.
91
+ module Value
92
+ ##
93
+ # Returns the N-Triples representation of this value.
94
+ #
95
+ # This method is only available when the 'rdf/ntriples' serializer has
96
+ # been explicitly required.
97
+ #
98
+ # @return [String]
99
+ # @since 0.2.1
100
+ def to_ntriples
101
+ RDF::NTriples.serialize(self)
102
+ end
103
+ end # Value
104
+ end # RDF
@@ -0,0 +1,329 @@
1
+ module RDF; class Query
2
+ ##
3
+ # An RDF query pattern.
4
+ class Pattern < RDF::Statement
5
+ ##
6
+ # @private
7
+ # @since 0.2.2
8
+ def self.from(pattern, options = {})
9
+ case pattern
10
+ when Pattern then pattern
11
+ when Array, Statement
12
+ self.new(pattern[0], pattern[1], pattern[2], options.merge(:context => pattern[3]))
13
+ when Hash then self.new(options.merge(pattern))
14
+ else raise ArgumentError, "expected RDF::Query::Pattern, RDF::Statement, Hash, or Array, but got #{pattern.inspect}"
15
+ end
16
+ end
17
+
18
+ ##
19
+ # @overload initialize(options = {})
20
+ # @param [Hash{Symbol => Object}] options
21
+ # @option options [Variable, Resource] :subject (nil)
22
+ # @option options [Variable, URI] :predicate (nil)
23
+ # @option options [Variable, Term] :object (nil)
24
+ # @option options [Variable, Resource] :context (nil)
25
+ # A context of nil matches any context, a context of false, matches only the default context.
26
+ # @option options [Boolean] :optional (false)
27
+ #
28
+ # @overload initialize(subject, predicate, object, options = {})
29
+ # @param [Variable, Resource] subject
30
+ # @param [Variable, URI] predicate
31
+ # @param [Variable, Term] object
32
+ # @param [Hash{Symbol => Object}] options
33
+ # @option options [Variable, Resource] :context (nil)
34
+ # @option options [Boolean] :optional (false)
35
+ def initialize(subject = nil, predicate = nil, object = nil, options = {})
36
+ super
37
+ end
38
+
39
+ ##
40
+ # @private
41
+ def initialize!
42
+ @context = Variable.new(@context) if @context.is_a?(Symbol)
43
+ @subject = Variable.new(@subject) if @subject.is_a?(Symbol)
44
+ @predicate = Variable.new(@predicate) if @predicate.is_a?(Symbol)
45
+ @object = Variable.new(@object) if @object.is_a?(Symbol)
46
+ super
47
+ end
48
+
49
+ ##
50
+ # Any additional options for this pattern.
51
+ #
52
+ # @return [Hash]
53
+ attr_reader :options
54
+
55
+ ##
56
+ # The estimated cost of this pattern (for query optimization).
57
+ #
58
+ # @return [Numeric]
59
+ attr_accessor :cost
60
+
61
+ ##
62
+ # Returns `true` if this is a blank pattern, with all terms being `nil`.
63
+ #
64
+ # @return [Boolean] `true` or `false`
65
+ # @since 0.3.0
66
+ def blank?
67
+ subject.nil? && predicate.nil? && object.nil? && context.nil?
68
+ end
69
+
70
+ ##
71
+ # Returns `true` if this is a constant pattern, with all terms being
72
+ # either URIs, blank nodes, or literals.
73
+ #
74
+ # A constant pattern is structurally and functionally equivalent to an
75
+ # RDF statement.
76
+ #
77
+ # @return [Boolean] `true` or `false`
78
+ # @since 0.3.0
79
+ def constant?
80
+ !(variable?)
81
+ end
82
+
83
+ ##
84
+ # Returns `true` if this is a variable pattern, with any term being
85
+ # `nil` or a variable.
86
+ #
87
+ # @return [Boolean] `true` or `false`
88
+ # @since 0.3.0
89
+ def variable?
90
+ subject.nil? || predicate.nil? || object.nil? || context.nil? || has_variables?
91
+ end
92
+
93
+ ##
94
+ # Returns `true` if this pattern contains any variables.
95
+ #
96
+ # @return [Boolean] `true` or `false`
97
+ # @since 0.3.0
98
+ def has_variables?
99
+ subject.is_a?(Variable) ||
100
+ predicate.is_a?(Variable) ||
101
+ object.is_a?(Variable) ||
102
+ context.is_a?(Variable)
103
+ end
104
+ alias_method :variables?, :has_variables?
105
+
106
+ ##
107
+ # Returns `true` if this is an optional pattern.
108
+ #
109
+ # @example
110
+ # Pattern.new(:s, :p, :o).optional? #=> false
111
+ # Pattern.new(:s, :p, :o, :optional => true).optional? #=> true
112
+ #
113
+ # @return [Boolean] `true` or `false`
114
+ # @since 0.3.0
115
+ def optional?
116
+ !!options[:optional]
117
+ end
118
+
119
+ ##
120
+ # Executes this query pattern on the given `queryable` object.
121
+ #
122
+ # Values are matched using using Queryable#query_pattern.
123
+ #
124
+ # If the optional `bindings` are given, variables will be substituted with their values
125
+ # when executing the query.
126
+ #
127
+ # To match triples only in the default context, set context to `false'.
128
+ #
129
+ # @example
130
+ # Pattern.new(:s, :p, :o).execute(RDF::Repository.load('data.nt'))
131
+ #
132
+ # @param [RDF::Queryable] queryable
133
+ # the graph or repository to query
134
+ # @param [Hash{Symbol => RDF::Term}] bindings
135
+ # optional variable bindings to use
136
+ # @yield [statement]
137
+ # each matching statement
138
+ # @yieldparam [RDF::Statement] statement
139
+ # an RDF statement matching this pattern
140
+ # @return [Enumerator]
141
+ # an enumerator yielding matching statements
142
+ # @see RDF::Queryable#query
143
+ # @since 0.3.0
144
+ def execute(queryable, bindings = {}, &block)
145
+ query = {
146
+ :subject => subject.is_a?(Variable) && bindings[subject.to_sym] ? bindings[subject.to_sym] : subject,
147
+ :predicate => predicate.is_a?(Variable) && bindings[predicate.to_sym] ? bindings[predicate.to_sym] : predicate,
148
+ :object => object.is_a?(Variable) && bindings[object.to_sym] ? bindings[object.to_sym] : object,
149
+ :context => context.is_a?(Variable) && bindings[context.to_sym] ? bindings[context.to_sym] : context,
150
+ }.delete_if{|k,v| v.nil?}
151
+
152
+ # Do all the variable terms refer to distinct variables?
153
+ variables = self.variables
154
+ if variable_count == variables.size
155
+ # If so, we can just let the repository implementation handle
156
+ # everything and yield matching statements directly:
157
+ queryable.query(query, &block)
158
+
159
+ # No, some terms actually refer to the same variable...
160
+ else
161
+ # Figure out which terms refer to the same variable:
162
+ terms = variables.each_key.find do |name|
163
+ terms = variable_terms(name)
164
+ break terms if terms.size > 1
165
+ end
166
+ queryable.query(query) do |statement|
167
+ # Only yield those matching statements where the variable
168
+ # constraint is also satisfied:
169
+ # FIXME: `Array#uniq` uses `#eql?` and `#hash`, not `#==`
170
+ if matches = terms.map { |term| statement.send(term) }.uniq.size.equal?(1)
171
+ block.call(statement)
172
+ end
173
+ end
174
+ end
175
+ end
176
+
177
+ ##
178
+ # Returns a query solution constructed by binding any variables in this
179
+ # pattern with the corresponding terms in the given `statement`.
180
+ #
181
+ # @example
182
+ # pattern.solution(statement)
183
+ #
184
+ # @param [RDF::Statement] statement
185
+ # an RDF statement to bind terms from
186
+ # @return [RDF::Query::Solution]
187
+ # @since 0.3.0
188
+ def solution(statement)
189
+ RDF::Query::Solution.new do |solution|
190
+ solution[subject.to_sym] = statement.subject if subject.is_a?(Variable)
191
+ solution[predicate.to_sym] = statement.predicate if predicate.is_a?(Variable)
192
+ solution[object.to_sym] = statement.object if object.is_a?(Variable)
193
+ solution[context.to_sym] = statement.context if context.is_a?(Variable)
194
+ end
195
+ end
196
+
197
+ ##
198
+ # Returns the variable terms in this pattern.
199
+ #
200
+ # @example
201
+ # Pattern.new(RDF::Node.new, :p, 123).variable_terms #=> [:predicate]
202
+ #
203
+ # @param [Symbol, #to_sym] name
204
+ # an optional variable name
205
+ # @return [Array<Symbol>]
206
+ # @since 0.3.0
207
+ def variable_terms(name = nil)
208
+ terms = []
209
+ terms << :subject if subject.is_a?(Variable) && (!name || name.eql?(subject.name))
210
+ terms << :predicate if predicate.is_a?(Variable) && (!name || name.eql?(predicate.name))
211
+ terms << :object if object.is_a?(Variable) && (!name || name.eql?(object.name))
212
+ terms << :context if context.is_a?(Variable) && (!name || name.eql?(context.name))
213
+ terms
214
+ end
215
+
216
+ ##
217
+ # Returns the number of variables in this pattern.
218
+ #
219
+ # Note: this does not count distinct variables, and will therefore e.g.
220
+ # return 3 even if two terms are actually the same variable.
221
+ #
222
+ # @return [Integer] (0..3)
223
+ def variable_count
224
+ count = 0
225
+ count += 1 if subject.is_a?(Variable)
226
+ count += 1 if predicate.is_a?(Variable)
227
+ count += 1 if object.is_a?(Variable)
228
+ count += 1 if context.is_a?(Variable)
229
+ count
230
+ end
231
+ alias_method :cardinality, :variable_count
232
+ alias_method :arity, :variable_count
233
+
234
+ ##
235
+ # Returns all variables in this pattern.
236
+ #
237
+ # Note: this returns a hash containing distinct variables only.
238
+ #
239
+ # @return [Hash{Symbol => Variable}]
240
+ def variables
241
+ variables = {}
242
+ variables.merge!(subject.variables) if subject.is_a?(Variable)
243
+ variables.merge!(predicate.variables) if predicate.is_a?(Variable)
244
+ variables.merge!(object.variables) if object.is_a?(Variable)
245
+ variables.merge!(context.variables) if context.is_a?(Variable)
246
+ variables
247
+ end
248
+
249
+ ##
250
+ # Returns `true` if this pattern contains bindings.
251
+ #
252
+ # @return [Boolean] `true` or `false`
253
+ def bindings?
254
+ !bindings.empty?
255
+ end
256
+
257
+ ##
258
+ # Returns the number of bindings in this pattern.
259
+ #
260
+ # @return [Integer] (0..3)
261
+ def binding_count
262
+ bindings.size
263
+ end
264
+
265
+ ##
266
+ # Returns all bindings in this pattern.
267
+ #
268
+ # @return [Hash{Symbol => RDF::Term}]
269
+ def bindings
270
+ bindings = {}
271
+ bindings.merge!(subject.bindings) if subject.is_a?(Variable)
272
+ bindings.merge!(predicate.bindings) if predicate.is_a?(Variable)
273
+ bindings.merge!(object.bindings) if object.is_a?(Variable)
274
+ bindings.merge!(context.bindings) if context.is_a?(Variable)
275
+ bindings
276
+ end
277
+
278
+ ##
279
+ # Returns `true` if all variables in this pattern are bound.
280
+ #
281
+ # @return [Boolean] `true` or `false`
282
+ def bound?
283
+ !variables.empty? && variables.values.all?(&:bound?)
284
+ end
285
+
286
+ ##
287
+ # Returns all bound variables in this pattern.
288
+ #
289
+ # @return [Hash{Symbol => Variable}]
290
+ def bound_variables
291
+ variables.reject { |name, variable| variable.unbound? }
292
+ end
293
+
294
+ ##
295
+ # Returns `true` if all variables in this pattern are unbound.
296
+ #
297
+ # @return [Boolean] `true` or `false`
298
+ def unbound?
299
+ !variables.empty? && variables.values.all?(&:unbound?)
300
+ end
301
+
302
+ ##
303
+ # Returns all unbound variables in this pattern.
304
+ #
305
+ # @return [Hash{Symbol => Variable}]
306
+ def unbound_variables
307
+ variables.reject { |name, variable| variable.bound? }
308
+ end
309
+
310
+ ##
311
+ # Returns a string representation of this pattern.
312
+ #
313
+ # @return [String]
314
+ def to_s
315
+ StringIO.open do |buffer| # FIXME in RDF::Statement
316
+ buffer << 'OPTIONAL ' if optional?
317
+ buffer << [subject, predicate, object].map do |r|
318
+ r.is_a?(RDF::Query::Variable) ? r.to_s : RDF::NTriples.serialize(r)
319
+ end.join(" ")
320
+ buffer << case context
321
+ when nil, false then " ."
322
+ when Variable then " #{context.to_s} ."
323
+ else " #{RDF::NTriples.serialize(context)} ."
324
+ end
325
+ buffer.string
326
+ end
327
+ end
328
+ end # Pattern
329
+ end; end # RDF::Query
@@ -0,0 +1,252 @@
1
+ class RDF::Query
2
+ ##
3
+ # An RDF query solution.
4
+ #
5
+ # @example Iterating over every binding in the solution
6
+ # solution.each_binding { |name, value| puts value.inspect }
7
+ # solution.each_variable { |variable| puts variable.value.inspect }
8
+ #
9
+ # @example Iterating over every value in the solution
10
+ # solution.each_value { |value| puts value.inspect }
11
+ #
12
+ # @example Checking whether a variable is bound or unbound
13
+ # solution.bound?(:title)
14
+ # solution.unbound?(:mbox)
15
+ #
16
+ # @example Retrieving the value of a bound variable
17
+ # solution[:mbox]
18
+ # solution.mbox
19
+ #
20
+ # @example Retrieving all bindings in the solution as a `Hash`
21
+ # solution.to_hash #=> {:mbox => "jrhacker@example.org", ...}
22
+ #
23
+ class Solution
24
+ # Undefine all superfluous instance methods:
25
+ undef_method(*(instance_methods.map(&:to_sym) - [:__id__, :__send__, :__class__, :__eval__,
26
+ :object_id, :dup, :instance_eval, :inspect, :to_s,
27
+ :class, :is_a?, :respond_to?, :respond_to_missing?]))
28
+
29
+ include Enumerable
30
+
31
+ ##
32
+ # Initializes the query solution.
33
+ #
34
+ # @param [Hash{Symbol => RDF::Term}] bindings
35
+ # @yield [solution]
36
+ def initialize(bindings = {}, &block)
37
+ @bindings = bindings.to_hash
38
+
39
+ if block_given?
40
+ case block.arity
41
+ when 1 then block.call(self)
42
+ else instance_eval(&block)
43
+ end
44
+ end
45
+ end
46
+
47
+ # @private
48
+ attr_reader :bindings
49
+
50
+ ##
51
+ # Enumerates over every variable binding in this solution.
52
+ #
53
+ # @yield [name, value]
54
+ # @yieldparam [Symbol] name
55
+ # @yieldparam [RDF::Term] value
56
+ # @return [Enumerator]
57
+ def each_binding(&block)
58
+ @bindings.each(&block)
59
+ end
60
+ alias_method :each, :each_binding
61
+
62
+ ##
63
+ # Enumerates over every variable name in this solution.
64
+ #
65
+ # @yield [name]
66
+ # @yieldparam [Symbol] name
67
+ # @return [Enumerator]
68
+ def each_name(&block)
69
+ @bindings.each_key(&block)
70
+ end
71
+ alias_method :each_key, :each_name
72
+
73
+ ##
74
+ # Enumerates over every variable value in this solution.
75
+ #
76
+ # @yield [value]
77
+ # @yieldparam [RDF::Term] value
78
+ # @return [Enumerator]
79
+ def each_value(&block)
80
+ @bindings.each_value(&block)
81
+ end
82
+
83
+ ##
84
+ # Returns `true` if this solution contains bindings for any of the given
85
+ # `variables`.
86
+ #
87
+ # @param [Array<Symbol, #to_sym>] variables
88
+ # an array of variables to check
89
+ # @return [Boolean] `true` or `false`
90
+ # @since 0.3.0
91
+ def has_variables?(variables)
92
+ variables.any? { |variable| bound?(variable) }
93
+ end
94
+
95
+ ##
96
+ # Enumerates over every variable in this solution.
97
+ #
98
+ # @yield [variable]
99
+ # @yieldparam [Variable]
100
+ # @return [Enumerator]
101
+ def each_variable(&block)
102
+ @bindings.each do |name, value|
103
+ block.call(Variable.new(name, value))
104
+ end
105
+ end
106
+
107
+ ##
108
+ # Returns `true` if the variable `name` is bound in this solution.
109
+ #
110
+ # @param [Symbol, #to_sym] name
111
+ # the variable name
112
+ # @return [Boolean] `true` or `false`
113
+ def bound?(name)
114
+ !unbound?(name)
115
+ end
116
+
117
+ ##
118
+ # Returns `true` if the variable `name` is unbound in this solution.
119
+ #
120
+ # @param [Symbol, #to_sym] name
121
+ # the variable name
122
+ # @return [Boolean] `true` or `false`
123
+ def unbound?(name)
124
+ @bindings[name.to_sym].nil?
125
+ end
126
+
127
+ ##
128
+ # Returns the value of the variable `name`.
129
+ #
130
+ # @param [Symbol, #to_sym] name
131
+ # the variable name
132
+ # @return [RDF::Term]
133
+ def [](name)
134
+ @bindings[name.to_sym]
135
+ end
136
+
137
+ ##
138
+ # Binds or rebinds the variable `name` to the given `value`.
139
+ #
140
+ # @param [Symbol, #to_sym] name
141
+ # the variable name
142
+ # @param [RDF::Term] value
143
+ # @return [RDF::Term]
144
+ # @since 0.3.0
145
+ def []=(name, value)
146
+ @bindings[name.to_sym] = value
147
+ end
148
+
149
+ ##
150
+ # Merges the bindings from the given `other` query solution into this
151
+ # one, overwriting any existing ones having the same name.
152
+ #
153
+ # @param [RDF::Query::Solution, #to_hash] other
154
+ # another query solution or hash bindings
155
+ # @return [void] self
156
+ # @since 0.3.0
157
+ def merge!(other)
158
+ @bindings.merge!(other.to_hash)
159
+ self
160
+ end
161
+
162
+ ##
163
+ # Merges the bindings from the given `other` query solution with a copy
164
+ # of this one.
165
+ #
166
+ # @param [RDF::Query::Solution, #to_hash] other
167
+ # another query solution or hash bindings
168
+ # @return [RDF::Query::Solution]
169
+ # @since 0.3.0
170
+ def merge(other)
171
+ self.class.new(@bindings.dup).merge!(other)
172
+ end
173
+
174
+ ##
175
+ # Compatible Mappings
176
+ # Two solution mappings μ1 and μ2 are compatible if, for every variable v in dom(μ1) and in dom(μ2), μ1(v) = μ2(v).
177
+ #
178
+ # @param [RDF::Query::Solution, #to_hash] other
179
+ # another query solution or hash bindings
180
+ # @return [Boolean]
181
+ def compatible?(other)
182
+ @bindings.all? do |k, v|
183
+ !other.to_hash.has_key?(k) || other[k].eql?(v)
184
+ end
185
+ end
186
+
187
+ ##
188
+ # Isomorphic Mappings
189
+ # Two solution mappings μ1 and μ2 are isomorphic if,
190
+ # for every variable v in dom(μ1) and in dom(μ2), μ1(v) = μ2(v).
191
+ #
192
+ # @param [RDF::Query::Solution, #to_hash] other
193
+ # another query solution or hash bindings
194
+ # @return [Boolean]
195
+ def isomorphic_with?(other)
196
+ @bindings.all? do |k, v|
197
+ !other.to_hash.has_key?(k) || other[k].eql?(v)
198
+ end
199
+ end
200
+
201
+ ##
202
+ # @return [Array<Array(Symbol, RDF::Term)>}
203
+ def to_a
204
+ @bindings.to_a
205
+ end
206
+
207
+ ##
208
+ # @return [Hash{Symbol => RDF::Term}}
209
+ def to_hash
210
+ @bindings.dup
211
+ end
212
+
213
+ ##
214
+ # Integer hash of this solution
215
+ # @return [Integer]
216
+ def hash
217
+ @bindings.hash
218
+ end
219
+
220
+ ##
221
+ # Equivalence of solution
222
+ def eql?(other)
223
+ other.is_a?(Solution) && @bindings.eql?(other.bindings)
224
+ end
225
+ alias_method :==, :eql?
226
+
227
+ ##
228
+ # Equals of solution
229
+ def ==(other)
230
+ other.is_a?(Solution) && @bindings == other.bindings
231
+ end
232
+
233
+ ##
234
+ # @return [String]
235
+ def inspect
236
+ sprintf("#<%s:%#0x(%s)>", self.class.name, __id__, @bindings.inspect)
237
+ end
238
+
239
+ protected
240
+
241
+ ##
242
+ # @param [Symbol] name
243
+ # @return [RDF::Term]
244
+ def method_missing(name, *args, &block)
245
+ if args.empty? && @bindings.has_key?(name.to_sym)
246
+ @bindings[name.to_sym]
247
+ else
248
+ super # raises NoMethodError
249
+ end
250
+ end
251
+ end # Solution
252
+ end # RDF::Query