File Coverage

blib/lib/URI/Query.pm
Criterion Covered Total %
statement 139 143 97.2
branch 40 54 74.0
condition 20 24 83.3
subroutine 26 26 100.0
pod 13 14 92.8
total 238 261 91.1


line stmt bran cond sub pod time code
1             #
2             # Class providing URI query string manipulation
3             #
4              
5             package URI::Query;
6              
7 9     9   44441 use 5.00503;
  9         22  
8 9     9   30 use strict;
  9         9  
  9         157  
9 9     9   3366 use parent 'Clone';
  9         2237  
  9         35  
10              
11 9     9   24234 use URI::Escape qw(uri_escape_utf8 uri_unescape);
  9         8616  
  9         505  
12 9     9   36 use Carp;
  9         14  
  9         845  
13              
14             use overload
15             '""' => \&stringify,
16 2     2   245 'eq' => sub { $_[0]->stringify eq $_[1]->stringify },
17 9     9   2850 'ne' => sub { $_[0]->stringify ne $_[1]->stringify };
  9     2   2339  
  9         89  
  2         149  
18              
19 9     9   487 use vars q($VERSION);
  9         10  
  9         11358  
20             $VERSION = '0.16';
21              
22             # -------------------------------------------------------------------------
23             # Remove all occurrences of the given parameters
24             sub strip
25             {
26 7     7 1 33 my $self = shift;
27 7         13 foreach (@_) {
28 10 100       51 delete $self->{qq}->{$_} and $self->{changed}++;
29             }
30 7         26 $self;
31             }
32              
33             # Remove all parameters except those given
34             sub strip_except
35             {
36 3     3 1 4 my $self = shift;
37 3         6 my %keep = map { $_ => 1 } @_;
  9         16  
38 3         5 foreach (keys %{$self->{qq}}) {
  3         10  
39 13 100       23 next if $keep{$_};
40 5 50       12 delete $self->{qq}->{$_} and $self->{changed}++;
41             }
42 3         7 $self;
43             }
44              
45             # Remove all empty/undefined parameters
46             sub strip_null
47             {
48 2     2 1 4 my $self = shift;
49 2         2 foreach (keys %{$self->{qq}}) {
  2         8  
50 9 100       7 next if @{$self->{qq}->{$_}};
  9         23  
51 3 50       9 delete $self->{qq}->{$_} and $self->{changed}++;
52             }
53 2         6 $self;
54             }
55              
56             # Remove all parameters matching $re
57             sub strip_like
58             {
59 3     3 1 3 my $self = shift;
60 3 50       9 my $re = shift or croak "Missing regex param to strip_like";
61 3 50 33     17 croak "Invalid param '$re' to strip_like - must be regex" if ! ref $re || ref $re ne 'Regexp';
62 3 50       5 croak "Too many params to strip_like - only one permitted" if @_;
63              
64 3         3 foreach (keys %{$self->{qq}}) {
  3         9  
65 10 100       30 next if $_ !~ $re;
66 3 50       9 delete $self->{qq}->{$_} and $self->{changed}++;
67             }
68              
69 3         13 $self;
70             }
71              
72             # Replace all occurrences of the given parameters
73             sub replace
74             {
75 2     2 1 14 my $self = shift;
76 2         5 my %arg = @_;
77 2         5 for my $key (keys %arg) {
78 5         7 $self->{qq}->{$key} = [];
79 5 100       10 if (ref $arg{$key} eq 'ARRAY') {
80 1         1 push @{$self->{qq}->{$key}}, $_ foreach @{$arg{$key}};
  1         3  
  3         5  
81             }
82             else {
83 4         3 push @{$self->{qq}->{$key}}, $arg{$key};
  4         7  
84             }
85 5         5 $self->{changed}++;
86             }
87 2         8 $self;
88             }
89              
90             # Return the stringified qq hash
91             sub stringify
92             {
93 91     91 1 2399 my $self = shift;
94 91   100     367 my $sep = shift || $self->{sep} || '&';
95 91         112 my @out = ();
96 91         72 for my $key (sort keys %{$self->{qq}}) {
  91         311  
97 262         2330 for my $value (@{$self->{qq}->{$key}}) {
  262         321  
98 382         2149 push @out, sprintf("%s=%s", uri_escape_utf8($key), uri_escape_utf8($value));
99             }
100             }
101 91         1809 join $sep, @out;
102             }
103              
104             # Return the stringified qq hash with a leading '?'
105             sub qstringify
106             {
107 3     3 1 3 my $self = shift;
108 3         6 return '?' . $self->stringify(@_);
109             }
110              
111             sub revert
112             {
113 4     4 1 398 my $self = shift;
114             # Revert qq to the qq_orig hashref
115 4         7 $self->{qq} = $self->_deepcopy($self->{qq_orig});
116 4         11 $self->{changed} = 0;
117 4         5 $self;
118             }
119              
120             sub has_changed {
121 9     9 1 8 my $self = shift;
122 9 100       29 $self->{changed} > 0 ? 1 : 0;
123             }
124              
125             # -------------------------------------------------------------------------
126             # Convenience methods
127              
128             # Return the current qq hash(ref) with one-elt arrays flattened
129             sub hash
130             {
131 4     4 1 4602 my $self = shift;
132 4         11 my %qq = %{$self->{qq}};
  4         15  
133             # Flatten one element arrays
134 4         26 for (sort keys %qq) {
135 10 100       8 $qq{$_} = $qq{$_}->[0] if @{$qq{$_}} == 1;
  10         26  
136             }
137 4 50       16 return wantarray ? %qq : \%qq;
138             }
139              
140             # Return the current qq hash(ref) with all elements as arrayrefs
141             sub hash_arrayref
142             {
143 1     1 1 9753 my $self = shift;
144 1         1 my %qq = %{$self->{qq}};
  1         4  
145             # (Don't flatten one element arrays)
146 1 50       4 return wantarray ? %qq : \%qq;
147             }
148              
149             # Return the current query as a string of html hidden input tags
150             sub hidden
151             {
152 1     1 1 1778 my $self = shift;
153 1         1 my $str = '';
154 1         1 for my $key (sort keys %{$self->{qq}}) {
  1         5  
155 4         4 for my $value (@{$self->{qq}->{$key}}) {
  4         5  
156 6         8 $str .= qq(\n);
157             }
158             }
159 1         2 return $str;
160             }
161              
162             # -------------------------------------------------------------------------
163             # Set the output separator to use by default
164             sub separator
165             {
166 1     1 1 331 my $self = shift;
167 1         2 $self->{sep} = shift;
168             }
169              
170             # Deep copy routine, originally swiped from a Randal Schwartz column
171             sub _deepcopy
172             {
173 231     231   205 my ($self, $this) = @_;
174 231 100       343 if (! ref $this) {
    100          
    50          
    0          
    0          
175 116         277 return $this;
176             } elsif (ref $this eq "ARRAY") {
177 85         125 return [map $self->_deepcopy($_), @$this];
178             } elsif (ref $this eq "HASH") {
179 30         69 return {map { $_ => $self->_deepcopy($this->{$_}) } keys %$this};
  85         125  
180             } elsif (ref $this eq "CODE") {
181 0         0 return $this;
182             } elsif (sprintf $this) {
183             # Object! As a last resort, try copying the stringification value
184 0         0 return sprintf $this;
185             } else {
186 0         0 die "what type is $_? (" . ref($this) . ")";
187             }
188             }
189              
190             # Parse query string, storing as hash (qq) of key => arrayref pairs
191             sub _parse_qs
192             {
193 15     15   15 my $self = shift;
194 15         17 my $qs = shift;
195 15         77 for (split /[&;]/, $qs) {
196 70         93 my ($key, $value) = map { uri_unescape($_) } split /=/, $_, 2;
  140         416  
197 70   100     827 $self->{qq}->{$key} ||= [];
198 70 100 66     209 push @{$self->{qq}->{$key}}, $value if defined $value && $value ne '';
  69         124  
199             }
200             $self
201 15         24 }
202              
203             # Process arrayref arguments into hash (qq) of key => arrayref pairs
204             sub _init_from_arrayref
205             {
206 6     6   7 my ($self, $arrayref) = @_;
207 6         15 while (@$arrayref) {
208 24         123 my $key = shift @$arrayref;
209 24         23 my $value = shift @$arrayref;
210 24         45 my $key_unesc = uri_unescape($key);
211              
212 24   100     148 $self->{qq}->{$key_unesc} ||= [];
213 24 100 100     72 if (defined $value && $value ne '') {
214 22         20 my @values;
215 22 100       30 if (! ref $value) {
    50          
216 20         30 @values = split "\0", $value;
217             }
218             elsif (ref $value eq 'ARRAY') {
219 2         4 @values = @$value;
220             }
221             else {
222 0         0 die "Invalid value found: $value. Not string or arrayref!";
223             }
224 22         14 push @{$self->{qq}->{$key_unesc}}, map { uri_unescape($_) } @values;
  22         31  
  26         48  
225             }
226             }
227             }
228              
229             # Constructor - either new($qs) where $qs is a scalar query string or a
230             # a hashref of key => value pairs, or new(key => val, key => val);
231             # In the array form, keys can repeat, and/or values can be arrayrefs.
232             sub new
233             {
234 26     26 0 4704 my $class = shift;
235 26         71 my $self = bless { qq => {} }, $class;
236 26 100 100     210 if (@_ == 1 && ! ref $_[0] && $_[0]) {
    100 66        
    100 100        
237 15         33 $self->_parse_qs($_[0]);
238             }
239             elsif (@_ == 1 && ref $_[0] eq 'HASH') {
240 3         5 $self->_init_from_arrayref([ %{$_[0]} ]);
  3         11  
241             }
242             elsif (scalar(@_) % 2 == 0) {
243 3         11 $self->_init_from_arrayref(\@_);
244             }
245              
246             # Clone the qq hashref to allow reversion
247 26         84 $self->{qq_orig} = $self->_deepcopy($self->{qq});
248              
249             # Changed flag
250 26         43 $self->{changed} = 0;
251              
252 26         82 return $self;
253             }
254             # -------------------------------------------------------------------------
255              
256             1;
257              
258             =head1 NAME
259              
260             URI::Query - class providing URI query string manipulation
261              
262             =head1 SYNOPSIS
263              
264             # Constructor - using a GET query string
265             $qq = URI::Query->new($query_string);
266             # OR Constructor - using a hashref of key => value parameters
267             $qq = URI::Query->new($cgi->Vars);
268             # OR Constructor - using an array of successive keys and values
269             $qq = URI::Query->new(@params);
270              
271             # Clone the current object
272             $qq2 = $qq->clone;
273              
274             # Revert back to the initial constructor state (to do it all again)
275             $qq->revert;
276              
277             # Remove all occurrences of the given parameters
278             $qq->strip('page', 'next');
279              
280             # Remove all parameters except the given ones
281             $qq->strip_except('pagesize', 'order');
282              
283             # Remove all empty/undefined parameters
284             $qq->strip_null;
285              
286             # Replace all occurrences of the given parameters
287             $qq->replace(page => $page, foo => 'bar');
288              
289             # Set the argument separator to use for output (default: unescaped '&')
290             $qq->separator(';');
291              
292             # Output the current query string
293             print "$qq"; # OR $qq->stringify;
294             # Stringify with explicit argument separator
295             $qq->stringify(';');
296              
297             # Output the current query string with a leading '?'
298             $qq->qstringify;
299             # Stringify with a leading '?' and an explicit argument separator
300             $qq->qstringify(';');
301              
302             # Get a flattened hash/hashref of the current parameters
303             # (single item parameters as scalars, multiples as an arrayref)
304             my %qq = $qq->hash;
305              
306             # Get a non-flattened hash/hashref of the current parameters
307             # (parameter => arrayref of values)
308             my %qq = $qq->hash_arrayref;
309              
310             # Get the current query string as a set of hidden input tags
311             print $qq->hidden;
312              
313             # Check whether the query has changed since construction
314             if ($qq->has_changed) {
315             print "changed version: $qq\n";
316             }
317              
318              
319             =head1 DESCRIPTION
320              
321             URI::Query provides simple URI query string manipulation, allowing you
322             to create and manipulate URI query strings from GET and POST requests in
323             web applications. This is primarily useful for creating links where you
324             wish to preserve some subset of the parameters to the current request,
325             and potentially add or replace others. Given a query string this is
326             doable with regexes, of course, but making sure you get the anchoring
327             and escaping right is tedious and error-prone - this module is simpler.
328              
329             =head2 CONSTRUCTOR
330              
331             URI::Query objects can be constructed from scalar query strings
332             ('foo=1&bar=2&bar=3'), from a hashref which has parameters as keys, and
333             values either as scalars or arrayrefs of scalars (to handle the case of
334             parameters with multiple values e.g. { foo => '1', bar => [ '2', '3' ] }),
335             or arrays composed of successive parameters-value pairs
336             e.g. ('foo', '1', 'bar', '2', 'bar', '3'). For instance:
337              
338             # Constructor - using a GET query string
339             $qq = URI::Query->new($query_string);
340              
341             # Constructor - using an array of successive keys and values
342             $qq = URI::Query->new(@params);
343              
344             # Constructor - using a hashref of key => value parameters,
345             # where values are either scalars or arrayrefs of scalars
346             $qq = URI::Query->new($cgi->Vars);
347              
348             URI::Query also handles L-style hashrefs, where multiple
349             values are packed into a single string, separated by the "\0" (null)
350             character.
351              
352             All keys and values are URI unescaped at construction time, and are
353             stored and referenced unescaped. So a query string like:
354              
355             group=prod%2Cinfra%2Ctest&op%3Aset=x%3Dy
356              
357             is stored as:
358              
359             'group' => 'prod,infra,test'
360             'op:set' => 'x=y'
361              
362             You should always use the unescaped/normal variants in methods i.e.
363              
364             $qq->replace('op:set' => 'x=z');
365              
366             NOT:
367              
368             $qq->replace('op%3Aset' => 'x%3Dz');
369              
370             You can also construct a new URI::Query object by cloning an existing
371             one:
372              
373             $qq2 = $qq->clone;
374              
375              
376             =head2 MODIFIER METHODS
377              
378             All modifier methods change the state of the URI::Query object in some
379             way, and return $self, so they can be used in chained style e.g.
380              
381             $qq->revert->strip('foo')->replace(bar => 123);
382              
383             Note that URI::Query stashes a copy of the parameter set that existed
384             at construction time, so that any changes made by these methods can be
385             rolled back using 'revert()'. So you don't (usually) need to keep
386             multiple copies around to handle incompatible changes.
387              
388             =over 4
389              
390             =item revert()
391              
392             Revert the current parameter set back to that originally given at
393             construction time i.e. discard all changes made since construction.
394              
395             =item strip($param1, $param2, ...)
396              
397             Remove all occurrences of the given parameters and their values from
398             the current parameter set.
399              
400             =item strip_except($param1, $param2, ...)
401              
402             Remove all parameters EXCEPT those given from the current parameter
403             set.
404              
405             =item strip_null()
406              
407             Remove all parameters that have a value of undef from the current
408             parameter set.
409              
410             =item replace($param1 => $value1, $param2, $value2, ...)
411              
412             Replace the values of the given parameters in the current parameter set
413             with these new ones. Parameter names must be scalars, but values can be
414             either scalars or arrayrefs of scalars, when multiple values are desired.
415              
416             Note that 'replace' can also be used to add or append, since there's
417             no requirement that the parameters already exist in the current parameter
418             set.
419              
420             =item strip_like($regex)
421              
422             Remove all parameters whose names match the given (qr-quoted) regex e.g.
423              
424             $qq->strip_like(qr/^utm/)
425              
426             Does NOT match against parameter values.
427              
428             =item separator($separator)
429              
430             Set the argument separator to use for output. Default: '&'.
431              
432             =back
433              
434             =head2 ACCESSOR METHODS
435              
436             =over 4
437              
438             =item has_changed()
439              
440             If the query is actually changed by any of the modifier methods (strip,
441             strip_except, strip_null, strip_like, or replace) it sets an internal
442             changed flag which can be access by:
443              
444             $qq->has_changed
445              
446             revert() resets the has_changed flag to false.
447              
448             =back
449              
450             =head2 OUTPUT METHODS
451              
452             =over 4
453              
454             =item "$qq", stringify(), stringify($separator)
455              
456             Return the current parameter set as a conventional param=value query
457             string, using $separator as the separator if given. e.g.
458              
459             foo=1&bar=2&bar=3
460              
461             Note that all parameters and values are URI escaped by stringify(), so
462             that query-string reserved characters do not occur within elements. For
463             instance, a parameter set of:
464              
465             'group' => 'prod,infra,test'
466             'op:set' => 'x=y'
467              
468             will be stringified as:
469              
470             group=prod%2Cinfra%2Ctest&op%3Aset=x%3Dy
471              
472             =item qstringify(), qstringify($separator)
473              
474             Convenience method to stringify with a leading '?' e.g.
475              
476             ?foo=1&bar=2&bar=3
477              
478             =item hash()
479              
480             Return a hash (in list context) or hashref (in scalar context) of the
481             current parameter set. Single-item parameters have scalar values, while
482             while multiple-item parameters have arrayref values e.g.
483              
484             {
485             foo => 1,
486             bar => [ 2, 3 ],
487             }
488              
489             =item hash_arrayref()
490              
491             Return a hash (in list context) or hashref (in scalar context) of the
492             current parameter set. All values are returned as arrayrefs, including
493             those with single values e.g.
494              
495             {
496             foo => [ 1 ],
497             bar => [ 2, 3 ],
498             }
499              
500             =item hidden()
501              
502             Returns the current parameter set as a concatenated string of hidden
503             input tags, one per parameter-value e.g.
504              
505            
506            
507            
508              
509             =back
510              
511             =head1 BUGS AND CAVEATS
512              
513             Please report bugs and/or feature requests to
514             C, or through
515             the web interface at
516             L.
517              
518             Should allow unescaping of input to be turned off, for situations in
519             which it's already been done. Please let me know if you find you
520             actually need this.
521              
522             I don't think it makes sense on the output side though, since you need
523             to understand the structure of the query to escape elements correctly.
524              
525              
526             =head1 PATCHES
527              
528             URI::Query code lives at L.
529             Patches / pull requests welcome!
530              
531              
532             =head1 AUTHOR
533              
534             Gavin Carr
535              
536              
537             =head1 COPYRIGHT
538              
539             Copyright 2004-2015, Gavin Carr.
540              
541             This program is free software. You may copy or redistribute it under the
542             same terms as perl itself.
543              
544             =cut
545              
546             =for Pod::Coverage new
547              
548             # vim:sw=4:et