rdf 1.1.0p4 → 1.1.0

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