File Coverage

blib/lib/URI/Query.pm
Criterion Covered Total %
statement 115 119 96.6
branch 27 36 75.0
condition 18 21 85.7
subroutine 21 21 100.0
pod 10 11 90.9
total 191 208 91.8


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