sparql-client 2.0.2 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,4 @@
1
- module SPARQL; class Client
1
+ class SPARQL::Client
2
2
  ##
3
3
  # A SPARQL query builder.
4
4
  #
@@ -17,12 +17,6 @@ module SPARQL; class Client
17
17
  # @return [Hash{Symbol => Object}]
18
18
  attr_reader :options
19
19
 
20
- ##
21
- # Values returned from previous query.
22
- #
23
- # @return [Array<[key, RDF::Value]>]
24
- attr_reader :values
25
-
26
20
  ##
27
21
  # Creates a boolean `ASK` query.
28
22
  #
@@ -32,8 +26,8 @@ module SPARQL; class Client
32
26
  # @param [Hash{Symbol => Object}] options (see {#initialize})
33
27
  # @return [Query]
34
28
  # @see http://www.w3.org/TR/sparql11-query/#ask
35
- def self.ask(options = {})
36
- self.new(:ask, options)
29
+ def self.ask(**options)
30
+ self.new(:ask, **options)
37
31
  end
38
32
 
39
33
  ##
@@ -42,17 +36,22 @@ module SPARQL; class Client
42
36
  # @example SELECT * WHERE { ?s ?p ?o . }
43
37
  # Query.select.where([:s, :p, :o])
44
38
  #
39
+ # @example SELECT ?s WHERE {?s ?p ?o .}
40
+ # Query.select(:s).where([:s, :p, :o])
41
+ #
42
+ # @example SELECT COUNT(?uri as ?c) WHERE {?uri a owl:Class}
43
+ # Query.select(count: {uri: :c}).where([:uri, RDF.type, RDF::OWL.Class])
44
+ #
45
45
  # @param [Array<Symbol>] variables
46
46
  # @return [Query]
47
47
  #
48
- # @overload self.select(*variables, options)
48
+ # @overload self.select(*variables, **options)
49
49
  # @param [Array<Symbol>] variables
50
50
  # @param [Hash{Symbol => Object}] options (see {#initialize})
51
51
  # @return [Query]
52
52
  # @see http://www.w3.org/TR/sparql11-query/#select
53
- def self.select(*variables)
54
- options = variables.last.is_a?(Hash) ? variables.pop : {}
55
- self.new(:select, options).select(*variables)
53
+ def self.select(*variables, **options)
54
+ self.new(:select, **options).select(*variables)
56
55
  end
57
56
 
58
57
  ##
@@ -64,14 +63,13 @@ module SPARQL; class Client
64
63
  # @param [Array<Symbol, RDF::URI>] variables
65
64
  # @return [Query]
66
65
  #
67
- # @overload self.describe(*variables, options)
66
+ # @overload self.describe(*variables, **options)
68
67
  # @param [Array<Symbol, RDF::URI>] variables
69
68
  # @param [Hash{Symbol => Object}] options (see {#initialize})
70
69
  # @return [Query]
71
70
  # @see http://www.w3.org/TR/sparql11-query/#describe
72
- def self.describe(*variables)
73
- options = variables.last.is_a?(Hash) ? variables.pop : {}
74
- self.new(:describe, options).describe(*variables)
71
+ def self.describe(*variables, **options)
72
+ self.new(:describe, **options).describe(*variables)
75
73
  end
76
74
 
77
75
  ##
@@ -83,27 +81,30 @@ module SPARQL; class Client
83
81
  # @param [Array<RDF::Query::Pattern, Array>] patterns
84
82
  # @return [Query]
85
83
  #
86
- # @overload self.construct(*variables, options)
84
+ # @overload self.construct(*variables, **options)
87
85
  # @param [Array<RDF::Query::Pattern, Array>] patterns
88
86
  # @param [Hash{Symbol => Object}] options (see {#initialize})
89
87
  # @return [Query]
90
88
  # @see http://www.w3.org/TR/sparql11-query/#construct
91
- def self.construct(*patterns)
92
- options = patterns.last.is_a?(Hash) ? patterns.pop : {}
93
- self.new(:construct, options).construct(*patterns) # FIXME
89
+ def self.construct(*patterns, **options)
90
+ self.new(:construct, **options).construct(*patterns) # FIXME
94
91
  end
95
92
 
96
93
  ##
97
94
  # @param [Symbol, #to_s] form
98
- # @overload self.construct(*variables, options)
95
+ # @overload self.construct(*variables, **options)
99
96
  # @param [Symbol, #to_s] form
100
97
  # @param [Hash{Symbol => Object}] options (see {Client#initialize})
98
+ # @option options [Hash{Symbol => Symbol}] :count
99
+ # Contents are symbols relating a variable described within the query,
100
+ # to the projected variable.
101
+ #
101
102
  # @yield [query]
102
103
  # @yieldparam [Query]
103
- def initialize(form = :ask, options = {}, &block)
104
+ def initialize(form = :ask, **options, &block)
104
105
  @subqueries = []
105
106
  @form = form.respond_to?(:to_sym) ? form.to_sym : form.to_s.to_sym
106
- super([], options, &block)
107
+ super([], **options, &block)
107
108
  end
108
109
 
109
110
  ##
@@ -121,11 +122,21 @@ module SPARQL; class Client
121
122
  # @example SELECT * WHERE { ?s ?p ?o . }
122
123
  # query.select.where([:s, :p, :o])
123
124
  #
124
- # @param [Array<Symbol>] variables
125
+ # @example SELECT ?s WHERE {?s ?p ?o .}
126
+ # query.select(:s).where([:s, :p, :o])
127
+ #
128
+ # @example SELECT COUNT(?uri as ?c) WHERE {?uri a owl:Class}
129
+ # query.select(count: {uri: :c}).where([:uri, RDF.type, RDF::OWL.Class])
130
+ #
131
+ # @param [Array<Symbol>, Hash{Symbol => RDF::Query::Variable}] variables
125
132
  # @return [Query]
126
133
  # @see http://www.w3.org/TR/sparql11-query/#select
127
134
  def select(*variables)
128
- @values = variables.map { |var| [var, RDF::Query::Variable.new(var)] }
135
+ @values = if variables.length == 1 && variables.first.is_a?(Hash)
136
+ variables.to_a
137
+ else
138
+ variables.map { |var| [var, RDF::Query::Variable.new(var)] }
139
+ end
129
140
  self
130
141
  end
131
142
 
@@ -172,19 +183,55 @@ module SPARQL; class Client
172
183
  # query.select.where([:s, :p, :o])
173
184
  # query.select.whether([:s, :p, :o])
174
185
  #
186
+ # @example SELECT * WHERE { { SELECT * WHERE { ?s ?p ?o . } } . ?s ?p ?o . }
187
+ # subquery = query.select.where([:s, :p, :o])
188
+ # query.select.where([:s, :p, :o], subquery)
189
+ #
190
+ # @example SELECT * WHERE { { SELECT * WHERE { ?s ?p ?o . } } . ?s ?p ?o . }
191
+ # query.select.where([:s, :p, :o]) do |q|
192
+ # q.select.where([:s, :p, :o])
193
+ # end
194
+ #
195
+ # Block form can be used for chaining calls in addition to creating sub-select queries.
196
+ #
197
+ # @example SELECT * WHERE { ?s ?p ?o . } ORDER BY ?o
198
+ # query.select.where([:s, :p, :o]) do
199
+ # order(:o)
200
+ # end
201
+ #
175
202
  # @param [Array<RDF::Query::Pattern, Array>] patterns_queries
176
203
  # splat of zero or more patterns followed by zero or more queries.
204
+ # @yield [query]
205
+ # Yield form with or without argument; without an argument, evaluates within the query.
206
+ # @yieldparam [SPARQL::Client::Query] query Actually a delegator to query. Methods other than `#select` are evaluated against `self`. For `#select`, a new Query is created, and the result added as a subquery.
177
207
  # @return [Query]
178
208
  # @see http://www.w3.org/TR/sparql11-query/#GraphPattern
179
- def where(*patterns_queries)
209
+ def where(*patterns_queries, &block)
180
210
  subqueries, patterns = patterns_queries.partition {|pq| pq.is_a? SPARQL::Client::Query}
181
211
  @patterns += build_patterns(patterns)
182
212
  @subqueries += subqueries
213
+
214
+ if block_given?
215
+ decorated_query = WhereDecorator.new(self)
216
+ case block.arity
217
+ when 1 then block.call(decorated_query)
218
+ else decorated_query.instance_eval(&block)
219
+ end
220
+ end
183
221
  self
184
222
  end
185
223
 
186
224
  alias_method :whether, :where
187
225
 
226
+ # @private
227
+ class WhereDecorator < SimpleDelegator
228
+ def select(*variables)
229
+ query = SPARQL::Client::Query.select(*variables)
230
+ __getobj__.instance_variable_get(:@subqueries) << query
231
+ query
232
+ end
233
+ end
234
+
188
235
  ##
189
236
  # @example SELECT * WHERE { ?s ?p ?o . } ORDER BY ?o
190
237
  # query.select.where([:s, :p, :o]).order(:o)
@@ -194,7 +241,7 @@ module SPARQL; class Client
194
241
  # query.select.where([:s, :p, :o]).order_by(:o, :p)
195
242
  #
196
243
  # @example SELECT * WHERE { ?s ?p ?o . } ORDER BY ASC(?o) DESC(?p)
197
- # query.select.where([:s, :p, :o]).order_by(:o => :asc, :p => :desc)
244
+ # query.select.where([:s, :p, :o]).order_by(o: :asc, p: :desc)
198
245
  #
199
246
  # @param [Array<Symbol, String>] variables
200
247
  # @return [Query]
@@ -320,16 +367,38 @@ module SPARQL; class Client
320
367
  end
321
368
 
322
369
  ##
323
- # @example PREFIX dc: <http://purl.org/dc/elements/1.1/> PREFIX foaf: <http://xmlns.com/foaf/0.1/> SELECT * WHERE \{ ?s ?p ?o . \}
324
- # query.select.
325
- # prefix(dc: RDF::URI("http://purl.org/dc/elements/1.1/")).
326
- # prefix(foaf: RDF::URI("http://xmlns.com/foaf/0.1/")).
327
- # where([:s, :p, :o])
370
+ # @overload prefix(prefix: uri)
371
+ # @example PREFIX dc: <http://purl.org/dc/elements/1.1/> PREFIX foaf: <http://xmlns.com/foaf/0.1/> SELECT * WHERE \{ ?s ?p ?o . \}
372
+ # query.select.
373
+ # prefix(dc: RDF::URI("http://purl.org/dc/elements/1.1/")).
374
+ # prefix(foaf: RDF::URI("http://xmlns.com/foaf/0.1/")).
375
+ # where([:s, :p, :o])
328
376
  #
329
- # @return [Query]
377
+ # @param [RDF::URI] uri
378
+ # @param [Symbol, String] prefix
379
+ # @return [Query]
380
+ #
381
+ # @overload prefix(string)
382
+ # @example PREFIX dc: <http://purl.org/dc/elements/1.1/> PREFIX foaf: <http://xmlns.com/foaf/0.1/> SELECT * WHERE \{ ?s ?p ?o . \}
383
+ # query.select.
384
+ # prefix("dc: <http://purl.org/dc/elements/1.1/>").
385
+ # prefix("foaf: <http://xmlns.com/foaf/0.1/>").
386
+ # where([:s, :p, :o])
387
+ #
388
+ # @param [string] string
389
+ # @return [Query]
330
390
  # @see http://www.w3.org/TR/sparql11-query/#prefNames
331
- def prefix(string)
332
- (options[:prefixes] ||= []) << string
391
+ def prefix(val)
392
+ options[:prefixes] ||= []
393
+ if val.kind_of? String
394
+ options[:prefixes] << val
395
+ elsif val.kind_of? Hash
396
+ val.each do |k, v|
397
+ options[:prefixes] << "#{k}: <#{v}>"
398
+ end
399
+ else
400
+ raise ArgumentError, "prefix must be a kind of String or a Hash"
401
+ end
333
402
  self
334
403
  end
335
404
 
@@ -338,10 +407,206 @@ module SPARQL; class Client
338
407
  # query.select.where([:s, :p, :o]).
339
408
  # optional([:s, RDF.type, :o], [:s, RDF::Vocab::DC.abstract, :o])
340
409
  #
410
+ # The block form can be used for adding filters:
411
+ #
412
+ # @example ASK WHERE { ?s ?p ?o . OPTIONAL { ?s ?p ?o . FILTER(regex(?s, 'Abiline, Texas'))} }
413
+ # query.ask.where([:s, :p, :o]).optional([:s, :p, :o]) do
414
+ # filter("regex(?s, 'Abiline, Texas')")
415
+ # end
416
+ #
417
+ # @param [Array<RDF::Query::Pattern, Array>] patterns
418
+ # splat of zero or more patterns followed by zero or more queries.
419
+ # @yield [query]
420
+ # Yield form with or without argument; without an argument, evaluates within the query.
421
+ # @yieldparam [SPARQL::Client::Query] query used for creating filters on the optional patterns.
341
422
  # @return [Query]
342
423
  # @see http://www.w3.org/TR/sparql11-query/#optionals
343
- def optional(*patterns)
424
+ def optional(*patterns, &block)
344
425
  (options[:optionals] ||= []) << build_patterns(patterns)
426
+
427
+ if block_given?
428
+ # Steal options[:filters]
429
+ query_filters = options[:filters]
430
+ options[:filters] = []
431
+ case block.arity
432
+ when 1 then block.call(self)
433
+ else instance_eval(&block)
434
+ end
435
+ options[:optionals].last.concat(options[:filters])
436
+ options[:filters] = query_filters
437
+ end
438
+
439
+ self
440
+ end
441
+
442
+ ##
443
+ # @example SELECT * WHERE \{ ?book dc:title ?title \} UNION \{ ?book dc11:title ?title \}
444
+ # query.select.where([:book, RDF::Vocab::DC.title, :title]).
445
+ # union([:book, RDF::Vocab::DC11.title, :title])
446
+ #
447
+ # @example SELECT * WHERE \{ ?book dc:title ?title \} UNION \{ ?book dc11:title ?title . FILTER(langmatches(lang(?title), 'EN'))\}
448
+ # query1 = SPARQL::Client::Query.select.
449
+ # where([:book, RDF::Vocab::DC11.title, :title]).
450
+ # filter("langmatches(?title, 'en')")
451
+ # query.select.where([:book, RDF::Vocab::DC.title, :title]).union(query1)
452
+ #
453
+ # The block form can be used for more complicated queries, using the `select` form (note, use either block or argument forms, not both):
454
+ #
455
+ # @example SELECT * WHERE \{ ?book dc:title ?title \} UNION \{ ?book dc11:title ?title . FILTER(langmatches(lang(?title), 'EN'))\}
456
+ # query1 = SPARQL::Client::Query.select.where([:book, RDF::Vocab::DC11.title, :title]).filter("langmatches(?title, 'en')")
457
+ # query.select.where([:book, RDF::Vocab::DC.title, :title]).union do |q|
458
+ # q.select.
459
+ # where([:book, RDF::Vocab::DC11.title, :title]).
460
+ # filter("langmatches(?title, 'en')")
461
+ # end
462
+ #
463
+ # @param [Array<RDF::Query::Pattern, Array>] patterns
464
+ # splat of zero or more patterns followed by zero or more queries.
465
+ # @yield [query]
466
+ # Yield form with or without argument; without an argument, evaluates within the query.
467
+ # @yieldparam [SPARQL::Client::Query] query used for adding select clauses.
468
+ # @return [Query]
469
+ # @see http://www.w3.org/TR/sparql11-query/#optionals
470
+ def union(*patterns, &block)
471
+ options[:unions] ||= []
472
+
473
+ if block_given?
474
+ raise ArgumentError, "#union requires either arguments or a block, not both." unless patterns.empty?
475
+ # Evaluate calls in a new query instance
476
+ query = self.class.select
477
+ case block.arity
478
+ when 1 then block.call(query)
479
+ else query.instance_eval(&block)
480
+ end
481
+ options[:unions] << query
482
+ elsif patterns.all? {|p| p.is_a?(SPARQL::Client::Query)}
483
+ # With argument form, all must be patterns or queries
484
+ options[:unions] += patterns
485
+ elsif patterns.all? {|p| p.is_a?(Array)}
486
+ # With argument form, all must be patterns, or queries
487
+ options[:unions] << self.class.select.where(*patterns)
488
+ else
489
+ raise ArgumentError, "#union arguments are triple patters or queries, not both."
490
+ end
491
+
492
+ self
493
+ end
494
+
495
+ ##
496
+ # @example SELECT * WHERE \{ ?book dc:title ?title . MINUS \{ ?book dc11:title ?title \} \}
497
+ # query.select.where([:book, RDF::Vocab::DC.title, :title]).
498
+ # minus([:book, RDF::Vocab::DC11.title, :title])
499
+ #
500
+ # @example SELECT * WHERE \{ ?book dc:title ?title MINUS \{ ?book dc11:title ?title . FILTER(langmatches(lang(?title), 'EN')) \} \}
501
+ # query1 = SPARQL::Client::Query.select.
502
+ # where([:book, RDF::Vocab::DC11.title, :title]).
503
+ # filter("langmatches(?title, 'en')")
504
+ # query.select.where([:book, RDF::Vocab::DC.title, :title]).minus(query1)
505
+ #
506
+ # The block form can be used for more complicated queries, using the `select` form (note, use either block or argument forms, not both):
507
+ #
508
+ # @example SELECT * WHERE \{ ?book dc:title ?title MINUS \{ ?book dc11:title ?title . FILTER(langmatches(lang(?title), 'EN'))\} \}
509
+ # query1 = SPARQL::Client::Query.select.where([:book, RDF::Vocab::DC11.title, :title]).filter("langmatches(?title, 'en')")
510
+ # query.select.where([:book, RDF::Vocab::DC.title, :title]).minus do |q|
511
+ # q.select.
512
+ # where([:book, RDF::Vocab::DC11.title, :title]).
513
+ # filter("langmatches(?title, 'en')")
514
+ # end
515
+ #
516
+ # @param [Array<RDF::Query::Pattern, Array>] patterns
517
+ # splat of zero or more patterns followed by zero or more queries.
518
+ # @yield [query]
519
+ # Yield form with or without argument; without an argument, evaluates within the query.
520
+ # @yieldparam [SPARQL::Client::Query] query used for adding select clauses.
521
+ # @return [Query]
522
+ # @see http://www.w3.org/TR/sparql11-query/#optionals
523
+ def minus(*patterns, &block)
524
+ options[:minuses] ||= []
525
+
526
+ if block_given?
527
+ raise ArgumentError, "#minus requires either arguments or a block, not both." unless patterns.empty?
528
+ # Evaluate calls in a new query instance
529
+ query = self.class.select
530
+ case block.arity
531
+ when 1 then block.call(query)
532
+ else query.instance_eval(&block)
533
+ end
534
+ options[:minuses] << query
535
+ elsif patterns.all? {|p| p.is_a?(SPARQL::Client::Query)}
536
+ # With argument form, all must be patterns or queries
537
+ options[:minuses] += patterns
538
+ elsif patterns.all? {|p| p.is_a?(Array)}
539
+ # With argument form, all must be patterns, or queries
540
+ options[:minuses] << self.class.select.where(*patterns)
541
+ else
542
+ raise ArgumentError, "#minus arguments are triple patters or queries, not both."
543
+ end
544
+
545
+ self
546
+ end
547
+
548
+ ##
549
+ # Specify inline data for a query
550
+ #
551
+ # @overload values
552
+ # Values returned from previous query.
553
+ #
554
+ # @return [Array<Array(key, RDF::Value)>]
555
+ #
556
+ # @overload values(vars, *data)
557
+ # @example single variable with multiple values
558
+ # query.select
559
+ # .where([:s, RDF::URI('http://purl.org/dc/terms/title'), :title])
560
+ # .values(:title, "This title", "Another title")
561
+ #
562
+ # @example multiple variables with multiple values
563
+ # query.select
564
+ # .where([:s, RDF::URI('http://purl.org/dc/terms/title'), :title],
565
+ # [:s, RDF.type, :type])
566
+ # .values([:type, :title],
567
+ # [RDF::URI('http://pcdm.org/models#Object'), "This title"],
568
+ # [RDF::URI('http://pcdm.org/models#Collection', 'Another title'])
569
+ #
570
+ # @example multiple variables with UNDEF
571
+ # query.select
572
+ # .where([:s, RDF::URI('http://purl.org/dc/terms/title'), :title],
573
+ # [:s, RDF.type, :type])
574
+ # .values([:type, :title],
575
+ # [nil "This title"],
576
+ # [RDF::URI('http://pcdm.org/models#Collection', nil])
577
+ #
578
+ # @param [Symbol, Array<Symbol>] vars
579
+ # @param [Array<RDF::Term, String, nil>] *data
580
+ # @return [Query]
581
+ def values(*args)
582
+ return @values if args.empty?
583
+ vars, *data = *args
584
+ vars = Array(vars).map {|var| RDF::Query::Variable.new(var)}
585
+ if vars.length == 1
586
+ # data may be a in array form or simple form
587
+ if data.any? {|d| d.is_a?(Array)} && !data.all? {|d| d.is_a?(Array)}
588
+ raise ArgumentError, "values data must all be in array form or all simple"
589
+ end
590
+ data = data.map {|d| Array(d)}
591
+ end
592
+
593
+ # Each data value must be an array with the same number of entries as vars
594
+ unless data.all? {|d| d.is_a?(Array) && d.all? {|dd| dd.is_a?(RDF::Value) || dd.is_a?(String) || dd.nil?}}
595
+ raise ArgumentError, "values data must each be an array of terms, strings, or nil"
596
+ end
597
+
598
+ # Turn strings into Literals
599
+ data = data.map do |d|
600
+ d.map do |nil_literal_or_term|
601
+ case nil_literal_or_term
602
+ when nil then nil
603
+ when String then RDF::Literal(nil_literal_or_term)
604
+ when RDF::Value then graph_uri_or_var
605
+ else raise ArgumentError
606
+ end
607
+ end
608
+ end
609
+ options[:values] = [vars, *data]
345
610
  self
346
611
  end
347
612
 
@@ -352,15 +617,17 @@ module SPARQL; class Client
352
617
  end
353
618
 
354
619
  ##
355
- # @private
620
+ # @private
356
621
  def build_patterns(patterns)
357
622
  patterns.map {|pattern| RDF::Query::Pattern.from(pattern)}
358
623
  end
359
624
 
360
625
  ##
361
- # @private
626
+ # @example ASK WHERE { ?s ?p ?o . FILTER(regex(?s, 'Abiline, Texas')) }
627
+ # query.ask.where([:s, :p, :o]).filter("regex(?s, 'Abiline, Texas')")
628
+ # @return [Query]
362
629
  def filter(string)
363
- ((options[:filters] ||= []) << string) if string and not string.empty?
630
+ ((options[:filters] ||= []) << Filter.new(string)) if string and not string.empty?
364
631
  self
365
632
  end
366
633
 
@@ -445,33 +712,11 @@ module SPARQL; class Client
445
712
  buffer << "FROM #{SPARQL::Client.serialize_value(options[:from])}" if options[:from]
446
713
 
447
714
  unless patterns.empty? && form == :describe
448
- buffer << 'WHERE {'
449
-
450
- if options[:graph]
451
- buffer << 'GRAPH ' + SPARQL::Client.serialize_value(options[:graph])
452
- buffer << '{'
453
- end
454
-
455
- @subqueries.each do |sq|
456
- buffer << "{ #{sq.to_s} } ."
457
- end
458
-
459
- buffer += SPARQL::Client.serialize_patterns(patterns)
460
- if options[:optionals]
461
- options[:optionals].each do |patterns|
462
- buffer << 'OPTIONAL {'
463
- buffer += SPARQL::Client.serialize_patterns(patterns)
464
- buffer << '}'
465
- end
466
- end
467
- if options[:filters]
468
- buffer += options[:filters].map { |filter| "FILTER(#{filter})" }
469
- end
470
- if options[:graph]
471
- buffer << '}' # GRAPH
472
- end
715
+ buffer += self.to_s_ggp.unshift('WHERE')
716
+ end
473
717
 
474
- buffer << '}' # WHERE
718
+ options.fetch(:unions, []).each do |query|
719
+ buffer += query.to_s_ggp.unshift('UNION')
475
720
  end
476
721
 
477
722
  if options[:group_by]
@@ -483,7 +728,7 @@ module SPARQL; class Client
483
728
  buffer << 'ORDER BY'
484
729
  options[:order_by].map { |elem|
485
730
  case elem
486
- # .order_by({ :var1 => :asc, :var2 => :desc})
731
+ # .order_by({ var1: :asc, var2: :desc})
487
732
  when Hash
488
733
  elem.each { |key, val|
489
734
  # check provided values
@@ -524,6 +769,55 @@ module SPARQL; class Client
524
769
  buffer.join(' ')
525
770
  end
526
771
 
772
+ # Serialize a Group Graph Pattern
773
+ # @private
774
+ def to_s_ggp
775
+ buffer = ["{"]
776
+
777
+ if options[:graph]
778
+ buffer << 'GRAPH ' + SPARQL::Client.serialize_value(options[:graph])
779
+ buffer << '{'
780
+ end
781
+
782
+ @subqueries.each do |sq|
783
+ buffer << "{ #{sq.to_s} } ."
784
+ end
785
+
786
+ buffer += SPARQL::Client.serialize_patterns(patterns)
787
+ if options[:optionals]
788
+ options[:optionals].each do |patterns|
789
+ buffer << 'OPTIONAL {'
790
+ buffer += SPARQL::Client.serialize_patterns(patterns)
791
+ buffer << '}'
792
+ end
793
+ end
794
+ if options[:filters]
795
+ buffer += options[:filters].map(&:to_s)
796
+ end
797
+ if options[:values]
798
+ vars = options[:values].first.map {|var| SPARQL::Client.serialize_value(var)}
799
+ buffer << "VALUES (#{vars.join(' ')}) {"
800
+ options[:values][1..-1].each do |data_block_value|
801
+ buffer << '('
802
+ buffer << data_block_value.map do |value|
803
+ value.nil? ? 'UNDEF' : SPARQL::Client.serialize_value(value)
804
+ end.join(' ')
805
+ buffer << ')'
806
+ end
807
+ buffer << '}'
808
+ end
809
+ if options[:graph]
810
+ buffer << '}' # GRAPH
811
+ end
812
+
813
+ options.fetch(:minuses, []).each do |query|
814
+ buffer += query.to_s_ggp.unshift('MINUS')
815
+ end
816
+
817
+ buffer << '}'
818
+ buffer
819
+ end
820
+
527
821
  ##
528
822
  # Outputs a developer-friendly representation of this query to `stderr`.
529
823
  #
@@ -540,5 +834,16 @@ module SPARQL; class Client
540
834
  def inspect
541
835
  sprintf("#<%s:%#0x(%s)>", self.class.name, __id__, to_s)
542
836
  end
837
+
838
+ # Allow Filters to be
839
+ class Filter < SPARQL::Client::QueryElement
840
+ def initialize(*args)
841
+ super
842
+ end
843
+
844
+ def to_s
845
+ "FILTER(#{elements.join(' ')})"
846
+ end
847
+ end
543
848
  end
544
- end; end
849
+ end