rdf 1.1.0p4 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. checksums.yaml +6 -14
  2. data/README +33 -33
  3. data/VERSION +1 -1
  4. data/lib/rdf.rb +60 -12
  5. data/lib/rdf/cli.rb +7 -1
  6. data/lib/rdf/cli/vocab-loader.rb +240 -0
  7. data/lib/rdf/format.rb +2 -2
  8. data/lib/rdf/mixin/enumerable.rb +12 -4
  9. data/lib/rdf/mixin/queryable.rb +13 -13
  10. data/lib/rdf/model/graph.rb +5 -4
  11. data/lib/rdf/model/list.rb +15 -4
  12. data/lib/rdf/model/literal.rb +2 -1
  13. data/lib/rdf/model/statement.rb +10 -1
  14. data/lib/rdf/model/term.rb +8 -0
  15. data/lib/rdf/model/uri.rb +107 -2
  16. data/lib/rdf/model/value.rb +8 -0
  17. data/lib/rdf/ntriples/reader.rb +5 -4
  18. data/lib/rdf/query.rb +47 -12
  19. data/lib/rdf/query/solutions.rb +29 -29
  20. data/lib/rdf/reader.rb +13 -3
  21. data/lib/rdf/repository.rb +1 -0
  22. data/lib/rdf/util/file.rb +86 -6
  23. data/lib/rdf/vocab.rb +158 -58
  24. data/lib/rdf/vocab/cc.rb +28 -11
  25. data/lib/rdf/vocab/cert.rb +127 -9
  26. data/lib/rdf/vocab/dc.rb +242 -60
  27. data/lib/rdf/vocab/dc11.rb +42 -20
  28. data/lib/rdf/vocab/doap.rb +121 -42
  29. data/lib/rdf/vocab/exif.rb +540 -165
  30. data/lib/rdf/vocab/foaf.rb +353 -66
  31. data/lib/rdf/vocab/geo.rb +40 -10
  32. data/lib/rdf/vocab/gr.rb +1094 -0
  33. data/lib/rdf/vocab/http.rb +81 -23
  34. data/lib/rdf/vocab/ical.rb +361 -0
  35. data/lib/rdf/vocab/ma.rb +281 -69
  36. data/lib/rdf/vocab/og.rb +98 -0
  37. data/lib/rdf/vocab/owl.rb +226 -56
  38. data/lib/rdf/vocab/prov.rb +489 -0
  39. data/lib/rdf/vocab/rdfs.rb +38 -14
  40. data/lib/rdf/vocab/rsa.rb +25 -9
  41. data/lib/rdf/vocab/rss.rb +29 -11
  42. data/lib/rdf/vocab/schema.rb +3729 -647
  43. data/lib/rdf/vocab/sioc.rb +224 -89
  44. data/lib/rdf/vocab/skos.rb +141 -33
  45. data/lib/rdf/vocab/skosxl.rb +43 -0
  46. data/lib/rdf/vocab/v.rb +154 -0
  47. data/lib/rdf/vocab/vcard.rb +337 -0
  48. data/lib/rdf/vocab/void.rb +142 -0
  49. data/lib/rdf/vocab/wdrs.rb +129 -0
  50. data/lib/rdf/vocab/wot.rb +52 -18
  51. data/lib/rdf/vocab/xhtml.rb +3 -6
  52. data/lib/rdf/vocab/xhv.rb +239 -0
  53. data/lib/rdf/writer.rb +3 -3
  54. metadata +81 -14
data/lib/rdf/format.rb CHANGED
@@ -142,8 +142,8 @@ module RDF
142
142
 
143
143
  # If we have a sample, use that for format detection
144
144
  if sample = (options[:sample] if options.is_a?(Hash)) || (yield if block_given?)
145
- sample = sample.to_s
146
- sample.force_encoding(Encoding::UTF_8) if sample.respond_to?(:force_encoding)
145
+ sample = sample.dup.to_s
146
+ sample.force_encoding(Encoding::ASCII_8BIT) if sample.respond_to?(:force_encoding)
147
147
  # Given a sample, perform format detection across the appropriate formats, choosing
148
148
  # the first that matches
149
149
  format ||= @@subclasses
@@ -67,20 +67,23 @@ module RDF
67
67
  # Supported features include:
68
68
  # * `:context` supports statements with a context, allowing multiple contexts
69
69
  # * `:inferrence` supports RDFS inferrence of queryable contents.
70
+ # * `:validatable` allows a concrete Enumerable implementation to indicate that it does or does not support valididty checking. By default implementations are assumed to support validity checking.
70
71
  #
71
72
  # @param [Symbol, #to_sym] feature
72
73
  # @return [Boolean]
73
74
  # @since 0.3.5
74
75
  def supports?(feature)
75
- false
76
+ feature == :validity
76
77
  end
77
78
 
78
79
  ##
79
80
  # Returns `true` if all statements are valid
80
81
  #
81
82
  # @return [Boolean] `true` or `false`
83
+ # @raise [NotImplementedError] unless enumerable supports validation
82
84
  # @since 0.3.11
83
85
  def valid?
86
+ raise NotImplementedError, "#{self.class} does not support validation" unless supports?(:validity)
84
87
  each_statement do |s|
85
88
  return false if s.invalid?
86
89
  end
@@ -91,6 +94,7 @@ module RDF
91
94
  # Returns `true` if value is not valid
92
95
  #
93
96
  # @return [Boolean] `true` or `false`
97
+ # @raise [NotImplementedError] unless enumerable supports validation
94
98
  # @since 0.2.1
95
99
  def invalid?
96
100
  !valid?
@@ -102,7 +106,7 @@ module RDF
102
106
  # @raise [ArgumentError] if the value is invalid
103
107
  # @since 0.3.9
104
108
  def validate!
105
- raise ArgumentError if invalid?
109
+ raise ArgumentError if supports?(:validity) && invalid?
106
110
  end
107
111
  alias_method :validate, :validate!
108
112
 
@@ -226,7 +230,9 @@ module RDF
226
230
  # @return [Enumerator]
227
231
  # @see #each_triple
228
232
  def enum_triple
229
- enum_for(:each_triple)
233
+ Countable::Enumerator.new do |yielder|
234
+ each_triple {|s, p, o| yielder << [s, p, o]}
235
+ end
230
236
  end
231
237
  alias_method :enum_triples, :enum_triple
232
238
 
@@ -287,7 +293,9 @@ module RDF
287
293
  # @return [Enumerator]
288
294
  # @see #each_quad
289
295
  def enum_quad
290
- enum_for(:each_quad)
296
+ Countable::Enumerator.new do |yielder|
297
+ each_quad {|s, p, o, c| yielder << [s, p, o, c]}
298
+ end
291
299
  end
292
300
  alias_method :enum_quads, :enum_quad
293
301
 
@@ -37,7 +37,8 @@ module RDF
37
37
  # @yieldparam [RDF::Statement, RDF::Query::Solution] statement
38
38
  # Statement or Solution
39
39
  # @yieldreturn [void] ignored
40
- # @return [Enumerator]
40
+ # @return [Enumerator, Query::Solutions]
41
+ # Returns an enumerator over statements or query solutions, if passed an {RDF::Query}
41
42
  # @see RDF::Queryable#query_pattern
42
43
  def query(pattern, options = {}, &block)
43
44
  raise TypeError, "#{self} is not readable" if respond_to?(:readable?) && !readable?
@@ -45,17 +46,16 @@ module RDF
45
46
  case pattern
46
47
  # A basic graph pattern (BGP) query:
47
48
  when Query
48
- if block_given?
49
- before_query(pattern) if respond_to?(:before_query)
50
- if method(:query_execute).arity == 1
51
- query_execute(pattern, &block)
52
- else
53
- query_execute(pattern, options, &block)
54
- end
55
- after_query(pattern) if respond_to?(:after_query)
49
+ before_query(pattern) if respond_to?(:before_query)
50
+ solutions = if method(:query_execute).arity == 1
51
+ query_execute(pattern, &block)
52
+ else
53
+ query_execute(pattern, options, &block)
56
54
  end
57
- enum_for(:query_execute, pattern)
58
-
55
+ after_query(pattern) if respond_to?(:after_query)
56
+ # Returns the solutions, not an enumerator
57
+ solutions
58
+
59
59
  # A simple triple/quad pattern query:
60
60
  else
61
61
  pattern = Query::Pattern.from(pattern)
@@ -116,7 +116,7 @@ module RDF
116
116
  # query execution by breaking down the query into its constituent
117
117
  # triple patterns and invoking `RDF::Query::Pattern#execute` on each
118
118
  # pattern.
119
- query.execute(self, options).each(&block)
119
+ query.execute(self, options, &block)
120
120
  end
121
121
  protected :query_execute
122
122
 
@@ -296,7 +296,7 @@ module RDF
296
296
  # Ensure that enumerators are, themselves, queryable
297
297
  this = self
298
298
  Queryable::Enumerator.new do |yielder|
299
- this.send(method, *args) {|y| yielder << y}
299
+ this.send(method, *args) {|*y| yielder << (y.length > 1 ? y : y.first)}
300
300
  end
301
301
  end
302
302
  alias_method :to_enum, :enum_for
@@ -100,10 +100,11 @@ module RDF
100
100
  # @option options [RDF::Queryable] :data (RDF::Repository.new)
101
101
  # Storage behind this graph.
102
102
  # @raise [ArgumentError] if a `data` does not support contexts.
103
- # @note contexts are only useful when used as a projection
104
- # on a `:data` which supports contexts. Otherwise, there is no
105
- # such thing as a named graph in RDF 1.1, a repository may have
106
- # graphs which are named, but the name is not a property of the graph.
103
+ # @note
104
+ # Contexts are only useful when used as a projection
105
+ # on a `:data` which supports contexts. Otherwise, there is no
106
+ # such thing as a named graph in RDF 1.1, a repository may have
107
+ # graphs which are named, but the name is not a property of the graph.
107
108
  # @overload initialize(options)
108
109
  # @param [Hash{Symbol => Object}] options
109
110
  # @option options [RDF::Queryable] :data (RDF::Repository.new)
@@ -42,8 +42,8 @@ module RDF
42
42
  @subject = subject || RDF.nil
43
43
  @graph = graph || RDF::Graph.new
44
44
 
45
- unless values.to_a.empty?
46
- values.reverse_each {|value| self.unshift(value)}
45
+ unless Array(values).empty?
46
+ Array(values).reverse_each {|value| self.unshift(value)}
47
47
  end
48
48
 
49
49
  if block_given?
@@ -761,6 +761,18 @@ module RDF
761
761
  each.to_set
762
762
  end
763
763
 
764
+ ##
765
+ # Returns the subject of the list.
766
+ #
767
+ # @example
768
+ # RDF::List[].to_term #=> "RDF[:nil]"
769
+ # RDF::List[1, 2, 3].to_term #=> "RDF::Node"
770
+ #
771
+ # @return [RDF::Resource]
772
+ def to_term
773
+ subject
774
+ end
775
+
764
776
  ##
765
777
  # Returns a string representation of this list.
766
778
  #
@@ -784,8 +796,7 @@ module RDF
784
796
  if self.equal?(NIL)
785
797
  'RDF::List::NIL'
786
798
  else
787
- #sprintf("#<%s:%#0x(%s)>", self.class.name, __id__, subject.to_s)
788
- sprintf("#<%s:%#0x(%s)>", self.class.name, __id__, to_s) # FIXME
799
+ sprintf("#<%s:%#0x(%s)>", self.class.name, __id__, join(', '))
789
800
  end
790
801
  end
791
802
  end
@@ -164,7 +164,8 @@ module RDF
164
164
  @object = value
165
165
  @string = options[:lexical] if options[:lexical]
166
166
  @string = value if !defined?(@string) && value.is_a?(String)
167
- @string.encode!(Encoding::UTF_8) if @string && @string.respond_to?(:encode!)
167
+ @string = @string.encode(Encoding::UTF_8) if @string
168
+ @object = @string if @string && @object.is_a?(String)
168
169
  @language = options[:language].to_s.to_sym if options[:language]
169
170
  @datatype = RDF::URI(options[:datatype]) if options[:datatype]
170
171
  @datatype ||= self.class.const_get(:DATATYPE) if self.class.const_defined?(:DATATYPE)
@@ -59,6 +59,7 @@ module RDF
59
59
  # @option options [RDF::Term] :object (nil)
60
60
  # @option options [RDF::Resource] :context (nil)
61
61
  # Note, in RDF 1.1, a context MUST be an IRI.
62
+ # @return [RDF::Statement]
62
63
  #
63
64
  # @overload initialize(subject, predicate, object, options = {})
64
65
  # @param [RDF::Resource] subject
@@ -66,6 +67,7 @@ module RDF
66
67
  # @param [RDF::Term] object
67
68
  # @param [Hash{Symbol => Object}] options
68
69
  # @option options [RDF::Resource] :context (nil)
70
+ # @return [RDF::Statement]
69
71
  def initialize(subject = nil, predicate = nil, object = nil, options = {})
70
72
  case subject
71
73
  when Hash
@@ -88,12 +90,19 @@ module RDF
88
90
  # @private
89
91
  def initialize!
90
92
  @context = Node.intern(@context) if @context.is_a?(Symbol)
91
- @subject = Node.intern(@subject) if @subject.is_a?(Symbol)
93
+ @subject = case @subject
94
+ when nil then nil
95
+ when Symbol then Node.intern(@subject)
96
+ when Term then @subject
97
+ when Value then @subject.to_term
98
+ else raise_error(ArgumentError, "expected subject to be nil or a term, was #{@subject.inspect}")
99
+ end
92
100
  @predicate = Node.intern(@predicate) if @predicate.is_a?(Symbol)
93
101
  @object = case @object
94
102
  when nil then nil
95
103
  when Symbol then Node.intern(@object)
96
104
  when Term then @object
105
+ when Value then @object.to_term
97
106
  else Literal.new(@object)
98
107
  end
99
108
  end
@@ -72,6 +72,14 @@ module RDF
72
72
  true
73
73
  end
74
74
 
75
+ ##
76
+ # Returns itself.
77
+ #
78
+ # @return [RDF::Value]
79
+ def to_term
80
+ self
81
+ end
82
+
75
83
  protected
76
84
  ##
77
85
  # Escape a term using standard character escapes
data/lib/rdf/model/uri.rb CHANGED
@@ -1146,15 +1146,120 @@ module RDF
1146
1146
  normalized_user + (password ? ":#{normalized_password}" : "") if userinfo
1147
1147
  end
1148
1148
 
1149
+ ##
1150
+ # Converts the query component to a Hash value.
1151
+ #
1152
+ # @example
1153
+ # RDF::URI.new("?one=1&two=2&three=3").query_values
1154
+ # #=> {"one" => "1", "two" => "2", "three" => "3"}
1155
+ # RDF::URI.new("?one=two&one=three").query_values(Array)
1156
+ # #=> [["one", "two"], ["one", "three"]]
1157
+ # RDF::URI.new("?one=two&one=three").query_values(Hash)
1158
+ # #=> {"one" => ["two", "three"]}
1159
+ #
1160
+ # @param [Class] return_type (Hash)
1161
+ # The return type desired. Value must be either # `Hash` or `Array`.
1162
+ # @return [Hash, Array] The query string parsed as a Hash or Array object.
1163
+ def query_values(return_type=Hash)
1164
+ raise ArgumentError, "Invalid return type. Must be Hash or Array." unless [Hash, Array].include?(return_type)
1165
+ return nil if query.nil?
1166
+ query.to_s.split('&').
1167
+ inject(return_type == Hash ? {} : []) do |memo,kv|
1168
+ k,v = kv.to_s.split('=', 2)
1169
+ next if k.to_s.empty?
1170
+ k = ::URI.decode(k)
1171
+ v = ::URI.decode(v) if v
1172
+ if return_type == Hash
1173
+ case memo[k]
1174
+ when nil then memo[k] = v
1175
+ when Array then memo[k] << v
1176
+ else memo[k] = [memo[k], v]
1177
+ end
1178
+ else
1179
+ memo << [k, v].compact
1180
+ end
1181
+ memo
1182
+ end
1183
+ end
1184
+
1185
+ ##
1186
+ # Sets the query component for this URI from a Hash object.
1187
+ # An empty Hash or Array will result in an empty query string.
1188
+ #
1189
+ # @example
1190
+ # uri.query_values = {:a => "a", :b => ["c", "d", "e"]}
1191
+ # uri.query
1192
+ # # => "a=a&b=c&b=d&b=e"
1193
+ # uri.query_values = [['a', 'a'], ['b', 'c'], ['b', 'd'], ['b', 'e']]
1194
+ # uri.query
1195
+ # # => "a=a&b=c&b=d&b=e"
1196
+ # uri.query_values = [['a', 'a'], ['b', ['c', 'd', 'e']]]
1197
+ # uri.query
1198
+ # # => "a=a&b=c&b=d&b=e"
1199
+ # uri.query_values = [['flag'], ['key', 'value']]
1200
+ # uri.query
1201
+ # # => "flag&key=value"
1202
+ #
1203
+ # @param [Hash, #to_hash, Array] value The new query values.
1204
+ def query_values=(value)
1205
+ if value.nil?
1206
+ self.query = nil
1207
+ return
1208
+ end
1209
+
1210
+ value = value.to_hash if value.respond_to?(:to_hash)
1211
+ self.query = case value
1212
+ when Array
1213
+ value.map do |(k,v)|
1214
+ k = normalize_segment(k.to_s, UNRESERVED)
1215
+ if v.nil?
1216
+ k
1217
+ else
1218
+ "#{k}=#{normalize_segment(v.to_s, UNRESERVED)}"
1219
+ end
1220
+ end
1221
+ when Hash
1222
+ value.map do |k, v|
1223
+ k = normalize_segment(k.to_s, UNRESERVED)
1224
+ if v.nil?
1225
+ k
1226
+ else
1227
+ Array(v).map do |vv|
1228
+ if vv === TrueClass
1229
+ k
1230
+ else
1231
+ "#{k}=#{normalize_segment(vv.to_s, UNRESERVED)}"
1232
+ end
1233
+ end.join("&")
1234
+ end
1235
+ end
1236
+ else
1237
+ raise TypeError,
1238
+ "Can't convert #{value.class} into Hash."
1239
+ end.join("&")
1240
+ end
1241
+
1242
+ ##
1243
+ # The HTTP request URI for this URI. This is the path and the
1244
+ # query string.
1245
+ #
1246
+ # @return [String] The request URI required for an HTTP request.
1247
+ def request_uri
1248
+ return nil if absolute? && scheme !~ /^https?$/
1249
+ res = path.to_s.empty? ? "/" : path
1250
+ res += "?#{self.query}" if self.query
1251
+ return res
1252
+ end
1253
+
1149
1254
  private
1150
1255
 
1151
1256
  ##
1152
1257
  # Normalize a segment using a character range
1153
1258
  #
1154
- # @param [String] segment
1259
+ # @param [String] value
1155
1260
  # @param [Regexp] expr
1156
1261
  # @param [Boolean] downcase
1157
- # @result [String]
1262
+ # @return [String]
1158
1263
  def normalize_segment(value, expr, downcase = false)
1159
1264
  if value
1160
1265
  value = value.dup if value.frozen?
@@ -185,6 +185,14 @@ module RDF
185
185
  self
186
186
  end
187
187
 
188
+ ##
189
+ # Returns an `RDF::Term` representation of `self`.
190
+ #
191
+ # @return [RDF::Value]
192
+ def to_term
193
+ raise NotImplementedError, "#{self.class}#read_triple" # override in subclasses
194
+ end
195
+
188
196
  ##
189
197
  # Returns a developer-friendly representation of `self`.
190
198
  #
@@ -62,7 +62,7 @@ module RDF::NTriples
62
62
  # 166s
63
63
  PN_CHARS = /-|[0-9]|#{PN_CHARS_U}|#{U_CHARS2}/.freeze
64
64
  # 159s
65
- ECHAR = /\\[tbnrf\\"']/.freeze
65
+ ECHAR = /\\[tbnrf\\"]/.freeze
66
66
  # 18
67
67
  IRIREF = /<((?:#{IRI_RANGE}|#{ESCAPE_CHAR})*)>/.freeze
68
68
  # 141s
@@ -84,7 +84,7 @@ module RDF::NTriples
84
84
  SUBJECT = Regexp.union(URIREF, NODEID).freeze
85
85
  PREDICATE = Regexp.union(URIREF).freeze
86
86
  OBJECT = Regexp.union(URIREF, NODEID, LITERAL).freeze
87
- END_OF_STATEMENT = /^\s*\.\s*$/.freeze
87
+ END_OF_STATEMENT = /^\s*\.\s*(?:#.*)?$/.freeze
88
88
 
89
89
  ##
90
90
  # Reconstructs an RDF value from its serialized N-Triples
@@ -159,7 +159,7 @@ module RDF::NTriples
159
159
  # @see http://blog.grayproductions.net/articles/understanding_m17n
160
160
  # @see http://yehudakatz.com/2010/05/17/encodings-unabridged/
161
161
  def self.unescape(string)
162
- string = string.to_s.force_encoding(Encoding::ASCII_8BIT)
162
+ string = string.dup.force_encoding(Encoding::ASCII_8BIT)
163
163
 
164
164
  # Decode \t|\n|\r|\"|\\ character escapes:
165
165
  ESCAPE_CHARS.each { |escape| string.gsub!(escape.inspect[1...-1], escape) }
@@ -169,7 +169,7 @@ module RDF::NTriples
169
169
  (string.sub!(ESCAPE_SURROGATE) do
170
170
  if ESCAPE_SURROGATE1.include?($1.hex) && ESCAPE_SURROGATE2.include?($2.hex)
171
171
  s = [$1, $2].pack('H*H*')
172
- s = s.force_encoding(Encoding::UTF_16BE).encode!(Encoding::UTF_8)
172
+ s.force_encoding(Encoding::UTF_16BE).encode!(Encoding::UTF_8)
173
173
  else
174
174
  s = [$1.hex].pack('U*') << '\u' << $2
175
175
  end
@@ -210,6 +210,7 @@ module RDF::NTriples
210
210
  subject = read_uriref || read_node || fail_subject
211
211
  predicate = read_uriref(:intern => true) || fail_predicate
212
212
  object = read_uriref || read_node || read_literal || fail_object
213
+
213
214
  if validate? && !read_eos
214
215
  raise RDF::ReaderError, "expected end of statement in line #{lineno}: #{current_line.inspect}"
215
216
  end
data/lib/rdf/query.rb CHANGED
@@ -93,6 +93,27 @@ module RDF
93
93
  self.new(patterns, options, &block).execute(queryable, options)
94
94
  end
95
95
 
96
+ ##
97
+ # Cast values as Solutions
98
+ # @overload Solutions()
99
+ # @return [Solutions] returns Solutions.new()
100
+ #
101
+ # @overload Solutions(solutions)
102
+ # @return [Solutions] returns the argument
103
+ #
104
+ # @overload Solutions(array)
105
+ # @param [Array] array
106
+ # @return [Solutions] returns the array extended with solutions
107
+ #
108
+ # @overload Solutions(*args)
109
+ # @param [Array<Solution>] args
110
+ # @return [Solutions] returns new solutions including the arguments, which must each be a {Solution}
111
+ def self.Solutions(*args)
112
+ return args.first if args.length == 1 && args.first.is_a?(Solutions)
113
+ args = args.first if args.first.is_a?(Array) && args.length == 1
114
+ return Solutions.new(args)
115
+ end
116
+
96
117
  ##
97
118
  # The variables used in this query.
98
119
  #
@@ -160,7 +181,7 @@ module RDF
160
181
  @options = patterns.last.is_a?(Hash) ? patterns.pop.dup : {}
161
182
  patterns << @options if patterns.empty?
162
183
  @variables = {}
163
- @solutions = @options.delete(:solutions) || Solutions.new
184
+ @solutions = Query::Solutions(@options.delete(:solutions))
164
185
  context = @options.fetch(:context, @options.fetch(:name, nil))
165
186
  @options.delete(:context)
166
187
  @options.delete(:name)
@@ -257,13 +278,17 @@ module RDF
257
278
  # overrides default context defined on query.
258
279
  # @option options [RDF::Resource, RDF::Query::Variable, false] name (nil)
259
280
  # Alias for `:context`.
260
- # @option options [Hash{Symbol => RDF::Term}] solutions
281
+ # @option options [RDF::Query::Solutions] solutions
261
282
  # optional initial solutions for chained queries
283
+ # @yield [solution]
284
+ # each matching solution
285
+ # @yieldparam [RDF::Query::Solution] solution
286
+ # @yieldreturn [void] ignored
262
287
  # @return [RDF::Query::Solutions]
263
288
  # the resulting solution sequence
264
289
  # @see http://www.holygoat.co.uk/blog/entry/2005-10-25-1
265
290
  # @see http://www.w3.org/TR/sparql11-query/#emptyGroupPattern
266
- def execute(queryable, options = {})
291
+ def execute(queryable, options = {}, &block)
267
292
  validate!
268
293
  options = options.dup
269
294
 
@@ -273,10 +298,13 @@ module RDF
273
298
  # Use provided solutions to allow for query chaining
274
299
  # Otherwise, a quick empty solution simplifies the logic below; no special case for
275
300
  # the first pattern
276
- @solutions = options[:solutions] || (Solutions.new << RDF::Query::Solution.new({}))
301
+ @solutions = Query::Solutions(options[:solutions] || Solution.new)
277
302
 
278
303
  # If there are no patterns, just return the empty solution
279
- return @solutions if empty?
304
+ if empty?
305
+ @solutions.each(&block) if block_given?
306
+ return @solutions
307
+ end
280
308
 
281
309
  patterns = @patterns
282
310
  context = options.fetch(:context, options.fetch(:name, self.context))
@@ -289,14 +317,14 @@ module RDF
289
317
  patterns.first.context = context
290
318
  end
291
319
  end
292
-
320
+
293
321
  patterns.each do |pattern|
294
322
 
295
- old_solutions, @solutions = @solutions, Solutions.new
323
+ old_solutions, @solutions = @solutions, Query::Solutions()
296
324
 
297
325
  options[:bindings].keys.each do |variable|
298
326
  if pattern.variables.include?(variable)
299
- unbound_solutions, old_solutions = old_solutions, Solutions.new
327
+ unbound_solutions, old_solutions = old_solutions, Query::Solutions()
300
328
  options[:bindings][variable].each do |binding|
301
329
  unbound_solutions.each do |solution|
302
330
  old_solutions << solution.merge(variable => binding)
@@ -325,11 +353,18 @@ module RDF
325
353
  # that can have constraints are often broad without them.
326
354
  # We have no solutions at all:
327
355
  return @solutions if @solutions.empty?
328
- # We have no solutions for variables we should have solutions for:
329
- if !pattern.optional? && pattern.variables.keys.any? { |variable| !@solutions.variable_names.include?(variable) }
330
- return Solutions.new
356
+
357
+ if !pattern.optional?
358
+ # We have no solutions for variables we should have solutions for:
359
+ need_vars = pattern.variables.keys
360
+ @solutions.each do |solution|
361
+ break if need_vars.empty?
362
+ need_vars -= solution.bindings.keys
363
+ end
364
+ return Query::Solutions() unless need_vars.empty?
331
365
  end
332
366
  end
367
+ @solutions.each(&block) if block_given?
333
368
  @solutions
334
369
  end
335
370
 
@@ -354,7 +389,7 @@ module RDF
354
389
  # @return [Boolean]
355
390
  # @see #failed?
356
391
  def matched?
357
- !@failed
392
+ !failed?
358
393
  end
359
394
 
360
395
  # Add patterns from another query to form a new Query