URI::Template - Object for handling URI templates


URI-Template documentation Contained in the URI-Template distribution.

Index


Code Index:

NAME

Top

URI::Template - Object for handling URI templates

SYNOPSIS

Top

    use URI::Template;
    my $template = URI::Template->new( 'http://example.com/{x}' );
    my $uri      = $template->process( x => 'y' );
    # uri is a URI object with value 'http://example.com/y'

DESCRIPTION

Top

This is an initial attempt to provide a wrapper around URI templates as described at http://www.ietf.org/internet-drafts/draft-gregorio-uritemplate-03.txt

INSTALLATION

Top

    perl Makefile.PL
    make
    make test
    make install

METHODS

Top

new( $template )

Creates a new URI::Template instance with the template passed in as the first parameter.

template

This method returns the original template string.

variables

Returns an array of unique variable names found in the template. NB: they are returned in random order.

expansions

This method returns an list of expansions found in the template. Currently, these are just coderefs. In the future, they will be more interesting.

process( \%vars )

Given a list of key-value pairs or an array ref of values (for positional substitution), it will URI escape the values and substitute them in to the template. Returns a URI object.

process_to_string( \%vars )

Processes input like the process method, but doesn't inflate the result to a URI object.

AUTHOR

Top

Brian Cassidy <bricas@cpan.org>

Ricardo SIGNES <rjbs@cpan.org>

COPYRIGHT AND LICENSE

Top


URI-Template documentation Contained in the URI-Template distribution.
package URI::Template;

use strict;
use warnings;

our $VERSION = '0.15';

use URI;
use URI::Escape qw(uri_escape_utf8);
use Unicode::Normalize;
use overload '""' => \&template;

sub new {
    my $class = shift;
    my $templ = shift || die 'No template provided';
    my $self  = bless { template => $templ, _vars => {} } => $class;
    
    $self->_study;

    return $self;
}

sub _study {
    my ($self) = @_;
    my @hunks = grep { defined && length } split /(\{.+?\})/, $self->template;
    for (@hunks) {
      next unless /^\{(.+?)\}$/;
      $_ = $self->_compile_expansion($1);
    }
    $self->{studied} = \@hunks;
}

sub _op_gen_join {
  my ($self, $exp) = @_;

  return sub {
    my ($var) = @_;

    my @pairs;
    for my $keypair (@{ $exp->{vars} }) {
      my $key = $keypair->[ 0 ];
      my $val = $keypair->[ 1 ]->( $var );
      next if !exists $var->{$key} && $val eq '';
      Carp::croak "invalid variable ($key) supplied to join operator"
        if ref $var->{$key};

      push @pairs, $key . '=' . $val;
    }
    return join $exp->{arg}, @pairs;
  };
}

sub _op_gen_opt {
    my ($self, $exp) = @_;

    Carp::croak "-opt accepts exactly one argument" if @{ $exp->{vars} } != 1;

    my $value   = $exp->{arg};
    my $varname = $exp->{vars}->[0]->[0];

    return sub {
      my ($var) = @_;
      return '' unless exists $var->{$varname} and defined $var->{$varname};
      return '' if ref $var->{$varname} and not @{ $var->{$varname} };

      return $value;
    };
}

sub _op_gen_neg {
    my ($self, $exp) = @_;

    Carp::croak "-neg accepts exactly one argument" if @{ $exp->{vars} } != 1;

    my $value   = $exp->{arg};
    my $varname = $exp->{vars}->[0]->[0];

    return sub {
      my ($var) = @_;
      return $value unless exists $var->{$varname} && defined $var->{$varname};
      return $value if ref $var->{$varname} && !  @{ $var->{$varname} };

      return '';
    };
}

sub _op_gen_prefix {
    my ($self, $exp) = @_;

    Carp::croak "-prefix accepts exactly one argument" if @{$exp->{vars}} != 1;

    my $prefix = $exp->{arg};
    my $name   = $exp->{vars}->[0]->[0];

    return sub {
      my ($var) = @_;
      return '' unless exists $var->{$name} && defined $var->{$name};
      my $array = ref $var->{$name} ? $var->{$name} : [ $var->{$name} ];
      return '' unless @$array;

      return join '', map { "$prefix$_" } @$array;
    };
}

sub _op_gen_suffix {
    my ($self, $exp) = @_;

    Carp::croak "-suffix accepts exactly one argument" if @{$exp->{vars}} != 1;

    my $suffix = $exp->{arg};
    my $name   = $exp->{vars}->[0]->[0];

    return sub {
      my ($var) = @_;
      return '' unless exists $var->{$name} && defined $var->{$name};
      my $array = ref $var->{$name} ? $var->{$name} : [ $var->{$name} ];
      return '' unless @$array;

      return join '', map { "$_$suffix" } @$array;
    };
}

sub _op_gen_list {
    my ($self, $exp) = @_;

    Carp::croak "-list accepts exactly one argument" if @{$exp->{vars}} != 1;

    my $joiner = $exp->{arg};
    my $name   = $exp->{vars}->[0]->[0];

    return sub {
      my ($var) = @_;
      return '' unless exists $var->{$name} && defined $var->{$name};
      Carp::croak "variable ($name) used in -list must be an array reference"
        unless ref $var->{$name};

      return '' unless my @array = @{ $var->{$name} };

      return join $joiner, @array;
    };
}

# not op_gen_* as it is not an op from the spec
sub _op_fill_var {
    my( $self, $exp ) = @_;
    my( $var, $default ) = split( /=/, $exp, 2 );
    $default = '' if !defined $default;

    return $var, sub {
        return exists $_[0]->{$var} ? $_[0]->{$var} : $default;
    };
}

sub _compile_expansion {
    my ($self, $str) = @_;

    if ($str =~ /\A-([a-z]+)\|(.*?)\|(.+)\z/) {
      my $exp = { op => $1, arg => $2, vars => [ map { [ $self->_op_fill_var( $_ ) ] } split /,/, $3 ] };
      $self->{ _vars }->{ $_->[ 0 ] }++ for @{ $exp->{ vars } };
      Carp::croak "unknown expansion operator $exp->{op} in $str"
        unless my $code = $self->can("_op_gen_$exp->{op}");

      return $self->$code($exp);
    }

    # remove "optional" flag (for opensearch compatibility)
    $str =~ s{\?$}{};

    my @var = $self->_op_fill_var( $str );
    $self->{ _vars }->{ $var[ 0 ] }++;
    return $var[ 1 ];
}

sub template {
    return $_[ 0 ]->{ template };
}

sub variables {
    return keys %{ $_[ 0 ]->{ _vars } };
}

sub expansions {
    my $self = shift;
    return grep { ref } @{ $self->{studied} };
}

sub process {
    my $self = shift;
    return URI->new( $self->process_to_string( @_ ) );
}

sub process_to_string {
    my $self = shift;
    my $arg  = @_ == 1 ? $_[0] : { @_ };

    my %data;
    for my $key (keys %$arg) {
      $data{ $key } = ref $arg->{$key}
                    ? [ map { uri_escape_utf8(NFKC($_)) } @{ $arg->{$key} } ]
                    : uri_escape_utf8(NFKC($arg->{$key}));
    }

    my $str = '';

    for my $hunk (@{ $self->{studied} }) {
        if (! ref $hunk) { $str .= $hunk; next; }

        $str .= $hunk->(\%data);
    }

    return $str;
}

1;