rdf 3.0.11 → 3.1.2

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 (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