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

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

- (Addressable::Template) initialize(pattern)

Creates a new Addressable::Template object.



233
234
235
236
237
238
# File 'lib/addressable/template.rb', line 233

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

- (String) pattern (readonly)



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

def pattern
  @pattern
end

Instance Method Details

- (TrueClass, FalseClass) ==(template) Also known as: eql?

Returns true if the Template objects are equal. This method does NOT normalize either Template before doing the comparison.



262
263
264
265
# File 'lib/addressable/template.rb', line 262

def ==(template)
  return false unless template.kind_of?(Template)
  return self.pattern == template.pattern
end

- (Addressable::URI) expand(mapping, processor = nil)

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


575
576
577
578
579
580
581
582
# File 'lib/addressable/template.rb', line 575

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

- (Hash, NilClass) extract(uri, processor = nil)

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"]}


330
331
332
333
# File 'lib/addressable/template.rb', line 330

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

- (String) inspect

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



248
249
250
251
# File 'lib/addressable/template.rb', line 248

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

- (Hash, NilClass) match(uri, processor = nil)

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"]]


401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
# File 'lib/addressable/template.rb', line 401

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)

  return nil unless uri.to_str.match(expansion_regexp)
  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)
        mapping[name] ||= nil
        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 == '*'
            if unparsed_values[index]
              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
            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.has_key?(name) || mapping[name].nil?
          # Doesn't exist, set to value (even if value is nil)
          mapping[name] = value
        end
        index = index + 1
      end
    end
    return Addressable::Template::MatchData.new(uri, self, mapping)
  else
    return nil
  end
end

- (Addressable::Template) partial_expand(mapping, processor = nil)

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"


510
511
512
513
514
515
516
517
# File 'lib/addressable/template.rb', line 510

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

- (Hash) variable_defaults

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



601
602
603
604
# File 'lib/addressable/template.rb', line 601

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

- (Array) variables 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.



591
592
593
# File 'lib/addressable/template.rb', line 591

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