Class: Addressable::Template

Inherits:
Object
  • Object
show all
Defined in:
lib/addressable/template.rb

Overview

This is an implementation of a URI template based on RFC 6570 (tools.ietf.org/html/rfc6570).

Defined Under Namespace

Classes: InvalidTemplateOperatorError, InvalidTemplateValueError, MatchData, TemplateOperatorAbortedError

Constant Summary collapse

RESERVED =
"(?:[#{anything}]|%[a-fA-F0-9][a-fA-F0-9])"
UNRESERVED =
"(?:[#{
  Addressable::URI::CharacterClasses::UNRESERVED
}]|%[a-fA-F0-9][a-fA-F0-9])"
VARNAME =
/^#{variable}$/
VARSPEC =
/^#{varspec}$/
VARIABLE_LIST =
/^#{varspec}(?:,#{varspec})*$/
EXPRESSION =
/\{([#{operator}])?(#{varspec}(?:,#{varspec})*)\}/
LEADERS =
{
  '?' => '?',
  '/' => '/',
  '#' => '#',
  '.' => '.',
  ';' => ';',
  '&' => '&'
}
JOINERS =
{
  '?' => '&',
  '.' => '.',
  ';' => ';',
  '&' => '&',
  '/' => '/'
}

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(pattern) ⇒ Addressable::Template

Creates a new Addressable::Template object.

Parameters:

  • pattern (#to_str)

    The URI Template pattern.



165
166
167
168
169
170
# File 'lib/addressable/template.rb', line 165

def initialize(pattern)
  if !pattern.respond_to?(:to_str)
    raise TypeError, "Can't convert #{pattern.class} into String."
  end
  @pattern = pattern.to_str.freeze
end

Instance Attribute Details

#patternString (readonly)

Returns The Template object’s pattern.

Returns:

  • (String)

    The Template object’s pattern.



174
175
176
# File 'lib/addressable/template.rb', line 174

def pattern
  @pattern
end

Instance Method Details

#expand(mapping, processor = nil) ⇒ Addressable::URI

Expands a URI template into a full URI.

The object should respond to either the validate or transform messages or both. Both the validate and transform methods should take two parameters: name and value. The validate method should return true or false; true if the value of the variable is valid, false otherwise. An InvalidTemplateValueError exception will be raised if the value is invalid. The transform method should return the transformed variable value as a String. If a transform method is used, the value will not be percent encoded automatically. Unicode normalization will be performed both before and after sending the value to the transform method.

Examples:

class ExampleProcessor
  def self.validate(name, value)
    return !!(value =~ /^[\w ]+$/) if name == "query"
    return true
  end

  def self.transform(name, value)
    return value.gsub(/ /, "+") if name == "query"
    return value
  end
end

Addressable::Template.new(
  "http://example.com/search/{query}/"
).expand(
  {"query" => "an example search query"},
  ExampleProcessor
).to_str
#=> "http://example.com/search/an+example+search+query/"

Addressable::Template.new(
  "http://example.com/search/{query}/"
).expand(
  {"query" => "an example search query"}
).to_str
#=> "http://example.com/search/an%20example%20search%20query/"

Addressable::Template.new(
  "http://example.com/search/{query}/"
).expand(
  {"query" => "bogus!"},
  ExampleProcessor
).to_str
#=> Addressable::Template::InvalidTemplateValueError

Parameters:

  • mapping (Hash)

    The mapping that corresponds to the pattern.

  • processor (#validate, #transform) (defaults to: nil)

    An optional processor object may be supplied.

Returns:



482
483
484
485
486
487
488
489
# File 'lib/addressable/template.rb', line 482

def expand(mapping, processor=nil)
  result = self.pattern.dup
  mapping = normalize_keys(mapping)
  result.gsub!( EXPRESSION ) do |capture|
    transform_capture(mapping, capture, processor)
  end
  return Addressable::URI.parse(result)
end

#extract(uri, processor = nil) ⇒ Hash, NilClass

Extracts a mapping from the URI using a URI Template pattern.

Examples:

class ExampleProcessor
  def self.restore(name, value)
    return value.gsub(/\+/, " ") if name == "query"
    return value
  end

  def self.match(name)
    return ".*?" if name == "first"
    return ".*"
  end
end

uri = Addressable::URI.parse(
  "http://example.com/search/an+example+search+query/"
)
Addressable::Template.new(
  "http://example.com/search/{query}/"
).extract(uri, ExampleProcessor)
#=> {"query" => "an example search query"}

uri = Addressable::URI.parse("http://example.com/a/b/c/")
Addressable::Template.new(
  "http://example.com/{first}/{second}/"
).extract(uri, ExampleProcessor)
#=> {"first" => "a", "second" => "b/c"}

uri = Addressable::URI.parse("http://example.com/a/b/c/")
Addressable::Template.new(
  "http://example.com/{first}/{-list|/|second}/"
).extract(uri)
#=> {"first" => "a", "second" => ["b", "c"]}

Parameters:

  • uri (Addressable::URI, #to_str)

    The URI to extract from.

  • processor (#restore, #match) (defaults to: nil)

    A template processor object may optionally be supplied.

    The object should respond to either the restore or match messages or both. The restore method should take two parameters: ‘[String] name` and `[String] value`. The restore method should reverse any transformations that have been performed on the value to ensure a valid URI. The match method should take a single parameter: `[String] name`. The match method should return a String containing a regular expression capture group for matching on that particular variable. The default value is `“.*?”`. The match method has no effect on multivariate operator expansions.

Returns:

  • (Hash, NilClass)

    The Hash mapping that was extracted from the URI, or nil if the URI didn’t match the template.



242
243
244
245
# File 'lib/addressable/template.rb', line 242

def extract(uri, processor=nil)
  match_data = self.match(uri, processor)
  return (match_data ? match_data.mapping : nil)
end

#inspectString

Returns a String representation of the Template object’s state.

Returns:

  • (String)

    The Template object’s state, as a String.



180
181
182
183
# File 'lib/addressable/template.rb', line 180

def inspect
  sprintf("#<%s:%#0x PATTERN:%s>",
    self.class.to_s, self.object_id, self.pattern)
end

#match(uri, processor = nil) ⇒ Hash, NilClass

Extracts match data from the URI using a URI Template pattern.

Examples:

class ExampleProcessor
  def self.restore(name, value)
    return value.gsub(/\+/, " ") if name == "query"
    return value
  end

  def self.match(name)
    return ".*?" if name == "first"
    return ".*"
  end
end

uri = Addressable::URI.parse(
  "http://example.com/search/an+example+search+query/"
)
match = Addressable::Template.new(
  "http://example.com/search/{query}/"
).match(uri, ExampleProcessor)
match.variables
#=> ["query"]
match.captures
#=> ["an example search query"]

uri = Addressable::URI.parse("http://example.com/a/b/c/")
match = Addressable::Template.new(
  "http://example.com/{first}/{+second}/"
).match(uri, ExampleProcessor)
match.variables
#=> ["first", "second"]
match.captures
#=> ["a", "b/c"]

uri = Addressable::URI.parse("http://example.com/a/b/c/")
match = Addressable::Template.new(
  "http://example.com/{first}{/second*}/"
).match(uri)
match.variables
#=> ["first", "second"]
match.captures
#=> ["a", ["b", "c"]]

Parameters:

  • uri (Addressable::URI, #to_str)

    The URI to extract from.

  • processor (#restore, #match) (defaults to: nil)

    A template processor object may optionally be supplied.

    The object should respond to either the restore or match messages or both. The restore method should take two parameters: ‘[String] name` and `[String] value`. The restore method should reverse any transformations that have been performed on the value to ensure a valid URI. The match method should take a single parameter: `[String] name`. The match method should return a String containing a regular expression capture group for matching on that particular variable. The default value is `“.*?”`. The match method has no effect on multivariate operator expansions.

Returns:

  • (Hash, NilClass)

    The Hash mapping that was extracted from the URI, or nil if the URI didn’t match the template.



313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
# File 'lib/addressable/template.rb', line 313

def match(uri, processor=nil)
  uri = Addressable::URI.parse(uri)
  mapping = {}

  # First, we need to process the pattern, and extract the values.
  expansions, expansion_regexp =
    parse_template_pattern(pattern, processor)
  unparsed_values = uri.to_str.scan(expansion_regexp).flatten

  if uri.to_str == pattern
    return Addressable::Template::MatchData.new(uri, self, mapping)
  elsif expansions.size > 0
    index = 0
    expansions.each do |expansion|
      _, operator, varlist = *expansion.match(EXPRESSION)
      varlist.split(',').each do |varspec|
        _, name, modifier = *varspec.match(VARSPEC)
        case operator
        when nil, '+', '#', '/', '.'
          unparsed_value = unparsed_values[index]
          name = varspec[VARSPEC, 1]
          value = unparsed_value
          value = value.split(JOINERS[operator]) if value && modifier == '*'
        when ';', '?', '&'
          if modifier == '*'
            value = unparsed_values[index].split(JOINERS[operator])
            value = value.inject({}) do |acc, v|
              key, val = v.split('=')
              val = "" if val.nil?
              acc[key] = val
              acc
            end
          else
            if (unparsed_values[index])
              name, value = unparsed_values[index].split('=')
              value = "" if value.nil?
            end
          end
        end
        if processor != nil && processor.respond_to?(:restore)
          value = processor.restore(name, value)
        end
        if processor == nil
          if value.is_a?(Hash)
            value = value.inject({}){|acc, (k, v)|
              acc[Addressable::URI.unencode_component(k)] =
                Addressable::URI.unencode_component(v)
              acc
            }
          elsif value.is_a?(Array)
            value = value.map{|v| Addressable::URI.unencode_component(v) }
          else
            value = Addressable::URI.unencode_component(value)
          end
        end
        if mapping[name] == nil || mapping[name] == value
          mapping[name] = value
        else
          return nil
        end
        index = index + 1
      end
    end
    return Addressable::Template::MatchData.new(uri, self, mapping)
  else
    return nil
  end
end

#partial_expand(mapping, processor = nil) ⇒ Addressable::Template

Expands a URI template into another URI template.

The object should respond to either the validate or transform messages or both. Both the validate and transform methods should take two parameters: name and value. The validate method should return true or false; true if the value of the variable is valid, false otherwise. An InvalidTemplateValueError exception will be raised if the value is invalid. The transform method should return the transformed variable value as a String. If a transform method is used, the value will not be percent encoded automatically. Unicode normalization will be performed both before and after sending the value to the transform method.

Examples:

Addressable::Template.new(
  "http://example.com/{one}/{two}/"
).partial_expand({"one" => "1"}).pattern
#=> "http://example.com/1/{two}/"

Addressable::Template.new(
  "http://example.com/{?one,two}/"
).partial_expand({"one" => "1"}).pattern
#=> "http://example.com/?one=1{&two}/"

Addressable::Template.new(
  "http://example.com/{?one,two,three}/"
).partial_expand({"one" => "1", "three" => 3}).pattern
#=> "http://example.com/?one=1{&two}&three=3"

Parameters:

  • mapping (Hash)

    The mapping that corresponds to the pattern.

  • processor (#validate, #transform) (defaults to: nil)

    An optional processor object may be supplied.

Returns:



418
419
420
421
422
423
424
# File 'lib/addressable/template.rb', line 418

def partial_expand(mapping, processor=nil)
  result = self.pattern.dup
  result.gsub!( EXPRESSION ) do |capture|
    transform_partial_capture(mapping, capture, processor)
  end
  return Addressable::Template.new(result)
end

#variable_defaultsHash

Returns a mapping of variables to their default values specified in the template. Variables without defaults are not returned.

Returns:

  • (Hash)

    Mapping of template variables to their defaults



508
509
510
511
# File 'lib/addressable/template.rb', line 508

def variable_defaults
  @variable_defaults ||=
    Hash[*ordered_variable_defaults.reject { |k, v| v.nil? }.flatten]
end

#variablesArray Also known as: keys

Returns an Array of variables used within the template pattern. The variables are listed in the Array in the order they appear within the pattern. Multiple occurrences of a variable within a pattern are not represented in this Array.

Returns:

  • (Array)

    The variables present in the template’s pattern.



498
499
500
# File 'lib/addressable/template.rb', line 498

def variables
  @variables ||= ordered_variable_defaults.map { |var, val| var }.uniq
end