rdf 1.1.0.p0 → 1.1.0.p1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- ZWZmZWNkYTZjMTY1NjlmMjAyMzM3MmE2ODVmZmUwZDY0NDE3NDkzMQ==
4
+ MmJiOTYzMzMzOTA3MWUwZGU0OWM2YjI2OTNiNTkwMGI0NmFhOTdmMw==
5
5
  data.tar.gz: !binary |-
6
- MGMzZWNkNzhjYmZlOTk4NjkyY2VhNjEzOTgyMjRmY2U1MjRiMjA4Zg==
6
+ NmMzMGE3OGQ0ZWY0MjNhMzc3NDk3MTdhOWRiNWY4ZTBkM2YwMzliZA==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- M2M3OTczM2MwNjRkZWI4ZjgwNWUxZWY4MTJmNjAxMzY2NjUyYTAxMTI5NDlk
10
- NDVkYWI0NTE5YmZhYmQ4NDI3ZTlhNzI4NmYxNWZiOTA0NmU5ZDhlYzFlN2M4
11
- MTZlOWM1Yzk2Njg4OTYxMjgwY2E3NTlhZDJhZmU0YWY5NzIyYTE=
9
+ MGM3NzBiNDdkN2MxNGRjMTIyMzRhMmYyZWVkZDdmMGM0OTcxNjEwYTk4NmFh
10
+ YTQzODMzYjZhYzNkOTY0ZGU1ZWRjMjUwNGQ0YmZjMTcwYjY2ZjU2NmM1OGY5
11
+ NzM2ZmFiZWNhNTRmNzVmYjQzOTJhNmRmMWJhMzQzNzNmMjAwNzE=
12
12
  data.tar.gz: !binary |-
13
- NTE0M2E3OTQyYjgyMTgwOGEyYzI3ZTZlODA0MzQ2ZmIyMDEzYWU0OWFjZDhm
14
- MDc5NmE3OTJiMTc1NDI1ZTg5NjhlYmU1MzlhMjNhYTdjM2Q0ZDA3MzM4ZmMy
15
- MjA4MGNlOGM1YjNlM2UxODlhMTk0ZTYyYWM5NjBkMjJhNThiMjQ=
13
+ YzRkYmU0YzYzYzVlZTBhOTAxNDYzMmNhYzg5ZTU1ZGZjNjEwN2E5YzdmZTc4
14
+ OTRhZTQyOGI3ZDhhOGZhNWJhM2ZmOGVmZDBkNGVkNmRiMjdjNmJjMmU5Njky
15
+ ZTcxMGJlZWU2NDJhNjhkYWE5ZTJjZmYxNzc1NjZjNjE1ZjMxNDU=
data/README CHANGED
@@ -25,7 +25,7 @@ This is a pure-Ruby library for working with [Resource Description Framework
25
25
  not modify any of Ruby's core classes or standard library.
26
26
  * Based entirely on Ruby's autoloading, meaning that you can generally make
27
27
  use of any one part of the library without needing to load up the rest.
28
- * Compatible with Ruby Ruby 1.9.x, Ruby 2.0, Rubinius and JRuby 1.7+.
28
+ * Compatible with Ruby Ruby 1.9.2, Ruby 2.0, Rubinius and JRuby 1.7+.
29
29
  * Compatible with older Ruby versions with the help of the [Backports][] gem.
30
30
  * Performs auto-detection of input to select appropriate Reader class if one
31
31
  cannot be determined from file characteristics.
@@ -117,7 +117,7 @@ to use when loading a file. The specific format to use can be forced using, e.g.
117
117
  option where the specific format symbol is determined by the available readers. Both also use
118
118
  MimeType or file extension, where available.
119
119
 
120
- require 'linkeddata'
120
+ require 'rdf/nquads'
121
121
 
122
122
  graph = RDF::Graph.load("http://ruby-rdf.github.com/rdf/etc/doap.nq", :format => :nquads)
123
123
 
@@ -151,7 +151,10 @@ appropriate writer to use.
151
151
 
152
152
  A specific sub-type of Writer can also be invoked directly:
153
153
 
154
- graph.dump(:nquads)
154
+ require 'rdf/nquads'
155
+
156
+ repo = RDF::Repository.new << RDF::Statement.new(:hello, RDF::DC.title, "Hello, world!", :context => RDF::URI("context"))
157
+ File.open("hello.nq", "w") {|f| f << repo.dump(:nquads)}
155
158
 
156
159
  ## Reader/Writer convenience methods
157
160
  {RDF::Enumerable} implements `to_{format}` for each available instance of {RDF::Reader}.
@@ -331,15 +334,14 @@ from BNode identity (i.e., they each entail the other)
331
334
 
332
335
  ## Dependencies
333
336
 
334
- * [Ruby](http://ruby-lang.org/) (>= 1.9.x)
335
- * [Addressable](http://rubygems.org/gems/addressable) (>= 2.2.0)
337
+ * [Ruby](http://ruby-lang.org/) (>= 1.9.2)
336
338
 
337
339
  ## Installation
338
340
 
339
341
  The recommended installation method is via [RubyGems](http://rubygems.org/).
340
342
  To install the latest official release of RDF.rb, do:
341
343
 
342
- % [sudo] gem install rdf # Ruby 1.9.x+
344
+ % [sudo] gem install rdf # Ruby 1.9.2+
343
345
 
344
346
  ## Download
345
347
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.1.0.p0
1
+ 1.1.0.p1
data/lib/rdf.rb CHANGED
@@ -5,6 +5,7 @@ require 'bigdecimal'
5
5
  require 'date'
6
6
  require 'time'
7
7
 
8
+ require 'rdf/version'
8
9
  require 'rdf/version'
9
10
 
10
11
  module RDF
@@ -64,6 +65,7 @@ module RDF
64
65
  ##
65
66
  # Alias for `RDF::Resource.new`.
66
67
  #
68
+ # @param (see RDF::Resource#initialize)
67
69
  # @return [RDF::Resource]
68
70
  def self.Resource(*args, &block)
69
71
  Resource.new(*args, &block)
@@ -72,6 +74,7 @@ module RDF
72
74
  ##
73
75
  # Alias for `RDF::Node.new`.
74
76
  #
77
+ # @param (see RDF::Node#initialize)
75
78
  # @return [RDF::Node]
76
79
  def self.Node(*args, &block)
77
80
  Node.new(*args, &block)
@@ -80,13 +83,7 @@ module RDF
80
83
  ##
81
84
  # Alias for `RDF::URI.new`.
82
85
  #
83
- # @overload URI(uri)
84
- # @param [URI, String, #to_s] uri
85
- #
86
- # @overload URI(options = {})
87
- # @param [Hash{Symbol => Object}] options
88
- # passed to `Addressable::URI.new`
89
- #
86
+ # @param (see RDF::URI#initialize)
90
87
  # @return [RDF::URI]
91
88
  def self.URI(*args, &block)
92
89
  case uri = args.first
@@ -101,6 +98,7 @@ module RDF
101
98
  ##
102
99
  # Alias for `RDF::Literal.new`.
103
100
  #
101
+ # @param (see RDF::Literal#initialize)
104
102
  # @return [RDF::Literal]
105
103
  def self.Literal(*args, &block)
106
104
  case literal = args.first
@@ -112,6 +110,7 @@ module RDF
112
110
  ##
113
111
  # Alias for `RDF::Graph.new`.
114
112
  #
113
+ # @param (see RDF::Graph#initialize)
115
114
  # @return [RDF::Graph]
116
115
  def self.Graph(*args, &block)
117
116
  Graph.new(*args, &block)
@@ -120,6 +119,7 @@ module RDF
120
119
  ##
121
120
  # Alias for `RDF::Statement.new`.
122
121
  #
122
+ # @param (see RDF::Statement#initialize)
123
123
  # @return [RDF::Statement]
124
124
  def self.Statement(*args, &block)
125
125
  Statement.new(*args, &block)
@@ -128,7 +128,7 @@ module RDF
128
128
  ##
129
129
  # Alias for `RDF::Vocabulary.create`.
130
130
  #
131
- # @param [String] uri
131
+ # @param (see RDF::Vocabulary#initialize)
132
132
  # @return [Class]
133
133
  def self.Vocabulary(uri)
134
134
  Vocabulary.create(uri)
@@ -106,6 +106,9 @@ module RDF
106
106
 
107
107
  ##
108
108
  # Deletes RDF statements from `self`.
109
+ # If any statement contains a {Query::Variable}, it is
110
+ # considered to be a pattern, and used to query
111
+ # self to find matching statements to delete.
109
112
  #
110
113
  # @param [Enumerable<RDF::Statement>] statements
111
114
  # @raise [TypeError] if `self` is immutable
@@ -118,7 +121,7 @@ module RDF
118
121
  when value.respond_to?(:each_statement)
119
122
  delete_statements(value)
120
123
  nil
121
- when (statement = Statement.from(value)).valid?
124
+ when (statement = Statement.from(value)).constant?
122
125
  statement
123
126
  else
124
127
  delete_statements(query(value))
@@ -78,7 +78,6 @@ module RDF
78
78
  require 'rdf/model/literal/datetime'
79
79
  require 'rdf/model/literal/time'
80
80
  require 'rdf/model/literal/token'
81
- require 'rdf/model/literal/xml'
82
81
 
83
82
  include RDF::Term
84
83
 
@@ -132,8 +131,14 @@ module RDF
132
131
  # depending on if there is language
133
132
  #
134
133
  # @param [Object] value
135
- # @option options [Symbol] :language (nil)
136
- # @option options [URI] :datatype (nil)
134
+ # @option options [Symbol] :language (nil)
135
+ # @option options [String] :lexical (nil)
136
+ # Supplied lexical representation of this literal,
137
+ # otherwise it comes from transforming `value` to a string form
138
+ # See {#to_s}.
139
+ # @option options [URI] :datatype (nil)
140
+ # @option options [Boolean] :validate (false)
141
+ # @option options [Boolean] :canonicalize (false)
137
142
  # @raise [ArgumentError]
138
143
  # if there is a language and datatype is no rdf:langString
139
144
  # @see http://www.w3.org/TR/rdf11-concepts/#section-Graph-Literal
@@ -105,6 +105,18 @@ module RDF
105
105
  true
106
106
  end
107
107
 
108
+ ##
109
+ # Returns `true` if any element of the statement is not a
110
+ # URI, Node or Literal.
111
+ #
112
+ # @return [Boolean]
113
+ def variable?
114
+ !(has_subject? && subject.resource? &&
115
+ has_predicate? && predicate.resource? &&
116
+ has_object? && (object.resource? || object.literal?) &&
117
+ (has_context? ? context.resource? : true ))
118
+ end
119
+
108
120
  ##
109
121
  # @return [Boolean]
110
122
  def invalid?
@@ -56,15 +56,6 @@ module RDF
56
56
  super
57
57
  end
58
58
 
59
- ##
60
- # Returns `true` if this term is constant.
61
- #
62
- # @return [Boolean] `true` or `false`
63
- # @see #variable?
64
- def constant?
65
- !(variable?)
66
- end
67
-
68
59
  ##
69
60
  # Returns a base representation of `self`.
70
61
  #
@@ -1,12 +1,11 @@
1
- require 'addressable/uri'
1
+ # coding: utf-8
2
+ require 'uri'
2
3
 
3
4
  module RDF
4
5
  ##
5
6
  # A Uniform Resource Identifier (URI).
6
7
  # Also compatible with International Resource Identifier (IRI)
7
8
  #
8
- # `RDF::URI` supports all the instance methods of `Addressable::URI`.
9
- #
10
9
  # @example Creating a URI reference (1)
11
10
  # uri = RDF::URI.new("http://rdf.rubyforge.org/")
12
11
  #
@@ -45,7 +44,7 @@ module RDF
45
44
  SCHEME = Regexp.compile("[A-za-z](?:[A-Za-z0-9+-\.])*").freeze
46
45
  PORT = Regexp.compile("[0-9]*").freeze
47
46
  IP_literal = Regexp.compile("\\[[0-9A-Fa-f:\\.]*\\]").freeze # Simplified, no IPvFuture
48
- PCT_ENCODED = Regexp.compile("%[0-9A-Fa-f]{2}").freeze
47
+ PCT_ENCODED = Regexp.compile("%[0-9A-Fa-f][0-9A-Fa-f]").freeze
49
48
  GEN_DELIMS = Regexp.compile("[:/\\?\\#\\[\\]@]").freeze
50
49
  SUB_DELIMS = Regexp.compile("[!\\$&'\\(\\)\\*\\+,;=]").freeze
51
50
  RESERVED = Regexp.compile("(?:#{GEN_DELIMS}|#{SUB_DELIMS})").freeze
@@ -79,7 +78,36 @@ module RDF
79
78
 
80
79
  IHIER_PART = Regexp.compile("(?:(?://#{IAUTHORITY}#{IPATH_ABEMPTY})|(?:#{IPATH_ABSOLUTE})|(?:#{IPATH_ROOTLESS})|(?:#{IPATH_EMPTY}))").freeze
81
80
  IRI = Regexp.compile("^#{SCHEME}:(?:#{IHIER_PART})(?:\\?#{IQUERY})?(?:\\##{IFRAGMENT})?$").freeze
82
-
81
+
82
+ # Split an IRI into it's component parts
83
+ IRI_PARTS = /^(?:([^:\/?#]+):)?(?:\/\/([^\/?#]*))?([^?#]*)(\?[^#]*)?(#.*)?$/
84
+
85
+ # Remove dot expressions regular expressions
86
+ RDS_2A = /^\.?\.\/(.*)$/.freeze
87
+ RDS_2B1 = /^\/\.$/.freeze
88
+ RDS_2B2 = /^(?:\/\.\/)(.*)$/.freeze
89
+ RDS_2C1 = /^\/\.\.$/.freeze
90
+ RDS_2C2 = /^(?:\/\.\.\/)(.*)$/.freeze
91
+ RDS_2D = /^\.\.?$/.freeze
92
+ RDS_2E = /^(\/?[^\/]*)(\/?.*)?$/.freeze
93
+
94
+ # Remove port, if it is standard for the scheme when normalizing
95
+ PORT_MAPPING = {
96
+ "http" => 80,
97
+ "https" => 443,
98
+ "ftp" => 21,
99
+ "tftp" => 69,
100
+ "sftp" => 22,
101
+ "ssh" => 22,
102
+ "svn+ssh" => 22,
103
+ "telnet" => 23,
104
+ "nntp" => 119,
105
+ "gopher" => 70,
106
+ "wais" => 210,
107
+ "ldap" => 389,
108
+ "prospero" => 1525
109
+ }
110
+
83
111
  ##
84
112
  # @return [RDF::Util::Cache]
85
113
  # @private
@@ -103,7 +131,7 @@ module RDF
103
131
  # object can't be returned for some reason, this method will fall back
104
132
  # to returning a freshly-allocated one.
105
133
  #
106
- # @param [String, #to_s] str
134
+ # @param (see #initialize)
107
135
  # @return [RDF::URI] an immutable, frozen URI object
108
136
  def self.intern(str)
109
137
  (cache[str = str.to_s] ||= self.new(str)).freeze
@@ -113,7 +141,8 @@ module RDF
113
141
  # Creates a new `RDF::URI` instance based on the given `uri` string.
114
142
  #
115
143
  # This is just an alias for {RDF::URI#initialize} for compatibity
116
- # with `Addressable::URI.parse`.
144
+ # with `Addressable::URI.parse`. Actual parsing is defered
145
+ # until {#object} is accessed.
117
146
  #
118
147
  # @param [String, #to_s] str
119
148
  # @return [RDF::URI]
@@ -122,20 +151,97 @@ module RDF
122
151
  end
123
152
 
124
153
  ##
125
- # @overload URI.new(uri)
126
- # @param [RDF::URI, String, #to_s] uri
154
+ # Resolve paths to their simplest form.
127
155
  #
128
- # @overload URI.new(options = {})
156
+ # TODO: This process is correct, but overly iterative. It could be better done with a single regexp which handled most of the segment collapses all at once. Parent segments would still require iteration.
157
+ #
158
+ # @param [String] path
159
+ # @return [String] normalized path
160
+ # @see http://tools.ietf.org/html/rfc3986#section-5.2.4
161
+ def self.normalize_path(path)
162
+ output, input = "", path.to_s
163
+ if input.encoding != Encoding::ASCII_8BIT
164
+ input = input.dup if input.frozen?
165
+ input = input.force_encoding(Encoding::ASCII_8BIT)
166
+ end
167
+ until input.empty?
168
+ if input.match(RDS_2A)
169
+ # If the input buffer begins with a prefix of "../" or "./", then remove that prefix from the input buffer; otherwise,
170
+ input = $1
171
+ elsif input.match(RDS_2B1) || input.match(RDS_2B2)
172
+ # if the input buffer begins with a prefix of "/./" or "/.", where "." is a complete path segment, then replace that prefix with "/" in the input buffer; otherwise,
173
+ input = "/#{$1}"
174
+ elsif input.match(RDS_2C1) || input.match(RDS_2C2)
175
+ # if the input buffer begins with a prefix of "/../" or "/..", where ".." is a complete path segment, then replace that prefix with "/" in the input buffer
176
+ input = "/#{$1}"
177
+
178
+ # and remove the last segment and its preceding "/" (if any) from the output buffer; otherwise,
179
+ output.sub!(/\/?[^\/]*$/, '')
180
+ elsif input.match(RDS_2D)
181
+ # if the input buffer consists only of "." or "..", then remove that from the input buffer; otherwise,
182
+ input = ""
183
+ elsif input.match(RDS_2E)
184
+ # move the first path segment in the input buffer to the end of the output buffer, including the initial "/" character (if any) and any subsequent characters up to, but not including, the next "/" character or the end of the input buffer.end
185
+ seg, input = $1, $2
186
+ output << seg
187
+ end
188
+ end
189
+
190
+ output.sub(/\/+/, '/').force_encoding(Encoding::UTF_8)
191
+ end
192
+
193
+ ##
194
+ # @overload URI(uri, options = {})
195
+ # @param [URI, String, #to_s] uri
129
196
  # @param [Hash{Symbol => Object}] options
130
- def initialize(uri_or_options)
131
- case uri_or_options
132
- when Hash
133
- @uri = Addressable::URI.new(uri_or_options)
134
- when Addressable::URI
135
- @uri = uri_or_options
136
- else
137
- @uri = Addressable::URI.parse(uri_or_options.to_s)
197
+ # @option options [Boolean] :validate (false)
198
+ # @option options [Boolean] :canonicalize (false)
199
+ #
200
+ # @overload URI(options = {})
201
+ # @param [Hash{Symbol => Object}] options
202
+ # @option options [Boolean] :validate (false)
203
+ # @option options [Boolean] :canonicalize (false)
204
+ # @option [String, #to_s] :scheme The scheme component.
205
+ # @option [String, #to_s] :user The user component.
206
+ # @option [String, #to_s] :password The password component.
207
+ # @option [String, #to_s] :userinfo
208
+ # The userinfo component. If this is supplied, the user and password
209
+ # components must be omitted.
210
+ # @option [String, #to_s] :host The host component.
211
+ # @option [String, #to_s] :port The port component.
212
+ # @option [String, #to_s] :authority
213
+ # The authority component. If this is supplied, the user, password,
214
+ # userinfo, host, and port components must be omitted.
215
+ # @option [String, #to_s] :path The path component.
216
+ # @option [String, #to_s] :query The query component.
217
+ # @option [String, #to_s] :fragment The fragment component.
218
+ def initialize(*args)
219
+ options = args.last.is_a?(Hash) ? args.last : {}
220
+ uri = args.first
221
+ case uri
222
+ when Hash
223
+ %w(
224
+ scheme
225
+ user password userinfo
226
+ host port authority
227
+ path query fragment
228
+ ).map(&:to_sym).each do |meth|
229
+ if uri.has_key?(meth)
230
+ self.send("#{meth}=".to_sym, uri[meth])
231
+ else
232
+ self.send(meth)
233
+ end
234
+ end
235
+ else
236
+ @value = uri.to_s
237
+ if @value.encoding != Encoding::UTF_8
238
+ @value = @value.dup if @value.frozen?
239
+ @value.force_encoding(Encoding::UTF_8)
240
+ end
138
241
  end
242
+
243
+ validate! if options[:validate]
244
+ canonicalize! if options[:canonicalize]
139
245
  end
140
246
 
141
247
  ##
@@ -157,7 +263,7 @@ module RDF
157
263
  # @see http://en.wikipedia.org/wiki/Uniform_Resource_Name
158
264
  # @since 0.2.0
159
265
  def urn?
160
- self.start_with?('urn:')
266
+ @object ? @object[:scheme] == 'urn' : start_with?('urn:')
161
267
  end
162
268
 
163
269
  ##
@@ -173,6 +279,16 @@ module RDF
173
279
  !urn?
174
280
  end
175
281
 
282
+ ##
283
+ # A URI is absolute when it has a scheme
284
+ # @return [Boolean] `true` or `false`
285
+ def absolute?; !scheme.nil?; end
286
+
287
+ ##
288
+ # A URI is relative when it does not have a scheme
289
+ # @return [Boolean] `true` or `false`
290
+ def relative?; !absolute?; end
291
+
176
292
  ##
177
293
  # Returns the string length of this URI.
178
294
  #
@@ -187,14 +303,13 @@ module RDF
187
303
  alias_method :size, :length
188
304
 
189
305
  ##
190
- # Determine if the URI is avalid according to RFC3987
306
+ # Determine if the URI is a valid according to RFC3987
191
307
  #
192
308
  # @return [Boolean] `true` or `false`
193
309
  # @since 0.3.9
194
310
  def valid?
195
- # As Addressable::URI does not perform adequate validation, validate
196
- # relative to RFC3987
197
- to_s.match(RDF::URI::IRI) || to_s.match(RDF::URI::IRELATIVE_REF) || false
311
+ # Validate relative to RFC3987
312
+ to_s.match(RDF::URI::IRI) || false
198
313
  end
199
314
 
200
315
  ##
@@ -217,6 +332,7 @@ module RDF
217
332
  def canonicalize
218
333
  self.dup.canonicalize!
219
334
  end
335
+ alias_method :normalize, :canonicalize
220
336
 
221
337
  ##
222
338
  # Converts this URI into its canonical lexical representation.
@@ -224,9 +340,17 @@ module RDF
224
340
  # @return [RDF::URI] `self`
225
341
  # @since 0.3.0
226
342
  def canonicalize!
227
- @uri.normalize!
343
+ @object = {
344
+ :scheme => normalized_scheme,
345
+ :authority => normalized_authority,
346
+ :path => normalized_path,
347
+ :query => normalized_query,
348
+ :fragment => normalized_fragment
349
+ }
350
+ @value = nil
228
351
  self
229
352
  end
353
+ alias_method :normalize!, :canonicalize!
230
354
 
231
355
  ##
232
356
  # Joins several URIs together.
@@ -250,12 +374,40 @@ module RDF
250
374
  # @see RDF::URI#+
251
375
  # @param [Array<String, RDF::URI, #to_s>] uris
252
376
  # @return [RDF::URI]
377
+ # @see http://tools.ietf.org/html/rfc3986#section-5.2.2
378
+ # @see http://tools.ietf.org/html/rfc3986#section-5.2.3
253
379
  def join(*uris)
254
- result = @uri.dup
380
+ joined_parts = object.dup.delete_if {|k, v| [:user, :password, :host, :port].include?(k)}
381
+
255
382
  uris.each do |uri|
256
- result = result.join(uri)
383
+ uri = RDF::URI.new(uri) unless uri.is_a?(RDF::URI)
384
+ next if uri.to_s.empty? # Don't mess with base URI
385
+
386
+ case
387
+ when uri.scheme
388
+ joined_parts = uri.object.merge(:path => self.class.normalize_path(uri.path))
389
+ when uri.authority
390
+ joined_parts[:authority] = uri.authority
391
+ joined_parts[:path] = self.class.normalize_path(uri.path)
392
+ joined_parts[:query] = uri.query
393
+ when uri.path.to_s.empty?
394
+ joined_parts[:query] = uri.query if uri.query
395
+ when uri.path[0,1] == '/'
396
+ joined_parts[:path] = self.class.normalize_path(uri.path)
397
+ joined_parts[:query] = uri.query
398
+ else
399
+ base_path = self.class.normalize_path(path.to_s)
400
+
401
+ # Merge path segments from section 5.2.3
402
+ base_path.sub!(/\/[^\/]*$/, '/')
403
+ joined_parts[:path] = self.class.normalize_path(base_path + uri.path)
404
+ joined_parts[:query] = uri.query
405
+ end
406
+ joined_parts[:fragment] = uri.fragment
257
407
  end
258
- self.class.new(result)
408
+
409
+ # Return joined URI
410
+ RDF::URI.new(joined_parts)
259
411
  end
260
412
 
261
413
  ##
@@ -281,6 +433,7 @@ module RDF
281
433
  #
282
434
  # @param [Any] fragment A URI fragment to be appended to this URI
283
435
  # @return [RDF::URI]
436
+ # @raise [ArgumentError] if the URI is invalid
284
437
  # @see RDF::URI#+
285
438
  # @see RDF::URI#join
286
439
  # @see <http://tools.ietf.org/html/rfc3986#section-5.2>
@@ -303,22 +456,34 @@ module RDF
303
456
  if urn?
304
457
  RDF::URI.intern(to_s.sub(/:+$/,'') + ':' + fragment.to_s.sub(/^:+/,''))
305
458
  else # !urn?
306
- case to_s[-1].chr
307
- when '#'
308
- case fragment.to_s[0].chr
309
- when '/' then # Base ending with '#', fragment beginning with '/'. The fragment wins, we use '/'.
310
- RDF::URI.intern(to_s.sub(/#+$/,'') + '/' + fragment.to_s.sub(/^\/+/,''))
459
+ res = self.dup
460
+ if res.fragment
461
+ case fragment.to_s[0,1]
462
+ when '/'
463
+ # Base with a fragment, fragment beginning with '/'. The fragment wins, we use '/'.
464
+ path, frag = fragment.to_s.split('#', 2)
465
+ res.path = "#{res.path}/#{path.sub(/^\/*/,'')}"
466
+ res.fragment = frag
311
467
  else
312
- RDF::URI.intern(to_s.sub(/#+$/,'') + '#' + fragment.to_s.sub(/^#+/,''))
468
+ # Replace fragment
469
+ res.fragment = fragment.to_s.sub(/^#+/,'')
313
470
  end
314
- else # includes '/'. Results from bases ending in '/' are the same as if there were no trailing slash.
315
- case fragment.to_s[0].chr
316
- when '#' then # Base ending with '/', fragment beginning with '#'. The fragment wins, we use '#'.
317
- RDF::URI.intern(to_s.sub(/\/+$/,'') + '#' + fragment.to_s.sub(/^#+/,''))
471
+ else
472
+ # Not a fragment. includes '/'. Results from bases ending in '/' are the same as if there were no trailing slash.
473
+ case fragment.to_s[0,1]
474
+ when '#'
475
+ # Base ending with '/', fragment beginning with '#'. The fragment wins, we use '#'.
476
+ res.path = res.path.to_s.sub!(/\/*$/, '')
477
+ # Add fragment
478
+ res.fragment = fragment.to_s.sub(/^#+/,'')
318
479
  else
319
- RDF::URI.intern(to_s.sub(/\/+$/,'') + '/' + fragment.to_s.sub(/^\/+/,''))
480
+ # Add fragment as path component
481
+ path, frag = fragment.to_s.split('#', 2)
482
+ res.path = res.path.to_s.sub(/\/*$/,'/') + path.sub(/^\/*/,'')
483
+ res.fragment = frag
320
484
  end
321
485
  end
486
+ RDF::URI.intern(res.to_s)
322
487
  end
323
488
  end
324
489
 
@@ -353,7 +518,7 @@ module RDF
353
518
  #
354
519
  # @return [Boolean] `true` or `false`
355
520
  def root?
356
- self.path == '/' || self.path.empty?
521
+ self.path == '/' || self.path.to_s.empty?
357
522
  end
358
523
 
359
524
  ##
@@ -368,9 +533,9 @@ module RDF
368
533
  if root?
369
534
  self
370
535
  else
371
- uri = self.dup
372
- uri.path = '/'
373
- uri
536
+ RDF::URI.new(
537
+ object.merge(:path => '/').
538
+ keep_if {|k, v| [:scheme, :authority, :path].include?(k)})
374
539
  end
375
540
  end
376
541
 
@@ -410,7 +575,7 @@ module RDF
410
575
  end
411
576
 
412
577
  ##
413
- # Returns a qualified name (QName) for this URI, if possible.
578
+ # Returns a qualified name (QName) for this URI based on available vocabularies, if possible.
414
579
  #
415
580
  # @example
416
581
  # RDF::URI('http://purl.org/dc/terms/').qname #=> [:dc, nil]
@@ -446,14 +611,21 @@ module RDF
446
611
  #
447
612
  # @return [RDF::URI]
448
613
  def dup
449
- self.class.new(@uri.dup)
614
+ self.class.new((@value || @object).dup)
450
615
  end
451
616
 
452
617
  ##
453
618
  # @private
454
619
  def freeze
455
- @uri.freeze
456
- super
620
+ unless frozen?
621
+ # Create derived components
622
+ authority; userinfo; user; password; host; port
623
+ @value = value.freeze
624
+ @object = object.freeze
625
+ @hash = hash.freeze
626
+ super
627
+ end
628
+ self
457
629
  end
458
630
 
459
631
  ##
@@ -521,7 +693,7 @@ module RDF
521
693
  # If other is a Literal, reverse test to consolodate complex type checking logic
522
694
  other == self
523
695
  when String then to_s == other
524
- when URI, Addressable::URI then to_s == other.to_s
696
+ when URI then to_s == other.to_s
525
697
  else other.respond_to?(:to_uri) && to_s == other.to_uri.to_s
526
698
  end
527
699
  end
@@ -577,7 +749,7 @@ module RDF
577
749
  #
578
750
  # @return [Sring]
579
751
  def to_base
580
- "<#{escape(@uri.to_s)}>"
752
+ "<#{escape(to_s)}>"
581
753
  end
582
754
 
583
755
  ##
@@ -587,48 +759,401 @@ module RDF
587
759
  # RDF::URI('http://example.org/').to_str #=> 'http://example.org/'
588
760
  #
589
761
  # @return [String]
590
- def to_str
591
- @uri.to_s
592
- end
762
+ def to_str; value; end
593
763
  alias_method :to_s, :to_str
594
764
 
765
+ ##
766
+ # Returns a <code>String</code> representation of the URI object's state.
767
+ #
768
+ # @return [String] The URI object's state, as a <code>String</code>.
769
+ def inspect
770
+ sprintf("#<%s:%#0x URI:%s>", URI.to_s, self.object_id, self.to_s)
771
+ end
772
+
773
+ ##
774
+ # lexical representation of URI, either absolute or relative
775
+ # @return [String]
776
+ def value
777
+ @value ||= [
778
+ ("#{scheme}:" if absolute?),
779
+ ("//#{authority}" if authority),
780
+ path,
781
+ ("?#{query}" if query),
782
+ ("##{fragment}" if fragment)
783
+ ].compact.join("")
784
+ end
785
+
595
786
  ##
596
787
  # Returns a hash code for this URI.
597
788
  #
598
789
  # @return [Fixnum]
599
790
  def hash
600
- @uri.hash
791
+ return @hash ||= (to_s.hash * -1)
601
792
  end
602
793
 
603
794
  ##
604
- # Returns `true` if this URI instance supports the `symbol` method.
795
+ # Returns object representation of this URI, broken into components
605
796
  #
606
- # @param [Symbol, String, #to_s] symbol
607
- # @return [Boolean] `true` or `false`
608
- def respond_to?(symbol)
609
- @uri.respond_to?(symbol) || super
797
+ # @return [Hash{Symbol => String}]
798
+ def object
799
+ @object ||= begin
800
+ parse @value
801
+ end
610
802
  end
803
+ alias_method :to_hash, :object
611
804
 
612
- protected
805
+ ##{
806
+ # Parse a URI into it's components
807
+ #
808
+ # @param [String, to_s] value
809
+ # @return [Object{Symbol => String}]
810
+ def parse(value)
811
+ value = value.to_s.dup.force_encoding(Encoding::ASCII_8BIT)
812
+ parts = {}
813
+ if matchdata = value.to_s.match(IRI_PARTS)
814
+ scheme, authority, path, query, fragment = matchdata.to_a[1..-1]
815
+ userinfo, hostport = authority.to_s.split('@', 2)
816
+ hostport, userinfo = userinfo, nil unless hostport
817
+ user, password = userinfo.to_s.split(':', 2)
818
+ host, port = hostport.to_s.split(':', 2)
819
+
820
+ parts[:scheme] = (scheme.force_encoding(Encoding::UTF_8) if scheme)
821
+ parts[:authority] = (authority.force_encoding(Encoding::UTF_8) if authority)
822
+ parts[:userinfo] = (userinfo.force_encoding(Encoding::UTF_8) if userinfo)
823
+ parts[:user] = (user.force_encoding(Encoding::UTF_8) if user)
824
+ parts[:password] = (password.force_encoding(Encoding::UTF_8) if password)
825
+ parts[:host] = (host.force_encoding(Encoding::UTF_8) if host)
826
+ parts[:port] = (::URI.decode(port).to_i if port)
827
+ parts[:path] = (path.to_s.force_encoding(Encoding::UTF_8) unless path.empty?)
828
+ parts[:query] = (query[1..-1].force_encoding(Encoding::UTF_8) if query)
829
+ parts[:fragment] = (fragment[1..-1].force_encoding(Encoding::UTF_8) if fragment)
830
+
831
+ parts.each_key do |k|
832
+ parts[k].force_encoding(Encoding::UTF_8) if parts[k].respond_to?(:encoding)
833
+ end
834
+ end
835
+
836
+ parts
837
+ end
613
838
 
614
839
  ##
615
- # @param [Symbol, String, #to_s] symbol
616
- # @param [Array<Object>] args
617
- # @yield
618
- # @return [Object]
619
- # @private
620
- def method_missing(symbol, *args, &block)
621
- if @uri.respond_to?(symbol)
622
- case result = @uri.send(symbol, *args, &block)
623
- when Addressable::URI
624
- self.class.new(result)
625
- else result
840
+ # @return [String]
841
+ def scheme; object.fetch(:scheme, nil); end
842
+
843
+ ##
844
+ # @param [String, #to_s] value
845
+ # @return [RDF::URI] self
846
+ def scheme=(value)
847
+ object[:scheme] = (value.to_s.force_encoding(Encoding::UTF_8) if value)
848
+ @value = nil
849
+ self
850
+ end
851
+
852
+ ##
853
+ # Return normalized version of scheme, if any
854
+ # @return [String]
855
+ def normalized_scheme
856
+ scheme.strip.downcase if scheme
857
+ end
858
+
859
+ ##
860
+ # @return [String]
861
+ def user
862
+ object.fetch(:user) do
863
+ @object[:user] = (userinfo.split(':', 2)[0] if userinfo)
864
+ end
865
+ end
866
+
867
+ ##
868
+ # @param [String, #to_s] value
869
+ # @return [RDF::URI] self
870
+ def user=(value)
871
+ object[:user] = (value.to_s.force_encoding(Encoding::UTF_8) if value)
872
+ @object[:userinfo] = format_userinfo("")
873
+ @object[:authority] = format_authority
874
+ @value = nil
875
+ self
876
+ end
877
+
878
+ ##
879
+ # Normalized version of user
880
+ # @return [String]
881
+ def normalized_user
882
+ ::URI.encode(::URI.decode(user), /[^#{IUNRESERVED}|#{SUB_DELIMS}]/) if user
883
+ end
884
+
885
+ ##
886
+ # @return [String]
887
+ def password
888
+ object.fetch(:password) do
889
+ @object[:password] = (userinfo.split(':', 2)[1] if userinfo)
890
+ end
891
+ end
892
+
893
+ ##
894
+ # @param [String, #to_s] value
895
+ # @return [RDF::URI] self
896
+ def password=(value)
897
+ object[:password] = (value.to_s.force_encoding(Encoding::UTF_8) if value)
898
+ @object[:userinfo] = format_userinfo("")
899
+ @object[:authority] = format_authority
900
+ @value = nil
901
+ self
902
+ end
903
+
904
+ ##
905
+ # Normalized version of password
906
+ # @return [String]
907
+ def normalized_password
908
+ ::URI.encode(::URI.decode(password), /[^#{IUNRESERVED}|#{SUB_DELIMS}]/) if password
909
+ end
910
+
911
+ ##
912
+ # @return [String]
913
+ def host
914
+ object.fetch(:host) do
915
+ @object[:host] = ($1 if @object[:authority].to_s.match(/(?:[^@]+@)?([^:]+)(?::.*)?$/))
916
+ end
917
+ end
918
+
919
+ ##
920
+ # @param [String, #to_s] value
921
+ # @return [RDF::URI] self
922
+ def host=(value)
923
+ object[:host] = (value.to_s.force_encoding(Encoding::UTF_8) if value)
924
+ @object[:authority] = format_authority
925
+ @value = nil
926
+ self
927
+ end
928
+
929
+ ##
930
+ # Normalized version of host
931
+ # @return [String]
932
+ def normalized_host
933
+ # Remove trailing '.' characters
934
+ normalize_segment(host, IHOST, true).sub(/\.*$/, '') if host
935
+ end
936
+
937
+ ##
938
+ # @return [String]
939
+ def port
940
+ object.fetch(:port) do
941
+ @object[:port] = ($1 if @object[:authority].to_s.match(/:(\d+)$/))
942
+ end
943
+ end
944
+
945
+ ##
946
+ # @param [String, #to_s] value
947
+ # @return [RDF::URI] self
948
+ def port=(value)
949
+ object[:port] = (value.to_s.to_i if value)
950
+ @object[:authority] = format_authority
951
+ @value = nil
952
+ self
953
+ end
954
+
955
+ ##
956
+ # Normalized version of port
957
+ # @return [String]
958
+ def normalized_port
959
+ if port
960
+ np = normalize_segment(port.to_s, PORT)
961
+ if PORT_MAPPING[normalized_scheme] == np.to_i
962
+ nil
963
+ else
964
+ np.to_i
626
965
  end
966
+ end
967
+ end
968
+
969
+ ##
970
+ # @return [String]
971
+ def path; object.fetch(:path, nil); end
972
+
973
+ ##
974
+ # @param [String, #to_s] value
975
+ # @return [RDF::URI] self
976
+ def path=(value)
977
+ if value
978
+ # Always lead with a slash
979
+ value = "/#{value}" if host && value.to_s =~ /^[^\/]/
980
+ object[:path] = value.to_s.force_encoding(Encoding::UTF_8)
627
981
  else
628
- super
982
+ object[:path] = nil
983
+ end
984
+ @value = nil
985
+ self
986
+ end
987
+
988
+ ##
989
+ # Normalized version of path
990
+ # @return [String]
991
+ def normalized_path
992
+ segments = path.to_s.split('/', -1) # preserve null segments
993
+
994
+ norm_segs = case
995
+ when authority
996
+ # ipath-abempty
997
+ segments.map {|s| normalize_segment(s, ISEGMENT)}
998
+ when segments.first.nil?
999
+ # ipath-absolute
1000
+ res = [nil]
1001
+ res << normalize_segment(segments[1], ISEGMENT_NZ) if segments.length > 1
1002
+ res += segments[2..-1].map {|s| normalize_segment(s, ISEGMENT)} if segments.length > 2
1003
+ res
1004
+ when segments.first.to_s.index(':')
1005
+ # ipath-noscheme
1006
+ res = []
1007
+ res << normalize_segment(segments[1], ISEGMENT_NZ_NC)
1008
+ res += segments[1..-1].map {|s| normalize_segment(s, ISEGMENT)} if segments.length > 1
1009
+ when segments.first
1010
+ # ipath-rootless
1011
+ # ipath-noscheme
1012
+ res = []
1013
+ res << normalize_segment(segments[0], ISEGMENT_NZ)
1014
+ res += segments[1..-1].map {|s| normalize_segment(s, ISEGMENT)} if segments.length > 1
1015
+ res
1016
+ else
1017
+ # Should be empty
1018
+ segments
1019
+ end
1020
+
1021
+ res = self.class.normalize_path(norm_segs.join("/"))
1022
+ # Special rules for specific protocols having empty paths
1023
+ res.empty? ? (%w(http https ftp tftp).include?(normalized_scheme) ? '/' : "") : res
1024
+ end
1025
+
1026
+ ##
1027
+ # @return [String]
1028
+ def query; object.fetch(:query, nil); end
1029
+
1030
+ ##
1031
+ # @param [String, #to_s] value
1032
+ # @return [RDF::URI] self
1033
+ def query=(value)
1034
+ object[:query] = (value.to_s.force_encoding(Encoding::UTF_8) if value)
1035
+ @value = nil
1036
+ self
1037
+ end
1038
+
1039
+ ##
1040
+ # Normalized version of query
1041
+ # @return [String]
1042
+ def normalized_query
1043
+ normalize_segment(query, IQUERY) if query
1044
+ end
1045
+
1046
+ ##
1047
+ # @return [String]
1048
+ def fragment; object.fetch(:fragment, nil); end
1049
+
1050
+ ##
1051
+ # @param [String, #to_s] value
1052
+ # @return [RDF::URI] self
1053
+ def fragment=(value)
1054
+ object[:fragment] = (value.to_s.force_encoding(Encoding::UTF_8) if value)
1055
+ @value = nil
1056
+ self
1057
+ end
1058
+
1059
+ ##
1060
+ # Normalized version of fragment
1061
+ # @return [String]
1062
+ def normalized_fragment
1063
+ normalize_segment(fragment, IFRAGMENT) if fragment
1064
+ end
1065
+
1066
+ ##
1067
+ # Authority is a combination of user, password, host and port
1068
+ def authority
1069
+ object.fetch(:authority) {
1070
+ @object[:authority] = (format_authority if @object[:host])
1071
+ }
1072
+ end
1073
+
1074
+ ##
1075
+ # @param [String, #to_s] value
1076
+ # @return [RDF::URI] self
1077
+ def authority=(value)
1078
+ object.delete_if {|k, v| [:user, :password, :host, :port, :userinfo].include?(k)}
1079
+ object[:authority] = (value.to_s.force_encoding(Encoding::UTF_8) if value)
1080
+ user; password; userinfo; host; port
1081
+ @value = nil
1082
+ self
1083
+ end
1084
+
1085
+ ##
1086
+ # Return normalized version of authority, if any
1087
+ # @return [String]
1088
+ def normalized_authority
1089
+ if authority
1090
+ (userinfo ? "#{normalized_userinfo}@" : "") +
1091
+ normalized_host +
1092
+ (normalized_port ? ":#{normalized_port}" : "")
1093
+ end
1094
+ end
1095
+
1096
+ ##
1097
+ # Userinfo is a combination of user and password
1098
+ def userinfo
1099
+ object.fetch(:userinfo) {
1100
+ @object[:userinfo] = (format_userinfo("") if @object[:user])
1101
+ }
1102
+ end
1103
+
1104
+ ##
1105
+ # @param [String, #to_s] value
1106
+ # @return [RDF::URI] self
1107
+ def userinfo=(value)
1108
+ object.delete_if {|k, v| [:user, :password, :authority].include?(k)}
1109
+ object[:userinfo] = (value.to_s.force_encoding(Encoding::UTF_8) if value)
1110
+ user; password; authority
1111
+ @value = nil
1112
+ self
1113
+ end
1114
+
1115
+ ##
1116
+ # Normalized version of userinfo
1117
+ # @return [String]
1118
+ def normalized_userinfo
1119
+ normalized_user + (password ? ":#{normalized_password}" : "") if userinfo
1120
+ end
1121
+
1122
+ private
1123
+
1124
+ ##
1125
+ # Normalize a segment using a character range
1126
+ #
1127
+ # @param [String] segment
1128
+ # @param [Regexp] expr
1129
+ # @param [Boolean] downcase
1130
+ # @result [String]
1131
+ def normalize_segment(value, expr, downcase = false)
1132
+ if value
1133
+ value = value.dup if value.frozen?
1134
+ value = value.force_encoding(Encoding::UTF_8)
1135
+ decoded = ::URI.decode(value)
1136
+ decoded.downcase! if downcase
1137
+ ::URI.encode(decoded, /[^#{expr}]/)
1138
+ end
1139
+ end
1140
+
1141
+ def format_userinfo(append = "")
1142
+ if @object[:user]
1143
+ @object[:user] + (@object[:password] ? ":#{@object[:password]}" : "") + append
1144
+ else
1145
+ ""
1146
+ end
1147
+ end
1148
+
1149
+ def format_authority
1150
+ if @object[:host]
1151
+ format_userinfo("@") + @object[:host] + (object[:port] ? ":#{object[:port]}" : "")
1152
+ else
1153
+ ""
629
1154
  end
630
1155
  end
631
- end # URI
1156
+ end
632
1157
 
633
1158
  # RDF::IRI is a synonym for RDF::URI
634
1159
  IRI = URI