rdf 3.0.12 → 3.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/AUTHORS +1 -1
  3. data/README.md +46 -11
  4. data/VERSION +1 -1
  5. data/etc/doap.nt +74 -80
  6. data/lib/rdf.rb +35 -23
  7. data/lib/rdf/changeset.rb +87 -19
  8. data/lib/rdf/cli.rb +7 -7
  9. data/lib/rdf/format.rb +17 -10
  10. data/lib/rdf/mixin/enumerable.rb +3 -2
  11. data/lib/rdf/mixin/mutable.rb +5 -15
  12. data/lib/rdf/mixin/queryable.rb +12 -4
  13. data/lib/rdf/mixin/writable.rb +9 -14
  14. data/lib/rdf/model/dataset.rb +1 -1
  15. data/lib/rdf/model/graph.rb +5 -2
  16. data/lib/rdf/model/list.rb +5 -5
  17. data/lib/rdf/model/literal.rb +3 -3
  18. data/lib/rdf/model/statement.rb +30 -7
  19. data/lib/rdf/model/uri.rb +44 -23
  20. data/lib/rdf/nquads.rb +6 -6
  21. data/lib/rdf/ntriples.rb +6 -4
  22. data/lib/rdf/ntriples/reader.rb +29 -7
  23. data/lib/rdf/ntriples/writer.rb +10 -1
  24. data/lib/rdf/query.rb +27 -35
  25. data/lib/rdf/query/hash_pattern_normalizer.rb +14 -12
  26. data/lib/rdf/query/pattern.rb +51 -18
  27. data/lib/rdf/query/solution.rb +20 -1
  28. data/lib/rdf/query/solutions.rb +15 -5
  29. data/lib/rdf/query/variable.rb +17 -5
  30. data/lib/rdf/reader.rb +76 -25
  31. data/lib/rdf/repository.rb +32 -18
  32. data/lib/rdf/transaction.rb +1 -1
  33. data/lib/rdf/util.rb +6 -5
  34. data/lib/rdf/util/cache.rb +2 -2
  35. data/lib/rdf/util/coercions.rb +60 -0
  36. data/lib/rdf/util/file.rb +20 -10
  37. data/lib/rdf/util/logger.rb +6 -6
  38. data/lib/rdf/vocab/owl.rb +401 -86
  39. data/lib/rdf/vocab/rdfs.rb +81 -18
  40. data/lib/rdf/vocab/rdfv.rb +147 -1
  41. data/lib/rdf/vocab/writer.rb +41 -3
  42. data/lib/rdf/vocab/xsd.rb +203 -2
  43. data/lib/rdf/vocabulary.rb +108 -14
  44. data/lib/rdf/writer.rb +32 -10
  45. metadata +32 -27
@@ -77,21 +77,23 @@ module RDF; class Query
77
77
  ##
78
78
  # Returns the normalization of the specified `hash_pattern`.
79
79
  #
80
- # @param [Hash{Symbol => Object}] hash_pattern (Hash.new)
81
- # the query pattern as a hash.
82
- # @param [Hash{Symbol => Object}] options (Hash.new)
83
- # any additional normalization options.
84
- # @option options [String] :anonymous_subject_format ("__%s__")
85
- # the string format for anonymous subjects.
86
- # @return [Hash{Symbol => Object}]
87
- # the resulting query pattern as a normalized hash.
88
- def normalize!(hash_pattern = {}, options = {})
80
+ # @overload normalize!(hash_pattern, **options)
81
+ # @param [Hash{Symbol => Object}] hash_pattern (Hash.new)
82
+ # the query pattern as a hash.
83
+ # @param [Hash{Symbol => Object}] **options
84
+ # any additional normalization options.
85
+ # @option options [String] :anonymous_subject_format ("__%s__")
86
+ # the string format for anonymous subjects.
87
+ # @return [Hash{Symbol => Object}]
88
+ # the resulting query pattern as a normalized hash.
89
+ def normalize!(*args)
90
+ hash_pattern = args.shift
91
+ options = args.shift || {}
92
+ anonymous_subject_format = options.fetch(:anonymous_subject_format, '__%s__')
89
93
  raise ArgumentError, "invalid hash pattern: #{hash_pattern.inspect}" unless hash_pattern.is_a?(Hash)
90
94
 
91
95
  counter = RDF::Query::HashPatternNormalizer::Counter.new
92
96
 
93
- anonymous_subject_format = (options[:anonymous_subject_format] || '__%s__').to_s
94
-
95
97
  hash_pattern.inject({}) { |acc, pair|
96
98
  subject, predicate_to_object = pair
97
99
 
@@ -184,7 +186,7 @@ module RDF; class Query
184
186
  # the query pattern as a hash.
185
187
  # @return [Hash{Symbol => Object}]
186
188
  # the resulting query pattern as a normalized hash.
187
- def normalize!(**hash_pattern)
189
+ def normalize!(hash_pattern)
188
190
  self.class.normalize!(hash_pattern, @options)
189
191
  end
190
192
  end # RDF::Query::HashPatternNormalizer
@@ -17,7 +17,7 @@ module RDF; class Query
17
17
  end
18
18
 
19
19
  ##
20
- # @overload initialize(**options)
20
+ # @overload initialize(options = {})
21
21
  # @param [Hash{Symbol => Object}] options
22
22
  # @option options [Variable, Resource, Symbol, nil] :subject (nil)
23
23
  # @option options [Variable, URI, Symbol, nil] :predicate (nil)
@@ -26,7 +26,7 @@ module RDF; class Query
26
26
  # A graph_name of nil matches any graph, a graph_name of false, matches only the default graph.
27
27
  # @option options [Boolean] :optional (false)
28
28
  #
29
- # @overload initialize(subject, predicate, object, **options)
29
+ # @overload initialize(subject, predicate, object, options = {})
30
30
  # @param [Variable, Resource, Symbol, nil] subject
31
31
  # @param [Variable, URI, Symbol, nil] predicate
32
32
  # @param [Variable, Termm, Symbol, nil] object
@@ -50,10 +50,12 @@ module RDF; class Query
50
50
 
51
51
  # Estmate cost positionally, with variables being least expensive as objects, then predicates, then subjects, then graph_names.
52
52
  # XXX does not consider bound variables, which would need to be dynamically calculated.
53
- @cost = (@object.nil? || @object.is_a?(Variable) ? 1 : 0) +
54
- (@predicate.nil? || @predicate.is_a?(Variable) ? 2 : 0) +
55
- (@subject.nil? || @subject.is_a?(Variable) ? 4 : 0) +
56
- (@graph_name.is_a?(Variable) ? 8 : 0)
53
+ @cost = (@object.nil? || @object.is_a?(Variable) ? 8 : 0) +
54
+ (@predicate.nil? || @predicate.is_a?(Variable) ? 4 : 0) +
55
+ (@subject.nil? || @subject.is_a?(Variable) ? 2 : 0) +
56
+ (@graph_name.is_a?(Variable) ? 1 : 0) +
57
+ (@object.is_a?(Pattern) ? (@object.cost * 4) : 0) +
58
+ (@subject.is_a?(Pattern) ? (@subject.cost * 2) : 0)
57
59
  super
58
60
  end
59
61
 
@@ -84,10 +86,10 @@ module RDF; class Query
84
86
  # @return [Boolean] `true` or `false`
85
87
  # @since 0.3.0
86
88
  def has_variables?
87
- subject.is_a?(Variable) ||
88
- predicate.is_a?(Variable) ||
89
- object.is_a?(Variable) ||
90
- graph_name.is_a?(Variable)
89
+ subject && subject.variable? ||
90
+ predicate && predicate.variable? ||
91
+ object && object.variable? ||
92
+ graph_name && graph_name.variable?
91
93
  end
92
94
  alias_method :variables?, :has_variables?
93
95
 
@@ -117,13 +119,33 @@ module RDF; class Query
117
119
  false
118
120
  end
119
121
 
122
+ ##
123
+ # Checks pattern equality against a statement, considering nesting.
124
+ #
125
+ # * A pattern which has a pattern as a subject or an object, matches
126
+ # a statement having a statement as a subject or an object using {#eql?}.
127
+ #
128
+ # @param [Statement] other
129
+ # @return [Boolean]
130
+ #
131
+ # @see RDF::URI#==
132
+ # @see RDF::Node#==
133
+ # @see RDF::Literal#==
134
+ # @see RDF::Query::Variable#==
135
+ def eql?(other)
136
+ return false unless other.is_a?(Statement) && (self.graph_name || false) == (other.graph_name || false)
137
+
138
+ predicate == other.predicate &&
139
+ (subject.is_a?(Pattern) ? subject.eql?(other.subject) : subject == other.subject) &&
140
+ (object.is_a?(Pattern) ? object.eql?(other.object) : object == other.object)
141
+ end
142
+
120
143
  ##
121
144
  # Executes this query pattern on the given `queryable` object.
122
145
  #
123
146
  # Values are matched using using Queryable#query_pattern.
124
147
  #
125
- # If the optional `bindings` are given, variables will be substituted with their values
126
- # when executing the query.
148
+ # If the optional `bindings` are given, variables will be substituted with their values when executing the query.
127
149
  #
128
150
  # To match triples only in the default graph, set graph_name to `false`.
129
151
  #
@@ -198,6 +220,8 @@ module RDF; class Query
198
220
  solution[predicate.to_sym] = statement.predicate if predicate.is_a?(Variable)
199
221
  solution[object.to_sym] = statement.object if object.is_a?(Variable)
200
222
  solution[graph_name.to_sym] = statement.graph_name if graph_name.is_a?(Variable)
223
+ solution.merge!(subject.solution(statement.subject)) if subject.is_a?(Pattern)
224
+ solution.merge!(object.solution(statement.object)) if object.is_a?(Pattern)
201
225
  end
202
226
  end
203
227
 
@@ -229,7 +253,8 @@ module RDF; class Query
229
253
  # @return [Integer] (0..3)
230
254
  def variable_count
231
255
  [subject, predicate, object, graph_name].inject(0) do |memo, term|
232
- memo += (term.is_a?(Variable) ? 1 : 0)
256
+ memo += (term.is_a?(Variable) ? 1 :
257
+ (term.is_a?(Pattern) ? term.variable_count : 0))
233
258
  end
234
259
  end
235
260
  alias_method :cardinality, :variable_count
@@ -243,7 +268,7 @@ module RDF; class Query
243
268
  # @return [Hash{Symbol => Variable}]
244
269
  def variables
245
270
  [subject, predicate, object, graph_name].inject({}) do |memo, term|
246
- term.is_a?(Variable) ? memo.merge(term.variables) : memo
271
+ term && term.variable? ? memo.merge(term.variables) : memo
247
272
  end
248
273
  end
249
274
 
@@ -254,8 +279,10 @@ module RDF; class Query
254
279
  # @return [self]
255
280
  def bind(solution)
256
281
  self.to_quad.each_with_index do |term, index|
257
- if term && term.variable? && solution[term]
282
+ if term.is_a?(Variable) && solution[term]
258
283
  self[index] = solution[term]
284
+ elsif term.is_a?(Pattern)
285
+ term.bind(solution)
259
286
  end
260
287
  end
261
288
  self
@@ -283,9 +310,9 @@ module RDF; class Query
283
310
  # @return [Hash{Symbol => RDF::Term}]
284
311
  def bindings
285
312
  bindings = {}
286
- bindings.merge!(subject.bindings) if subject.is_a?(Variable)
313
+ bindings.merge!(subject.bindings) if subject && subject.variable?
287
314
  bindings.merge!(predicate.bindings) if predicate.is_a?(Variable)
288
- bindings.merge!(object.bindings) if object.is_a?(Variable)
315
+ bindings.merge!(object.bindings) if object && object.variable?
289
316
  bindings.merge!(graph_name.bindings) if graph_name.is_a?(Variable)
290
317
  bindings
291
318
  end
@@ -330,7 +357,13 @@ module RDF; class Query
330
357
  StringIO.open do |buffer| # FIXME in RDF::Statement
331
358
  buffer << 'OPTIONAL ' if optional?
332
359
  buffer << [subject, predicate, object].map do |r|
333
- r.is_a?(RDF::Query::Variable) ? r.to_s : RDF::NTriples.serialize(r)
360
+ if r.is_a?(RDF::Query::Variable)
361
+ r.to_s
362
+ elsif r.is_a?(RDF::Query::Pattern)
363
+ "<<#{r.to_s[0..-3]}>>"
364
+ else
365
+ RDF::NTriples.serialize(r)
366
+ end
334
367
  end.join(" ")
335
368
  buffer << case graph_name
336
369
  when nil, false then " ."
@@ -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.