rdf 3.1.0 → 3.1.5

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.
@@ -8,7 +8,7 @@ begin
8
8
  require 'linkeddata'
9
9
  rescue LoadError
10
10
  # Silently load without linkeddata, but try some others
11
- %w(reasoner rdfa rdfxml turtle vocab json/ld ld/patch).each do |ser|
11
+ %w(microdata n3 rdfa rdfxml reasoner tabular trig trix turtle vocab xsd json/ld ld/patch).each do |ser|
12
12
  begin
13
13
  require ser.include?('/') ? ser : "rdf/#{ser}"
14
14
  rescue LoadError
@@ -296,6 +296,12 @@ module RDF
296
296
  control: :none,
297
297
  on: ["-o", "--output FILE"],
298
298
  description: "File to write output, defaults to STDOUT") {|arg| File.open(arg, "w")},
299
+ RDF::CLI::Option.new(
300
+ symbol: :ordered,
301
+ control: :checkbox,
302
+ datatype: TrueClass,
303
+ on: ["--ordered"],
304
+ description: "Use order preserving repository"),
299
305
  RDF::CLI::Option.new(
300
306
  symbol: :format,
301
307
  control: :select,
@@ -495,14 +501,16 @@ module RDF
495
501
  options[:format] = options[:format].to_sym if options[:format]
496
502
  options[:output_format] = options[:output_format].to_sym if options[:output_format]
497
503
 
498
- @repository = RDF::Repository.new
504
+ @repository = options[:ordered] ?
505
+ [].extend(RDF::Enumerable, RDF::Queryable) :
506
+ RDF::Repository.new
499
507
 
500
508
  # Parse input files if any command requires it
501
509
  if cmds.any? {|c| COMMANDS[c.to_sym][:parse]}
502
510
  start = Time.new
503
511
  count = 0
504
512
  self.parse(args, **options) do |reader|
505
- @repository << reader
513
+ reader.each_statement {|st| @repository << st}
506
514
  end
507
515
  secs = Time.new - start
508
516
  options[:logger].info "Parsed #{repository.count} statements with #{@readers.join(', ')} in #{secs} seconds @ #{count/secs} statements/second."
@@ -71,6 +71,7 @@ module RDF
71
71
  # * `:literal_equality' preserves [term-equality](https://www.w3.org/TR/rdf11-concepts/#dfn-literal-term-equality) for literals. Literals are equal only if their lexical values and datatypes are equal, character by character. Literals may be "inlined" to value-space for efficiency only if `:literal_equality` is `false`.
72
72
  # * `:validity` 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.
73
73
  # * `:skolemize` supports [Skolemization](https://www.w3.org/wiki/BnodeSkolemization) of an `Enumerable`. Implementations supporting this feature must implement a `#skolemize` method, taking a base URI used for minting URIs for BNodes as stable identifiers and a `#deskolemize` method, also taking a base URI used for turning URIs having that prefix back into the same BNodes which were originally skolemized.
74
+ # * `:rdfstar` supports RDF* where statements may be subjects or objects of other statements.
74
75
  #
75
76
  # @param [Symbol, #to_sym] feature
76
77
  # @return [Boolean]
@@ -9,6 +9,7 @@ module RDF
9
9
  extend RDF::Util::Aliasing::LateBound
10
10
  include RDF::Readable
11
11
  include RDF::Writable
12
+ include RDF::Util::Coercions
12
13
 
13
14
  ##
14
15
  # Returns `true` if `self` is mutable.
@@ -154,20 +155,9 @@ module RDF
154
155
  def delete(*statements)
155
156
  raise TypeError.new("#{self} is immutable") if immutable?
156
157
 
157
- statements.map! do |value|
158
- case
159
- when value.respond_to?(:each_statement)
160
- delete_statements(value)
161
- nil
162
- when (statement = Statement.from(value)).constant?
163
- statement
164
- else
165
- delete_statements(query(value))
166
- nil
167
- end
158
+ coerce_statements(statements, query: true, constant: true) do |value|
159
+ delete_statements(value)
168
160
  end
169
- statements.compact!
170
- delete_statements(statements) unless statements.empty?
171
161
 
172
162
  return self
173
163
  end
@@ -128,6 +128,14 @@ module RDF
128
128
  # method in order to provide for storage-specific optimized triple
129
129
  # pattern matching.
130
130
  #
131
+ # ## RDFStar (RDF*)
132
+ #
133
+ # Statements may have embedded statements as either a subject or object, recursively.
134
+ #
135
+ # Patterns may also have embedded patterns as either a subject or object, recursively.
136
+ #
137
+ # When matching, match an embedded pattern against embedded statements, recursively. (see {RDF::Query::Pattern#eql?})
138
+ #
131
139
  # @param [RDF::Query::Pattern] pattern
132
140
  # the query pattern to match
133
141
  # @param [Hash{Symbol => Object}] options ({})
@@ -7,6 +7,7 @@ module RDF
7
7
  # @see RDF::Repository
8
8
  module Writable
9
9
  extend RDF::Util::Aliasing::LateBound
10
+ include RDF::Util::Coercions
10
11
 
11
12
  ##
12
13
  # Returns `true` if `self` is writable.
@@ -53,31 +54,20 @@ module RDF
53
54
  # @overload insert(*statements)
54
55
  # @param [Array<RDF::Statement>] statements
55
56
  # @return [self]
57
+ # @raise [ArgumentError] on an attempt to insert an embedded statement when it is not supported
56
58
  #
57
59
  # @overload insert(statements)
58
60
  # @param [Enumerable<RDF::Statement>] statements
59
61
  # @return [self]
62
+ # @raise [ArgumentError] on an attempt to insert an embedded statement when it is not supported
60
63
  def insert(*statements)
61
- statements.map! do |value|
62
- case
63
- when value.respond_to?(:each_statement)
64
- insert_statements(value)
65
- nil
66
- when (statement = Statement.from(value))
67
- statement
68
- else
69
- raise ArgumentError.new("not a valid statement: #{value.inspect}")
70
- end
71
- end
72
- statements.compact!
73
- insert_statements(statements) unless statements.empty?
64
+ coerce_statements(statements) { |value| insert_statements value }
74
65
 
75
66
  return self
76
67
  end
77
68
  alias_method :insert!, :insert
78
69
 
79
70
  protected
80
-
81
71
  ##
82
72
  # Inserts statements from the given RDF reader into the underlying
83
73
  # storage or output stream.
@@ -132,10 +122,14 @@ module RDF
132
122
  #
133
123
  # @param [RDF::Enumerable] statements
134
124
  # @return [void]
125
+ # @raise [ArgumentError] on an attempt to insert an embedded statement when it is not supported
135
126
  # @since 0.1.6
136
127
  def insert_statements(statements)
137
128
  each = statements.respond_to?(:each_statement) ? :each_statement : :each
138
129
  statements.__send__(each) do |statement|
130
+ if statement.embedded? && respond_to?(:supports?) && !supports?(:rdfstar)
131
+ raise ArgumentError, "Wriable does not support embedded statements"
132
+ end
139
133
  insert_statement(statement)
140
134
  end
141
135
  end
@@ -150,6 +144,7 @@ module RDF
150
144
  #
151
145
  # @param [RDF::Statement] statement
152
146
  # @return [void]
147
+ # @raise [ArgumentError] on an attempt to insert an embedded statement when it is not supported
153
148
  # @abstract
154
149
  def insert_statement(statement)
155
150
  raise NotImplementedError.new("#{self.class}#insert_statement")
@@ -104,7 +104,7 @@ module RDF
104
104
  # @private
105
105
  # @see RDF::Enumerable#supports?
106
106
  def supports?(feature)
107
- return true if feature == :graph_name
107
+ return true if [:graph_name, :rdfstar].include?(feature)
108
108
  super
109
109
  end
110
110
 
@@ -283,6 +283,9 @@ module RDF
283
283
  # @private
284
284
  # @see RDF::Mutable#insert
285
285
  def insert_statement(statement)
286
+ if statement.embedded? && !@data.supports?(:rdfstar)
287
+ raise ArgumentError, "Graph does not support embedded statements"
288
+ end
286
289
  statement = statement.dup
287
290
  statement.graph_name = graph_name
288
291
  @data.insert(statement)
@@ -26,7 +26,7 @@ module RDF
26
26
  # RDF::Statement(s, p, "o")
27
27
  #
28
28
  class Statement
29
- include RDF::Value
29
+ include RDF::Resource
30
30
 
31
31
  ##
32
32
  # @private
@@ -112,7 +112,7 @@ module RDF
112
112
  elsif @subject.nil?
113
113
  nil
114
114
  else
115
- raise ArgumentError, "expected subject to be nil or a term, was #{@subject.inspect}"
115
+ raise ArgumentError, "expected subject to be nil or a resource, was #{@subject.inspect}"
116
116
  end
117
117
  @predicate = Node.intern(@predicate) if @predicate.is_a?(Symbol)
118
118
  @object = if @object.is_a?(Value)
@@ -124,6 +124,15 @@ module RDF
124
124
  else
125
125
  Literal.new(@object)
126
126
  end
127
+ @graph_name = if @graph_name.is_a?(Value)
128
+ @graph_name.to_term
129
+ elsif @graph_name.is_a?(Symbol)
130
+ Node.intern(@graph_name)
131
+ elsif !@graph_name
132
+ @graph_name
133
+ else
134
+ raise ArgumentError, "expected graph_name to be nil or a resource, was #{@graph_name.inspect}"
135
+ end
127
136
  end
128
137
 
129
138
  ##
@@ -140,10 +149,18 @@ module RDF
140
149
  #
141
150
  # @return [Boolean]
142
151
  def variable?
143
- !(has_subject? && subject.resource? &&
144
- has_predicate? && predicate.resource? &&
145
- has_object? && (object.resource? || object.literal?) &&
146
- (has_graph? ? graph_name.resource? : true))
152
+ !(has_subject? && subject.constant? &&
153
+ has_predicate? && predicate.constant? &&
154
+ has_object? && object.constant? &&
155
+ (has_graph? ? graph_name.constant? : true))
156
+ end
157
+
158
+ ##
159
+ # Returns `true` if any element of the statement is, itself, a statement.
160
+ #
161
+ # @return [Boolean]
162
+ def embedded?
163
+ subject && subject.statement? || object && object.statement?
147
164
  end
148
165
 
149
166
  ##
@@ -386,7 +403,13 @@ module RDF
386
403
  # @return [String]
387
404
  def to_s
388
405
  (graph_name ? to_quad : to_triple).map do |term|
389
- term.respond_to?(:to_base) ? term.to_base : term.inspect
406
+ if term.is_a?(Statement)
407
+ "<<#{term.to_s[0..-3]}>>"
408
+ elsif term.respond_to?(:to_base)
409
+ term.to_base
410
+ else
411
+ term.inspect
412
+ end
390
413
  end.join(" ") + " ."
391
414
  end
392
415
 
@@ -1,5 +1,5 @@
1
1
  # coding: utf-8
2
- require 'uri'
2
+ require 'cgi'
3
3
 
4
4
  module RDF
5
5
  ##
@@ -400,7 +400,7 @@ module RDF
400
400
  # @example Joining two URIs
401
401
  # RDF::URI.new('http://example.org/foo/bar').join('/foo')
402
402
  # #=> RDF::URI('http://example.org/foo')
403
- # @see <http://github.com/ruby-rdf/rdf-spec/blob/master/lib/rdf/spec/uri.rb>
403
+ # @see <https://github.com/ruby-rdf/rdf-spec/blob/master/lib/rdf/spec/uri.rb>
404
404
  # @see <http://tools.ietf.org/html/rfc3986#section-5.2>
405
405
  # @see RDF::URI#/
406
406
  # @see RDF::URI#+
@@ -471,7 +471,7 @@ module RDF
471
471
  # @see RDF::URI#+
472
472
  # @see RDF::URI#join
473
473
  # @see <http://tools.ietf.org/html/rfc3986#section-5.2>
474
- # @see <http://github.com/ruby-rdf/rdf-spec/blob/master/lib/rdf/spec/uri.rb>
474
+ # @see <https://github.com/ruby-rdf/rdf-spec/blob/master/lib/rdf/spec/uri.rb>
475
475
  # @example Building a HTTP URL
476
476
  # RDF::URI.new('http://example.org') / 'jhacker' / 'foaf.ttl'
477
477
  # #=> RDF::URI('http://example.org/jhacker/foaf.ttl')
@@ -846,7 +846,7 @@ module RDF
846
846
  parts[:user] = (user.dup.force_encoding(Encoding::UTF_8) if user)
847
847
  parts[:password] = (password.dup.force_encoding(Encoding::UTF_8) if password)
848
848
  parts[:host] = (host.dup.force_encoding(Encoding::UTF_8) if host)
849
- parts[:port] = (URI.decode(port).to_i if port)
849
+ parts[:port] = (CGI.unescape(port).to_i if port)
850
850
  parts[:path] = (path.to_s.dup.force_encoding(Encoding::UTF_8) unless path.empty?)
851
851
  parts[:query] = (query[1..-1].dup.force_encoding(Encoding::UTF_8) if query)
852
852
  parts[:fragment] = (fragment[1..-1].dup.force_encoding(Encoding::UTF_8) if fragment)
@@ -902,7 +902,7 @@ module RDF
902
902
  # Normalized version of user
903
903
  # @return [String]
904
904
  def normalized_user
905
- URI.encode(URI.decode(user), /[^#{IUNRESERVED}|#{SUB_DELIMS}]/) if user
905
+ URI.encode(CGI.unescape(user), /[^#{IUNRESERVED}|#{SUB_DELIMS}]/).force_encoding(Encoding::UTF_8) if user
906
906
  end
907
907
 
908
908
  ##
@@ -928,7 +928,7 @@ module RDF
928
928
  # Normalized version of password
929
929
  # @return [String]
930
930
  def normalized_password
931
- URI.encode(URI.decode(password), /[^#{IUNRESERVED}|#{SUB_DELIMS}]/) if password
931
+ URI.encode(CGI.unescape(password), /[^#{IUNRESERVED}|#{SUB_DELIMS}]/).force_encoding(Encoding::UTF_8) if password
932
932
  end
933
933
 
934
934
  HOST_FROM_AUTHORITY_RE = /(?:[^@]+@)?([^:]+)(?::.*)?$/.freeze
@@ -1180,8 +1180,8 @@ module RDF
1180
1180
  inject(return_type == Hash ? {} : []) do |memo,kv|
1181
1181
  k,v = kv.to_s.split('=', 2)
1182
1182
  next if k.to_s.empty?
1183
- k = URI.decode(k)
1184
- v = URI.decode(v) if v
1183
+ k = CGI.unescape(k)
1184
+ v = CGI.unescape(v) if v
1185
1185
  if return_type == Hash
1186
1186
  case memo[k]
1187
1187
  when nil then memo[k] = v
@@ -1293,9 +1293,9 @@ module RDF
1293
1293
  def normalize_segment(value, expr, downcase = false)
1294
1294
  if value
1295
1295
  value = value.dup.force_encoding(Encoding::UTF_8)
1296
- decoded = URI.decode(value)
1296
+ decoded = CGI.unescape(value)
1297
1297
  decoded.downcase! if downcase
1298
- URI.encode(decoded, /[^(?:#{expr})]/)
1298
+ URI.encode(decoded, /[^(?:#{expr})]/).force_encoding(Encoding::UTF_8)
1299
1299
  end
1300
1300
  end
1301
1301
 
@@ -69,9 +69,9 @@ module RDF
69
69
 
70
70
  begin
71
71
  unless blank? || read_comment
72
- subject = read_uriref || read_node || fail_subject
72
+ subject = read_uriref || read_node || read_rdfstar || fail_subject
73
73
  predicate = read_uriref(intern: true) || fail_predicate
74
- object = read_uriref || read_node || read_literal || fail_object
74
+ object = read_uriref || read_node || read_literal || read_rdfstar || fail_object
75
75
  graph_name = read_uriref || read_node
76
76
  if validate? && !read_eos
77
77
  log_error("Expected end of statement (found: #{current_line.inspect})", lineno: lineno, exception: RDF::ReaderError)
@@ -15,15 +15,17 @@ module RDF
15
15
  #
16
16
  # <https://rubygems.org/gems/rdf> <http://purl.org/dc/terms/title> "rdf" .
17
17
  #
18
- # Installation
19
- # ------------
18
+ # ## RDFStar (RDF*)
19
+ #
20
+ # Supports statements as resources using `<<s p o>>`.
21
+ #
22
+ # ## Installation
20
23
  #
21
24
  # This is the only RDF serialization format that is directly supported by
22
25
  # RDF.rb. Support for other formats is available in the form of add-on
23
26
  # gems, e.g. 'rdf-xml' or 'rdf-json'.
24
27
  #
25
- # Documentation
26
- # -------------
28
+ # ## Documentation
27
29
  #
28
30
  # * {RDF::NTriples::Format}
29
31
  # * {RDF::NTriples::Reader}
@@ -1,4 +1,7 @@
1
1
  # -*- encoding: utf-8 -*-
2
+
3
+ require 'strscan'
4
+
2
5
  module RDF::NTriples
3
6
  ##
4
7
  # N-Triples parser.
@@ -25,6 +28,10 @@ module RDF::NTriples
25
28
  # end
26
29
  # end
27
30
  #
31
+ # ** RDFStar (RDF*)
32
+ #
33
+ # Supports statements as resources using `<<s p o>>`.
34
+ #
28
35
  # @see http://www.w3.org/TR/rdf-testcases/#ntriples
29
36
  # @see http://www.w3.org/TR/n-triples/
30
37
  class Reader < RDF::Reader
@@ -70,6 +77,10 @@ module RDF::NTriples
70
77
  # 22
71
78
  STRING_LITERAL_QUOTE = /"((?:[^\"\\\n\r]|#{ECHAR}|#{UCHAR})*)"/.freeze
72
79
 
80
+ # RDF*
81
+ ST_START = /^<</.freeze
82
+ ST_END = /^\s*>>/.freeze
83
+
73
84
  # @see http://www.w3.org/TR/rdf-testcases/#ntrip_grammar
74
85
  COMMENT = /^#\s*(.*)$/.freeze
75
86
  NODEID = /^#{BLANK_NODE_LABEL}/.freeze
@@ -202,7 +213,7 @@ module RDF::NTriples
202
213
  begin
203
214
  read_statement
204
215
  rescue RDF::ReaderError
205
- value = read_uriref || read_node || read_literal
216
+ value = read_uriref || read_node || read_literal || read_rdfstar
206
217
  log_recover
207
218
  value
208
219
  end
@@ -218,9 +229,9 @@ module RDF::NTriples
218
229
 
219
230
  begin
220
231
  unless blank? || read_comment
221
- subject = read_uriref || read_node || fail_subject
232
+ subject = read_uriref || read_node || read_rdfstar || fail_subject
222
233
  predicate = read_uriref(intern: true) || fail_predicate
223
- object = read_uriref || read_node || read_literal || fail_object
234
+ object = read_uriref || read_node || read_literal || read_rdfstar || fail_object
224
235
 
225
236
  if validate? && !read_eos
226
237
  log_error("Expected end of statement (found: #{current_line.inspect})", lineno: lineno, exception: RDF::ReaderError)
@@ -234,6 +245,20 @@ module RDF::NTriples
234
245
  end
235
246
  end
236
247
 
248
+ ##
249
+ # @return [RDF::Statement]
250
+ def read_rdfstar
251
+ if @options[:rdfstar] && match(ST_START)
252
+ subject = read_uriref || read_node || read_rdfstar || fail_subject
253
+ predicate = read_uriref(intern: true) || fail_predicate
254
+ object = read_uriref || read_node || read_literal || read_rdfstar || fail_object
255
+ if !match(ST_END)
256
+ log_error("Expected end of statement (found: #{current_line.inspect})", lineno: lineno, exception: RDF::ReaderError)
257
+ end
258
+ RDF::Statement.new(subject, predicate, object)
259
+ end
260
+ end
261
+
237
262
  ##
238
263
  # @return [Boolean]
239
264
  # @see http://www.w3.org/TR/rdf-testcases/#ntrip_grammar (comment)
@@ -221,6 +221,15 @@ module RDF::NTriples
221
221
  format_triple(*statement.to_triple, **options)
222
222
  end
223
223
 
224
+ ##
225
+ # Returns the N-Triples representation of an RDF* reified statement.
226
+ #
227
+ # @param [RDF::Statement] statement
228
+ # @param [Hash{Symbol => Object}] options ({})
229
+ # @return [String]
230
+ def format_rdfstar(statement, **options)
231
+ "<<%s %s %s>>" % statement.to_a.map { |value| format_term(value, **options) }
232
+ end
224
233
  ##
225
234
  # Returns the N-Triples representation of a triple.
226
235
  #
@@ -289,6 +289,8 @@ module RDF
289
289
  # any additional keyword options
290
290
  # @option options [Hash{Symbol => RDF::Term}] bindings
291
291
  # optional variable bindings to use
292
+ # @option options [Boolean] :optimize
293
+ # Optimize query before execution.
292
294
  # @option options [RDF::Query::Solutions] solutions
293
295
  # optional initial solutions for chained queries
294
296
  # @yield [solution]
@@ -311,6 +313,7 @@ module RDF
311
313
  return @solutions
312
314
  end
313
315
 
316
+ self.optimize! if options[:optimize]
314
317
  patterns = @patterns
315
318
  graph_name = name if graph_name.nil?
316
319
  @graph_name = graph_name unless graph_name.nil?
@@ -505,7 +508,7 @@ module RDF
505
508
  # @return [RDF::Query]
506
509
  def dup
507
510
  patterns = @patterns.map {|p| p.dup}
508
- Query.new(patterns, solutions: @solutions.dup, **options)
511
+ Query.new(patterns, graph_name: graph_name, solutions: @solutions.dup, **options)
509
512
  end
510
513
 
511
514
  ##