10 use Unicode::Normalize ();
11 use overload '""' => \&template;
13 my $RESERVED = q(:/?#\[\]\@!\$\&'\(\)\*\+,;=);
18 ';' => \&_tostring_semi,
19 '?' => \&_tostring_query,
20 '&' => \&_tostring_query,
21 '/' => \&_tostring_path,
22 '.' => \&_tostring_path,
27 my $templ = shift || die 'No template provided';
28 my $self = bless { template => $templ, _vars => {} } => $class;
36 my ( $val, $safe ) = @_;
39 # try to mirror python's urllib quote
40 my $unsafe = '^A-Za-z0-9\-\._' . $safe;
41 return URI::Escape::uri_escape_utf8( Unicode::Normalize::NFKC( $val ),
46 my ( $var, $value, $exp ) = @_;
47 my $safe = $exp->{ safe };
49 if ( ref $value eq 'ARRAY' ) {
50 return join( ',', map { _quote( $_, $safe ) } @$value );
52 elsif ( ref $value eq 'HASH' ) {
57 . ( $var->{ explode } ? '=' : ',' )
58 . _quote( $value->{ $_ }, $safe )
62 elsif ( defined $value ) {
64 substr( $value, 0, $var->{ prefix } || length( $value ) ),
72 my ( $var, $value, $exp ) = @_;
73 my $safe = $exp->{ safe };
74 my $join = $exp->{ op };
75 $join = '&' if $exp->{ op } eq '?';
77 if ( ref $value eq 'ARRAY' ) {
78 if ( $var->{ explode } ) {
80 map { $var->{ name } . '=' . _quote( $_, $safe ) } @$value );
83 return $var->{ name } . '='
84 . join( ',', map { _quote( $_, $safe ) } @$value );
87 elsif ( ref $value eq 'HASH' ) {
88 if ( $var->{ explode } ) {
92 _quote( $_, $safe ) . '='
93 . _quote( $value->{ $_ }, $safe )
98 return $var->{ name } . '=' . join(
101 _quote( $_, $safe ) . ','
102 . _quote( $value->{ $_ }, $safe )
107 elsif ( defined $value ) {
108 return $var->{ name } unless length( $value );
112 substr( $value, 0, $var->{ prefix } || length( $value ) ),
119 sub _tostring_query {
120 my ( $var, $value, $exp ) = @_;
121 my $safe = $exp->{ safe };
122 my $join = $exp->{ op };
123 $join = '&' if $exp->{ op } =~ /[?&]/;
125 if ( ref $value eq 'ARRAY' ) {
127 return if $var->{ explode };
128 return $var->{ name } . '=';
130 if ( $var->{ explode } ) {
132 map { $var->{ name } . '=' . _quote( $_, $safe ) } @$value );
135 return $var->{ name } . '='
136 . join( ',', map { _quote( $_, $safe ) } @$value );
139 elsif ( ref $value eq 'HASH' ) {
140 if( !keys %$value ) {
141 return if $var->{ explode };
142 return $var->{ name } . '=';
144 if ( $var->{ explode } ) {
148 _quote( $_, $safe ) . '='
149 . _quote( $value->{ $_ }, $safe )
154 return $var->{ name } . '=' . join(
157 _quote( $_, $safe ) . ','
158 . _quote( $value->{ $_ }, $safe )
163 elsif ( defined $value ) {
164 return $var->{ name } . '=' unless length( $value );
168 substr( $value, 0, $var->{ prefix } || length( $value ) ),
174 my ( $var, $value, $exp ) = @_;
175 my $safe = $exp->{ safe };
176 my $join = $exp->{ op };
178 if ( ref $value eq 'ARRAY' ) {
179 return unless @$value;
181 ( $var->{ explode } ? $join : ',' ),
182 map { _quote( $_, $safe ) } @$value
185 elsif ( ref $value eq 'HASH' ) {
187 ( $var->{ explode } ? $join : ',' ),
190 . ( $var->{ explode } ? '=' : ',' )
191 . _quote( $value->{ $_ }, $safe )
195 elsif ( defined $value ) {
197 substr( $value, 0, $var->{ prefix } || length( $value ) ),
206 my @hunks = grep { defined && length } split /(\{.+?\})/, $self->template;
208 next unless /^\{(.+?)\}$/;
209 $_ = $self->_compile_expansion( $1 );
211 $self->{ studied } = \@hunks;
214 sub _compile_expansion {
215 my ( $self, $str ) = @_;
217 my %exp = ( op => '', vars => [], str => $str );
218 if ( $str =~ /^([+#.\/;?&|!\@])(.+)/ ) {
223 $exp{ safe } = $RESERVED if $exp{ op } =~ m{[+#]};
225 for my $varspec ( split( ',', delete $exp{ str } ) ) {
226 my %var = ( name => $varspec );
227 if ( $varspec =~ /=/ ) {
228 @var{ 'name', 'default' } = split( /=/, $varspec, 2 );
230 if ( $var{ name } =~ s{\*$}{} ) {
233 elsif ( $var{ name } =~ /:/ ) {
234 @var{ 'name', 'prefix' } = split( /:/, $var{ name }, 2 );
235 if ( $var{ prefix } =~ m{[^0-9]} ) {
236 die 'Non-numeric prefix specified';
240 # remove "optional" flag (for opensearch compatibility)
241 $var{ name } =~ s{\?$}{};
242 $self->{ _vars }->{ $var{ name } }++;
244 push @{ $exp{ vars } }, \%var;
247 my $join = $exp{ op };
248 my $start = $exp{ op };
250 if ( $exp{ op } eq '+' ) {
254 elsif ( $exp{ op } eq '#' ) {
257 elsif ( $exp{ op } eq '?' ) {
260 elsif ( $exp{ op } eq '&' ) {
263 elsif ( $exp{ op } eq '' ) {
267 if ( !exists $TOSTRING{ $exp{ op } } ) {
268 die 'Invalid operation "' . $exp{ op } . '"';
272 my $variables = shift;
275 for my $var ( @{ $exp{ vars } } ) {
277 if ( exists $variables->{ $var->{ name } } ) {
278 $value = $variables->{ $var->{ name } };
280 $value = $var->{ default } if !defined $value;
282 next unless defined $value;
284 my $expand = $TOSTRING{ $exp{ op } }->( $var, $value, \%exp );
286 push @return, $expand if defined $expand;
289 return $start . join( $join, @return ) if @return;
295 return $_[ 0 ]->{ template };
299 return keys %{ $_[ 0 ]->{ _vars } };
304 return grep { ref } @{ $self->{ studied } };
309 return URI->new( $self->process_to_string( @_ ) );
312 sub process_to_string {
314 my $arg = @_ == 1 ? $_[ 0 ] : { @_ };
317 for my $hunk ( @{ $self->{ studied } } ) {
318 if ( !ref $hunk ) { $str .= $hunk; next; }
320 $str .= $hunk->( $arg );
332 URI::Template - Object for handling URI templates (RFC 6570)
337 my $template = URI::Template->new( 'http://example.com/{x}' );
338 my $uri = $template->process( x => 'y' );
339 # uri is a URI object with value 'http://example.com/y'
343 This module provides a wrapper around URI templates as described in RFC 6570:
344 http://tools.ietf.org/html/rfc6570
355 =head2 new( $template )
357 Creates a new L<URI::Template> instance with the template passed in
358 as the first parameter.
362 This method returns the original template string.
366 Returns an array of unique variable names found in the template. NB: they are returned in random order.
370 This method returns an list of expansions found in the template. Currently,
371 these are just coderefs. In the future, they will be more interesting.
373 =head2 process( \%vars )
375 Given a list of key-value pairs or an array ref of values (for
376 positional substitution), it will URI escape the values and
377 substitute them in to the template. Returns a URI object.
379 =head2 process_to_string( \%vars )
381 Processes input like the C<process> method, but doesn't inflate the result to a
388 =item * Brian Cassidy E<lt>bricas@cpan.orgE<gt>
390 =item * Ricardo SIGNES E<lt>rjbs@cpan.orgE<gt>
394 =head1 COPYRIGHT AND LICENSE
396 Copyright 2007-2013 by Brian Cassidy
398 This library is free software; you can redistribute it and/or modify
399 it under the same terms as Perl itself.