rdf 3.1.0 → 3.1.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
  ##