rdf 3.0.11 → 3.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/AUTHORS +1 -1
  3. data/README.md +127 -95
  4. data/UNLICENSE +1 -1
  5. data/VERSION +1 -1
  6. data/etc/doap.nt +79 -85
  7. data/lib/rdf.rb +35 -23
  8. data/lib/rdf/changeset.rb +80 -19
  9. data/lib/rdf/cli.rb +7 -7
  10. data/lib/rdf/format.rb +17 -10
  11. data/lib/rdf/mixin/enumerable.rb +4 -3
  12. data/lib/rdf/mixin/mutable.rb +5 -15
  13. data/lib/rdf/mixin/queryable.rb +12 -4
  14. data/lib/rdf/mixin/transactable.rb +2 -2
  15. data/lib/rdf/mixin/writable.rb +9 -14
  16. data/lib/rdf/model/dataset.rb +1 -1
  17. data/lib/rdf/model/graph.rb +7 -4
  18. data/lib/rdf/model/list.rb +5 -5
  19. data/lib/rdf/model/literal.rb +3 -3
  20. data/lib/rdf/model/statement.rb +32 -9
  21. data/lib/rdf/model/uri.rb +53 -32
  22. data/lib/rdf/nquads.rb +6 -6
  23. data/lib/rdf/ntriples.rb +7 -5
  24. data/lib/rdf/ntriples/reader.rb +29 -7
  25. data/lib/rdf/ntriples/writer.rb +10 -1
  26. data/lib/rdf/query.rb +27 -35
  27. data/lib/rdf/query/hash_pattern_normalizer.rb +14 -12
  28. data/lib/rdf/query/pattern.rb +51 -18
  29. data/lib/rdf/query/solution.rb +20 -1
  30. data/lib/rdf/query/solutions.rb +15 -5
  31. data/lib/rdf/query/variable.rb +17 -5
  32. data/lib/rdf/reader.rb +76 -25
  33. data/lib/rdf/repository.rb +32 -18
  34. data/lib/rdf/transaction.rb +1 -1
  35. data/lib/rdf/util.rb +6 -5
  36. data/lib/rdf/util/cache.rb +2 -2
  37. data/lib/rdf/util/coercions.rb +60 -0
  38. data/lib/rdf/util/file.rb +20 -10
  39. data/lib/rdf/util/logger.rb +6 -6
  40. data/lib/rdf/util/uuid.rb +4 -4
  41. data/lib/rdf/vocab/owl.rb +401 -86
  42. data/lib/rdf/vocab/rdfs.rb +81 -18
  43. data/lib/rdf/vocab/rdfv.rb +147 -1
  44. data/lib/rdf/vocab/writer.rb +41 -3
  45. data/lib/rdf/vocab/xsd.rb +203 -2
  46. data/lib/rdf/vocabulary.rb +73 -15
  47. data/lib/rdf/writer.rb +33 -11
  48. metadata +34 -28
@@ -195,12 +195,31 @@ class RDF::Query
195
195
  # Merges the bindings from the given `other` query solution into this
196
196
  # one, overwriting any existing ones having the same name.
197
197
  #
198
+ # ## RDFStar (RDF*)
199
+ #
200
+ # If merging a binding for a statement to a pattern,
201
+ # merge their embedded solutions.
202
+ #
198
203
  # @param [RDF::Query::Solution, #to_h] other
199
204
  # another query solution or hash bindings
200
205
  # @return [void] self
201
206
  # @since 0.3.0
202
207
  def merge!(other)
203
- @bindings.merge!(other.to_h)
208
+ @bindings.merge!(other.to_h) do |key, v1, v2|
209
+ # Don't merge a pattern over a statement
210
+ # This happens because JOIN does a reverse merge,
211
+ # and a pattern is set in v2.
212
+ v2.is_a?(Pattern) ? v1 : v2
213
+ end
214
+ # Merge bindings from patterns
215
+ embedded_solutions = []
216
+ @bindings.each do |k, v|
217
+ if v.is_a?(Pattern) && other[k].is_a?(RDF::Statement)
218
+ embedded_solutions << v.solution(other[k])
219
+ end
220
+ end
221
+ # Merge embedded solutions
222
+ embedded_solutions.each {|soln| merge!(soln)}
204
223
  self
205
224
  end
206
225
 
@@ -66,13 +66,15 @@ module RDF; class Query
66
66
  #
67
67
  # @return [Array<Symbol>]
68
68
  def variable_names
69
- variables = self.inject({}) do |result, solution|
70
- solution.each_name do |name|
71
- result[name] ||= true
69
+ @variable_names ||= begin
70
+ variables = self.inject({}) do |result, solution|
71
+ solution.each_name do |name|
72
+ result[name] ||= true
73
+ end
74
+ result
72
75
  end
73
- result
76
+ variables.keys
74
77
  end
75
- variables.keys
76
78
  end
77
79
 
78
80
  ##
@@ -136,6 +138,7 @@ module RDF; class Query
136
138
  # @yieldreturn [Boolean]
137
139
  # @return [self]
138
140
  def filter(criteria = {})
141
+ @variable_names = nil
139
142
  if block_given?
140
143
  self.reject! do |solution|
141
144
  !yield(solution.is_a?(Solution) ? solution : Solution.new(solution))
@@ -223,6 +226,13 @@ module RDF; class Query
223
226
  solution.bindings.delete_if { |k, v| !variables.include?(k.to_sym) }
224
227
  end
225
228
  end
229
+
230
+ # Make sure variable_names are ordered by projected variables
231
+ projected_vars, vars = variables.map(&:to_sym), variable_names
232
+ vars = variable_names
233
+
234
+ # Maintain projected order, and add any non-projected variables
235
+ @variable_names = (projected_vars & vars) + (vars - projected_vars)
226
236
  self
227
237
  end
228
238
  alias_method :select, :project
@@ -157,12 +157,24 @@ class RDF::Query
157
157
  ##
158
158
  # Rebinds this variable to the given `value`.
159
159
  #
160
- # @param [RDF::Term] value
161
- # @return [RDF::Term] the previous value, if any.
160
+ # @overload bind(value)
161
+ # @param [RDF::Query::Solution] value
162
+ # @return [self] the bound variable
163
+ #
164
+ # @overload bind(value)
165
+ # @param [RDF::Term] value
166
+ # @return [RDF::Term] the previous value, if any.
162
167
  def bind(value)
163
- old_value = self.value
164
- self.value = value
165
- old_value
168
+ if value.is_a?(RDF::Query::Solution)
169
+ self.value = value.to_h.fetch(name, self.value)
170
+ self
171
+ else
172
+ warn "[DEPRECATION] RDF::Query::Variable#bind should be used with a solution, not a term.\n" +
173
+ "Called from #{Gem.location_of_caller.join(':')}"
174
+ old_value = self.value
175
+ self.value = value
176
+ old_value
177
+ end
166
178
  end
167
179
  alias_method :bind!, :bind
168
180
 
@@ -88,9 +88,15 @@ module RDF
88
88
  # @yieldreturn [String] another way to provide a sample, allows lazy for retrieving the sample.
89
89
  #
90
90
  # @return [Class]
91
- def self.for(options = {}, &block)
92
- options = options.merge(has_reader: true) if options.is_a?(Hash)
93
- if format = self.format || Format.for(options, &block)
91
+ def self.for(*arg, &block)
92
+ case arg.length
93
+ when 0 then arg = nil
94
+ when 1 then arg = arg.first
95
+ else
96
+ raise ArgumentError, "Format.for accepts zero or one argument, got #{arg.length}."
97
+ end
98
+ arg = arg.merge(has_reader: true) if arg.is_a?(Hash)
99
+ if format = self.format || Format.for(arg, &block)
94
100
  format.reader
95
101
  end
96
102
  end
@@ -115,6 +121,12 @@ module RDF
115
121
  # @return [Array<RDF::CLI::Option>]
116
122
  def self.options
117
123
  [
124
+ RDF::CLI::Option.new(
125
+ symbol: :base_uri,
126
+ control: :url,
127
+ datatype: RDF::URI,
128
+ on: ["--uri URI"],
129
+ description: "Base URI of input file, defaults to the filename.") {|arg| RDF::URI(arg)},
118
130
  RDF::CLI::Option.new(
119
131
  symbol: :canonicalize,
120
132
  datatype: TrueClass,
@@ -147,11 +159,11 @@ module RDF
147
159
  end
148
160
  end,
149
161
  RDF::CLI::Option.new(
150
- symbol: :base_uri,
151
- control: :url,
152
- datatype: RDF::URI,
153
- on: ["--uri URI"],
154
- description: "Base URI of input file, defaults to the filename.") {|arg| RDF::URI(arg)},
162
+ symbol: :rdfstar,
163
+ control: :select,
164
+ datatype: [:PG, :SA],
165
+ on: ["--rdf-star MODE"],
166
+ description: "Parse RDF*, either in Property Graph mode (PG) or Separate Assertions mode (SA).") {|arg| arg.to_sym},
155
167
  RDF::CLI::Option.new(
156
168
  symbol: :validate,
157
169
  datatype: TrueClass,
@@ -206,7 +218,7 @@ module RDF
206
218
  headers['Accept'] ||= (self.format.accept_type + %w(*/*;q=0.1)).join(", ")
207
219
  end
208
220
 
209
- Util::File.open_file(filename, options) do |file|
221
+ Util::File.open_file(filename, **options) do |file|
210
222
  format_options = options.dup
211
223
  format_options[:content_type] ||= file.content_type if
212
224
  file.respond_to?(:content_type) &&
@@ -229,7 +241,7 @@ module RDF
229
241
  options[:filename] ||= filename
230
242
 
231
243
  if reader
232
- reader.new(file, options, &block)
244
+ reader.new(file, **options, &block)
233
245
  else
234
246
  raise FormatError, "unknown RDF format: #{format_options.inspect}#{"\nThis may be resolved with a require of the 'linkeddata' gem." unless Object.const_defined?(:LinkedData)}"
235
247
  end
@@ -255,42 +267,48 @@ module RDF
255
267
  #
256
268
  # @param [IO, File, String] input
257
269
  # the input stream to read
258
- # @param [Encoding] encoding (Encoding::UTF_8)
259
- # the encoding of the input stream
260
- # @param [Boolean] validate (false)
261
- # whether to validate the parsed statements and values
270
+ # @param [#to_s] base_uri (nil)
271
+ # the base URI to use when resolving relative URIs (not supported by
272
+ # all readers)
262
273
  # @param [Boolean] canonicalize (false)
263
274
  # whether to canonicalize parsed literals
275
+ # @param [Encoding] encoding (Encoding::UTF_8)
276
+ # the encoding of the input stream
264
277
  # @param [Boolean] intern (true)
265
278
  # whether to intern all parsed URIs
279
+ # @param [:PG, :SA] rdfstar (nil)
280
+ # support parsing RDF* statement resources.
281
+ # If `:PG`, referenced statements are also emitted.
282
+ # If `:SA`, referenced statements are not emitted.
266
283
  # @param [Hash] prefixes (Hash.new)
267
284
  # the prefix mappings to use (not supported by all readers)
268
- # @param [#to_s] base_uri (nil)
269
- # the base URI to use when resolving relative URIs (not supported by
270
- # all readers)
271
285
  # @param [Hash{Symbol => Object}] options
272
286
  # any additional options
287
+ # @param [Boolean] validate (false)
288
+ # whether to validate the parsed statements and values
273
289
  # @yield [reader] `self`
274
290
  # @yieldparam [RDF::Reader] reader
275
291
  # @yieldreturn [void] ignored
276
292
  def initialize(input = $stdin,
277
- encoding: Encoding::UTF_8,
278
- validate: false,
293
+ base_uri: nil,
279
294
  canonicalize: false,
295
+ encoding: Encoding::UTF_8,
280
296
  intern: true,
281
297
  prefixes: Hash.new,
282
- base_uri: nil,
298
+ rdfstar: nil,
299
+ validate: false,
283
300
  **options,
284
301
  &block)
285
302
 
286
303
  base_uri ||= input.base_uri if input.respond_to?(:base_uri)
287
304
  @options = options.merge({
288
- encoding: encoding,
289
- validate: validate,
305
+ base_uri: base_uri,
290
306
  canonicalize: canonicalize,
307
+ encoding: encoding,
291
308
  intern: intern,
292
309
  prefixes: prefixes,
293
- base_uri: base_uri
310
+ rdfstar: rdfstar,
311
+ validate: validate
294
312
  })
295
313
 
296
314
  @input = case input
@@ -383,6 +401,9 @@ module RDF
383
401
  # Statements are yielded in the order that they are read from the input
384
402
  # stream.
385
403
  #
404
+ # If the `rdfstar` option is `:PG` and triples include
405
+ # embedded statements, they are also enumerated.
406
+ #
386
407
  # @overload each_statement
387
408
  # @yield [statement]
388
409
  # each statement
@@ -399,7 +420,11 @@ module RDF
399
420
  def each_statement(&block)
400
421
  if block_given?
401
422
  begin
402
- loop { block.call(read_statement) }
423
+ loop do
424
+ st = read_statement
425
+ block.call(st)
426
+ each_pg_statement(st, &block) if options[:rdfstar] == :PG
427
+ end
403
428
  rescue EOFError
404
429
  rewind rescue nil
405
430
  end
@@ -416,6 +441,9 @@ module RDF
416
441
  # Triples are yielded in the order that they are read from the input
417
442
  # stream.
418
443
  #
444
+ # If the `rdfstar` option is `:PG` and triples include
445
+ # embedded statements, they are also enumerated.
446
+ #
419
447
  # @overload each_triple
420
448
  # @yield [subject, predicate, object]
421
449
  # each triple
@@ -433,7 +461,14 @@ module RDF
433
461
  def each_triple(&block)
434
462
  if block_given?
435
463
  begin
436
- loop { block.call(*read_triple) }
464
+ loop do
465
+ triple = read_triple
466
+ block.call(*triple)
467
+ if options[:rdfstar] == :PG
468
+ block.call(*triple[0].to_a) if triple[0].is_a?(Statement)
469
+ block.call(*triple[2].to_a) if triple[2].is_a?(Statement)
470
+ end
471
+ end
437
472
  rescue EOFError
438
473
  rewind rescue nil
439
474
  end
@@ -544,6 +579,22 @@ module RDF
544
579
  log_error("Expected object (found: #{current_line.inspect})", lineno: lineno, exception: RDF::ReaderError)
545
580
  end
546
581
 
582
+ ##
583
+ # Recursively emit embedded statements in Property Graph mode
584
+ #
585
+ # @param [RDF::Statement] statement
586
+ def each_pg_statement(statement, &block)
587
+ if statement.subject.is_a?(Statement)
588
+ block.call(statement.subject)
589
+ each_pg_statement(statement.subject, &block)
590
+ end
591
+
592
+ if statement.object.is_a?(Statement)
593
+ block.call(statement.object)
594
+ each_pg_statement(statement.object, &block)
595
+ end
596
+ end
597
+
547
598
  public
548
599
  ##
549
600
  # Returns the encoding of the input stream.
@@ -119,9 +119,9 @@ module RDF
119
119
  # @yieldparam [Repository]
120
120
  # @return [void]
121
121
  def self.load(urls, **options, &block)
122
- self.new(options) do |repository|
122
+ self.new(**options) do |repository|
123
123
  Array(urls).each do |url|
124
- repository.load(url, options)
124
+ repository.load(url, **options)
125
125
  end
126
126
 
127
127
  if block_given?
@@ -182,6 +182,7 @@ module RDF
182
182
  when :validity then @options.fetch(:with_validity, true)
183
183
  when :literal_equality then true
184
184
  when :atomic_write then false
185
+ when :rdfstar then false
185
186
  when :snapshots then false
186
187
  else false
187
188
  end
@@ -266,6 +267,7 @@ module RDF
266
267
  when :validity then @options.fetch(:with_validity, true)
267
268
  when :literal_equality then true
268
269
  when :atomic_write then true
270
+ when :rdfstar then true
269
271
  when :snapshots then true
270
272
  else false
271
273
  end
@@ -340,7 +342,14 @@ module RDF
340
342
  # @see Mutable#apply_changeset
341
343
  def apply_changeset(changeset)
342
344
  data = @data
343
- changeset.deletes.each { |del| data = delete_from(data, del) }
345
+ changeset.deletes.each do |del|
346
+ if del.constant?
347
+ data = delete_from(data, del)
348
+ else
349
+ # we need this condition to handle wildcard statements
350
+ query_pattern(del) { |stmt| data = delete_from(data, stmt) }
351
+ end
352
+ end
344
353
  changeset.inserts.each { |ins| data = insert_to(data, ins) }
345
354
  @data = data
346
355
  end
@@ -388,20 +397,25 @@ module RDF
388
397
  graph_name.eql?(c)
389
398
 
390
399
  ss = if subject.nil? || subject.is_a?(RDF::Query::Variable)
391
- ss
392
- elsif ss.has_key?(subject)
393
- { subject => ss[subject] }
394
- else
395
- []
396
- end
400
+ ss
401
+ elsif subject.is_a?(RDF::Query::Pattern)
402
+ # Match subjects which are statements matching this pattern
403
+ ss.keys.select {|s| s.statement? && subject.eql?(s)}.inject({}) do |memo, st|
404
+ memo.merge(st => ss[st])
405
+ end
406
+ elsif ss.has_key?(subject)
407
+ { subject => ss[subject] }
408
+ else
409
+ []
410
+ end
397
411
  ss.each do |s, ps|
398
412
  ps = if predicate.nil? || predicate.is_a?(RDF::Query::Variable)
399
- ps
400
- elsif ps.has_key?(predicate)
401
- { predicate => ps[predicate] }
402
- else
403
- []
404
- end
413
+ ps
414
+ elsif ps.has_key?(predicate)
415
+ { predicate => ps[predicate] }
416
+ else
417
+ []
418
+ end
405
419
  ps.each do |p, os|
406
420
  os.each do |o, object_options|
407
421
  next unless object.nil? || object.eql?(o)
@@ -411,7 +425,7 @@ module RDF
411
425
  end
412
426
  end
413
427
  else
414
- enum_for(:query_pattern, pattern, options)
428
+ enum_for(:query_pattern, pattern, **options)
415
429
  end
416
430
  end
417
431
 
@@ -512,8 +526,8 @@ module RDF
512
526
  class SerializedTransaction < Transaction
513
527
  ##
514
528
  # @see Transaction#initialize
515
- def initialize(*)
516
- super
529
+ def initialize(*args, **options, &block)
530
+ super(*args, **options, &block)
517
531
  @base_snapshot = @snapshot
518
532
  end
519
533
 
@@ -24,7 +24,7 @@ module RDF
24
24
  # repository = RDF::Repository.new
25
25
  #
26
26
  # RDF::Transaction.begin(repository) do |tx|
27
- # tx.query(predicate: RDF::Vocab::DOAP.developer) do |statement|
27
+ # tx.query({predicate: RDF::Vocab::DOAP.developer}) do |statement|
28
28
  # puts statement.inspect
29
29
  # end
30
30
  # end
@@ -1,7 +1,8 @@
1
1
  module RDF; module Util
2
- autoload :Aliasing, 'rdf/util/aliasing'
3
- autoload :Cache, 'rdf/util/cache'
4
- autoload :File, 'rdf/util/file'
5
- autoload :Logger, 'rdf/util/logger'
6
- autoload :UUID, 'rdf/util/uuid'
2
+ autoload :Aliasing, 'rdf/util/aliasing'
3
+ autoload :Cache, 'rdf/util/cache'
4
+ autoload :File, 'rdf/util/file'
5
+ autoload :Logger, 'rdf/util/logger'
6
+ autoload :UUID, 'rdf/util/uuid'
7
+ autoload :Coercions, 'rdf/util/coercions'
7
8
  end; end
@@ -9,8 +9,8 @@ module RDF; module Util
9
9
  #
10
10
  # While this cache is something of an internal implementation detail of
11
11
  # RDF.rb, some external libraries do currently make use of it as well,
12
- # including [SPARQL](http://github.com/ruby-rdf/sparql/) and
13
- # [Spira](http://github.com/ruby-rdf/spira). Do be sure to include any changes
12
+ # including [SPARQL](https://github.com/ruby-rdf/sparql/) and
13
+ # [Spira](https://github.com/ruby-rdf/spira). Do be sure to include any changes
14
14
  # here in the RDF.rb changelog.
15
15
  #
16
16
  # @see RDF::URI.intern
@@ -0,0 +1,60 @@
1
+ # -*- coding: utf-8 -*-
2
+ module RDF
3
+ module Util
4
+ module Coercions
5
+ # This is a provisional module intended to house input
6
+ # coercions. Currently the only coercion is a statement
7
+ # preprocessor that is used in e.g. {RDF::Writable#insert} and
8
+ # {RDF::Mutable#delete}.
9
+
10
+ protected
11
+
12
+ ##
13
+ # Coerce an array of arguments into {RDF::Statement}, or
14
+ # {RDF::Enumerable} and then yield to a block. Note that this
15
+ # code was amalgamated from that which was sandwiched around
16
+ # both {RDF::Writable#insert_statements} and
17
+ # {RDF::Mutable#delete_statements}. The parameters `query` and
18
+ # `constant` are therefore present to handle the conditions
19
+ # where the statements contain wildcards and what to do about
20
+ # them.
21
+ #
22
+ # @example
23
+ # coerce_statements(statements) { |value| do_something(value) }
24
+ #
25
+ # @param statements [#map] The arbitrary-ish input to be manipulated
26
+ # @param query [false, true] Whether to call `query` before the block
27
+ # (as expected by {Mutable#delete_statements})
28
+ # @param constant [false, true] Whether to test if the statements
29
+ # are constant (as expected by {Mutable#delete_statements})
30
+ # @yield [RDF::Statement, RDF::Enumerable]
31
+ # @return statements
32
+ def coerce_statements(statements, query: false, constant: false, &block)
33
+ raise ArgumentError, 'expecting a block' unless block_given?
34
+
35
+ statements = statements.map do |value|
36
+ case
37
+ when value.respond_to?(:each_statement)
38
+ block.call(value)
39
+ nil
40
+ when (statement = Statement.from(value)) &&
41
+ (!constant || statement.constant?)
42
+ statement
43
+ when query
44
+ # XXX note that this only makes sense when the module is include()d
45
+ block.call(self.query(value))
46
+ nil
47
+ else
48
+ raise ArgumentError, "Not a valid statement: #{value.inspect}"
49
+ end
50
+ end.compact
51
+
52
+ block.call(statements) unless statements.empty?
53
+
54
+ # eh might as well return these
55
+ statements
56
+ end
57
+
58
+ end
59
+ end
60
+ end