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,
28 $templ = '' unless defined $templ;
29 my $self = bless { template => $templ, _vars => {} } => $class;
37 my ( $val, $safe ) = @_;
40 # try to mirror python's urllib quote
41 my $unsafe = '^A-Za-z0-9\-\._' . $safe;
42 return URI::Escape::uri_escape_utf8( Unicode::Normalize::NFKC( $val ),
47 my ( $var, $value, $exp ) = @_;
48 my $safe = $exp->{ safe };
50 if ( ref $value eq 'ARRAY' ) {
51 return join( ',', map { _quote( $_, $safe ) } @$value );
53 elsif ( ref $value eq 'HASH' ) {
58 . ( $var->{ explode } ? '=' : ',' )
59 . _quote( $value->{ $_ }, $safe )
63 elsif ( defined $value ) {
65 substr( $value, 0, $var->{ prefix } || length( $value ) ),
73 my ( $var, $value, $exp ) = @_;
74 my $safe = $exp->{ safe };
75 my $join = $exp->{ op };
76 $join = '&' if $exp->{ op } eq '?';
78 if ( ref $value eq 'ARRAY' ) {
79 if ( $var->{ explode } ) {
81 map { $var->{ name } . '=' . _quote( $_, $safe ) } @$value );
84 return $var->{ name } . '='
85 . join( ',', map { _quote( $_, $safe ) } @$value );
88 elsif ( ref $value eq 'HASH' ) {
89 if ( $var->{ explode } ) {
93 _quote( $_, $safe ) . '='
94 . _quote( $value->{ $_ }, $safe )
99 return $var->{ name } . '=' . join(
102 _quote( $_, $safe ) . ','
103 . _quote( $value->{ $_ }, $safe )
108 elsif ( defined $value ) {
109 return $var->{ name } unless length( $value );
113 substr( $value, 0, $var->{ prefix } || length( $value ) ),
120 sub _tostring_query {
121 my ( $var, $value, $exp ) = @_;
122 my $safe = $exp->{ safe };
123 my $join = $exp->{ op };
124 $join = '&' if $exp->{ op } =~ /[?&]/;
126 if ( ref $value eq 'ARRAY' ) {
128 if ( $var->{ explode } ) {
130 map { $var->{ name } . '=' . _quote( $_, $safe ) } @$value );
133 return $var->{ name } . '='
134 . join( ',', map { _quote( $_, $safe ) } @$value );
137 elsif ( ref $value eq 'HASH' ) {
138 return if !keys %$value;
139 if ( $var->{ explode } ) {
143 _quote( $_, $safe ) . '='
144 . _quote( $value->{ $_ }, $safe )
149 return $var->{ name } . '=' . join(
152 _quote( $_, $safe ) . ','
153 . _quote( $value->{ $_ }, $safe )
158 elsif ( defined $value ) {
159 return $var->{ name } . '=' unless length( $value );
163 substr( $value, 0, $var->{ prefix } || length( $value ) ),
169 my ( $var, $value, $exp ) = @_;
170 my $safe = $exp->{ safe };
171 my $join = $exp->{ op };
173 if ( ref $value eq 'ARRAY' ) {
174 return unless @$value;
176 ( $var->{ explode } ? $join : ',' ),
177 map { _quote( $_, $safe ) } @$value
180 elsif ( ref $value eq 'HASH' ) {
182 ( $var->{ explode } ? $join : ',' ),
185 . ( $var->{ explode } ? '=' : ',' )
186 . _quote( $value->{ $_ }, $safe )
190 elsif ( defined $value ) {
192 substr( $value, 0, $var->{ prefix } || length( $value ) ),
201 my @hunks = grep { defined && length } split /(\{.+?\})/, $self->template;
203 next unless /^\{(.+?)\}$/;
204 $_ = $self->_compile_expansion( $1 );
206 $self->{ studied } = \@hunks;
209 sub _compile_expansion {
210 my ( $self, $str ) = @_;
212 my %exp = ( op => '', vars => [], str => $str );
213 if ( $str =~ /^([+#.\/;?&|!\@])(.+)/ ) {
218 $exp{ safe } = $RESERVED if $exp{ op } =~ m{[+#]};
220 for my $varspec ( split( ',', delete $exp{ str } ) ) {
221 my %var = ( name => $varspec );
222 if ( $varspec =~ /=/ ) {
223 @var{ 'name', 'default' } = split( /=/, $varspec, 2 );
225 if ( $var{ name } =~ s{\*$}{} ) {
228 elsif ( $var{ name } =~ /:/ ) {
229 @var{ 'name', 'prefix' } = split( /:/, $var{ name }, 2 );
230 if ( $var{ prefix } =~ m{[^0-9]} ) {
231 die 'Non-numeric prefix specified';
235 # remove "optional" flag (for opensearch compatibility)
236 $var{ name } =~ s{\?$}{};
237 $self->{ _vars }->{ $var{ name } }++;
239 push @{ $exp{ vars } }, \%var;
242 my $join = $exp{ op };
243 my $start = $exp{ op };
245 if ( $exp{ op } eq '+' ) {
249 elsif ( $exp{ op } eq '#' ) {
252 elsif ( $exp{ op } eq '?' ) {
255 elsif ( $exp{ op } eq '&' ) {
258 elsif ( $exp{ op } eq '' ) {
262 if ( !exists $TOSTRING{ $exp{ op } } ) {
263 die 'Invalid operation "' . $exp{ op } . '"';
267 my $variables = shift;
270 for my $var ( @{ $exp{ vars } } ) {
272 if ( exists $variables->{ $var->{ name } } ) {
273 $value = $variables->{ $var->{ name } };
275 $value = $var->{ default } if !defined $value;
277 next unless defined $value;
279 my $expand = $TOSTRING{ $exp{ op } }->( $var, $value, \%exp );
281 push @return, $expand if defined $expand;
284 return $start . join( $join, @return ) if @return;
294 if ( defined $templ && $templ ne $self->{ template } ) {
295 $self->{ template } = $templ;
296 $self->{ _vars } = {};
301 return $self->{ template };
305 return keys %{ $_[ 0 ]->{ _vars } };
310 return grep { ref } @{ $self->{ studied } };
315 return URI->new( $self->process_to_string( @_ ) );
318 sub process_to_string {
320 my $arg = @_ == 1 ? $_[ 0 ] : { @_ };
323 for my $hunk ( @{ $self->{ studied } } ) {
324 if ( !ref $hunk ) { $str .= $hunk; next; }
326 $str .= $hunk->( $arg );
338 URI::Template - Object for handling URI templates (RFC 6570)
344 my $template = URI::Template->new( 'http://example.com/{x}' );
345 my $uri = $template->process( x => 'y' );
349 my $template = URI::Template->new();
350 $template->template( 'http://example.com/{x}' );
351 my $uri = $template->process( x => 'y' );
353 # uri is a URI object with value 'http://example.com/y'
357 This module provides a wrapper around URI templates as described in RFC 6570:
358 L<< http://tools.ietf.org/html/rfc6570 >>.
369 =head2 new( $template )
371 Creates a new L<URI::Template> instance with the template passed in
372 as the first parameter (optional).
374 =head2 template( $template )
376 This method returns the original template string. If provided, it will also set and parse a
381 Returns an array of unique variable names found in the template. NB: they are returned in random order.
385 This method returns an list of expansions found in the template. Currently,
386 these are just coderefs. In the future, they will be more interesting.
388 =head2 process( \%vars )
390 Given a list of key-value pairs or an array ref of values (for
391 positional substitution), it will URI escape the values and
392 substitute them in to the template. Returns a URI object.
394 =head2 process_to_string( \%vars )
396 Processes input like the C<process> method, but doesn't inflate the result to a
403 =item * Brian Cassidy E<lt>bricas@cpan.orgE<gt>
405 =item * Ricardo SIGNES E<lt>rjbs@cpan.orgE<gt>
409 =head1 COPYRIGHT AND LICENSE
411 Copyright 2007-2015 by Brian Cassidy
413 This library is free software; you can redistribute it and/or modify
414 it under the same terms as Perl itself.