File Coverage

blib/lib/CGI/MultiValuedHash.pm
Criterion Covered Total %
statement 57 164 34.7
branch 7 52 13.4
condition 6 27 22.2
subroutine 7 14 50.0
pod 9 9 100.0
total 86 266 32.3


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             CGI::MultiValuedHash - Store and manipulate url-encoded data
4              
5             =cut
6              
7             ######################################################################
8              
9             package CGI::MultiValuedHash;
10             require 5.004;
11              
12             # Copyright (c) 1999-2004, Darren R. Duncan. All rights reserved. This module
13             # is free software; you can redistribute it and/or modify it under the same terms
14             # as Perl itself. However, I do request that this copyright information and
15             # credits remain attached to the file. If you modify this module and
16             # redistribute a changed version then please attach a note listing the
17             # modifications. This module is available "as-is" and the author can not be held
18             # accountable for any problems resulting from its use.
19              
20 1     1   711 use strict;
  1         1  
  1         31  
21 1     1   4 use warnings;
  1         2  
  1         29  
22 1     1   5 use vars qw($VERSION @ISA);
  1         4  
  1         83  
23             $VERSION = '1.09';
24              
25             ######################################################################
26              
27             =head1 DEPENDENCIES
28              
29             =head2 Perl Version
30              
31             5.004
32              
33             =head2 Standard Modules
34              
35             I
36              
37             =head2 Nonstandard Modules
38              
39             Data::MultiValuedHash 1.081 (parent class)
40              
41             =cut
42              
43             ######################################################################
44              
45 1     1   922 use Data::MultiValuedHash 1.081;
  1         3056  
  1         2091  
46             @ISA = qw( Data::MultiValuedHash );
47              
48             ######################################################################
49              
50             =head1 SYNOPSIS
51              
52             use CGI::MultiValuedHash;
53              
54             my $case_insensitive = 1;
55             my $complementry_set = 1;
56              
57             my $params = CGI::MultiValuedHash->new( $case_insensitive,
58             $ENV{'HTTP_COOKIE'} || $ENV{'COOKIE'}, '; ', '&' );
59              
60             my $form_data;
61             read( STDIN, $form_data, $ENV{'CONTENT_LENGTH'} );
62             chomp( $form_data );
63             $params->from_url_encoded_string( $form_data );
64             $params->trim_bounding_whitespace(); # clean up user input
65              
66             foreach my $key ($params->keys()) {
67             my @values = $params->fetch( $key );
68             print "Field '$key' contains: '".join( "','", @values )."'\n";
69             }
70              
71             my @record_list = ();
72              
73             open( FH, "+
74             flock( FH, 2 );
75             seek( FH, 0, 2 );
76             $params->to_file( \*FH );
77             seek( FH, 0, 0 );
78             @record_list =
79             @{CGI::MultiValuedHash->batch_from_file( \*FH, $case_insensitive )};
80             flock( FH, 8 );
81             close( FH );
82              
83             foreach my $record (@record_list) {
84             print "\nSubmitted by:".$record->fetch_value( 'name' )."\n";
85             print "\nTracking cookie:".$record->fetch_value( 'track' )."\n";
86             my %Qs_and_As = $record->fetch_all( ['name', 'track'], $complementary_set );
87             foreach my $key (keys %Qs_and_As) {
88             my @values = @{$Qs_and_As{$key}};
89             print "Question: '$key'\n";
90             print "Answers: '".join( "','", @values )."'\n";
91             }
92             }
93              
94             =head1 DESCRIPTION
95              
96             This Perl 5 object class extends the functionality of Data::MultiValuedHash with
97             new methods that are especially useful in a CGI environment. Please read the POD
98             for the latter to see what the preexisting features are. New functionality
99             includes importing and exporting of url-encoded data. This process is
100             customizable and can handle such formats as http query or cookie strings, or
101             newline-delimited text files. Similarly, this class can import from or export to
102             a file stream. Other new features include exporting to html-encoded hidden form
103             fields, for the purpose of having persistant form data that is too large for a
104             query string. New manipulation features include trimming of whitespace from
105             values so that when users type only enter such the field reads as empty. Useful
106             inherited features include optional case-insensitive keys and the ability to
107             export subsets of data when only some is needed (separate "other" form fields
108             from special ones that you previously used).
109              
110             =cut
111              
112             ######################################################################
113              
114             # Names of properties for objects of this class are declared here:
115             my $KEY_MAIN_HASH = 'main_hash'; # this is a hash of arrays
116             my $KEY_CASE_INSE = 'case_inse'; # are our keys case insensitive?
117              
118             ######################################################################
119              
120             =head1 SYNTAX
121              
122             This class does not export any functions or methods, so you need to call them
123             using object notation. This means using Bfunction()> for functions
124             and B<$object-Emethod()> for methods. If you are inheriting this class for
125             your own modules, then that often means something like B<$self-Emethod()>.
126              
127             This class is a subclass of Data::MultiValuedHash and inherits all of the
128             latter's functionality and behaviour. Please read the POD for the latter to see
129             how to use the preexisting methods.
130              
131             =head1 FUNCTIONS AND METHODS
132              
133             =head2 initialize([ CASE[, SOURCE[, *]] ])
134              
135             The above method in Data::MultiValuedHash has hooks which allow subclasses to
136             add more data types to be used for SOURCE; the hook is called if SOURCE is not
137             a Hash ref (normal or of arrays) or an MVH object, which are already handled.
138             This class adds the ability to use filehandles and url-encoded strings as SOURCE.
139             If SOURCE is a valid file handle then from_file( SOURCE, * ) is used. Otherwise,
140             the method from_url_encoded_string( SOURCE, * ) is used.
141              
142             =cut
143              
144             ######################################################################
145             # This is the hook, called as _set...source( SOURCE[, *] )
146              
147             sub _set_hash_with_nonhash_source {
148 24     24   1026 my ($self, $initializer, @rest) = @_;
149 24 50       50 if( ref($initializer) eq 'GLOB' ) {
150 0         0 $self->from_file( $initializer, @rest );
151             } else {
152 24         47 $self->from_url_encoded_string( $initializer, @rest );
153             }
154             }
155              
156             ######################################################################
157              
158             =head2 to_url_encoded_string([ DELIM[, VALSEP] ])
159              
160             This method returns a scalar containing all of this object's keys and values
161             encoded in an url-escaped "query string" format. The escaping format specifies
162             that any characters which aren't in [a-zA-Z0-9_ .-] are replaced with a triplet
163             containing a "%" followed by the two-hex-digit representation of the ascii value
164             for the character. Also, any " " (space) is replaced with a "+". Each key and
165             value pair is delimited by a "=". If a key has multiple values, then there are
166             that many "key=value" pairs. The optional argument, DELIM, is a scalar
167             specifying what to use as a delimiter between pairs. This is "&" by default. If
168             a "\n" is given for DELIM, the resulting string would be suitable for writing to
169             a file where each key=value pair is on a separate line. The second optional
170             argument, VALSEP, is a scalar that specifies the delimiter between multiple
171             consecutive values which share a common key, and that key only appears once. For
172             example, SOURCE could be "key1=val1&val2; key2=val3&val4", as is the case with
173             "cookie" strings (DELIM is "; " and VALSEP is "&") or "isindex" queries.
174              
175             =cut
176              
177             ######################################################################
178              
179             sub to_url_encoded_string {
180 12     12 1 2909 my $self = CORE::shift( @_ );
181 12         20 my $rh_main_hash = $self->{$KEY_MAIN_HASH};
182 12   100     38 my $delim_kvpair = CORE::shift( @_ ) || '&';
183 12         16 my $delim_values = CORE::shift( @_ );
184 12         13 my @result;
185              
186 12         13 foreach my $key (sort (CORE::keys %{$rh_main_hash})) {
  12         44  
187 39         46 my $key_enc = $key;
188 39         63 $key_enc =~ s/([^\w .-])/'%'.sprintf("%2.2X",ord($1))/ge;
  0         0  
189 39         52 $key_enc =~ tr/ /+/;
190              
191 39         39 my @values;
192              
193 39         38 foreach my $value (@{$rh_main_hash->{$key}}) {
  39         71  
194 60         69 my $value_enc = $value; # s/// on $value changes original
195 60         99 $value_enc =~ s/([^\w .-])/'%'.sprintf("%2.2X",ord($1))/ge;
  18         73  
196 60         69 $value_enc =~ tr/ /+/;
197              
198 60         240 CORE::push( @values, $value_enc );
199             }
200              
201 39 100       167 CORE::push( @result, "$key_enc=".(
202             $delim_values ? join( $delim_values, @values ) :
203             join( "$delim_kvpair$key_enc=", @values )
204             ) );
205             }
206              
207 12         52 return( join( $delim_kvpair, @result ) );
208             }
209              
210             ######################################################################
211              
212             =head2 from_url_encoded_string( SOURCE[, DELIM[, VALSEP]] )
213              
214             This method takes a scalar, SOURCE, containing a set of keys and values encoded
215             in an url-escaped "query string" format, and adds them to this object. The
216             escaping format specifies that any characters which aren't in [a-zA-Z0-9_ .-] are
217             replaced with a triplet containing a "%" followed by the two-hex-digit
218             representation of the ascii value for the character. Also, any " " (space) is
219             replaced with a "+". Each key and value pair is delimited by a "=". If a key
220             has multiple values, then there are that many "key=value" pairs. The first
221             optional argument, DELIM, is a scalar specifying what to use as a delimiter
222             between pairs. This is "&" by default. If a "\n" is given for DELIM, the source
223             string was likely read from a file where each key=value pair is on a separate
224             line. The second optional argument, VALSEP, is a scalar that specifies the
225             delimiter between multiple consecutive values which share a common key, and that
226             key only appears once. For example, SOURCE could be "key1=val1&val2;
227             key2=val3&val4", as is the case with "cookie" strings (DELIM is "; " and VALSEP
228             is "&") or "isindex" queries.
229              
230             =cut
231              
232             ######################################################################
233              
234             sub from_url_encoded_string {
235 24     24 1 31 my $self = CORE::shift( @_ );
236 24         22 my $source_str = CORE::shift( @_ );
237 24   100     51 my $delim_kvpair = CORE::shift( @_ ) || '&';
238 24         27 my $delim_values = CORE::shift( @_ );
239 24         193 my @source = split( $delim_kvpair, $source_str );
240              
241 24         41 my $rh_main_hash = $self->{$KEY_MAIN_HASH};
242 24         31 my $case_inse = $self->{$KEY_CASE_INSE};
243              
244 24         36 foreach my $pair (@source) {
245 106         221 my ($key, $values_str) = split( '=', $pair, 2 );
246 106 50       186 next if( $key eq "" );
247              
248 106         111 $key =~ tr/+/ /;
249 106         118 $key =~ s/%([0-9a-fA-F]{2})/pack("c",hex($1))/ge;
  0         0  
250 106 50       181 $key = lc($key) if( $case_inse );
251 106   100     349 $rh_main_hash->{$key} ||= [];
252              
253 106 100       247 my @values = $delim_values ?
254             split( $delim_values, $values_str ) : $values_str;
255              
256 106         142 foreach my $value (@values) {
257 120         166 $value =~ tr/+/ /;
258 120         189 $value =~ s/%([0-9a-fA-F]{2})/pack("c",hex($1))/ge;
  36         130  
259              
260 120         104 CORE::push( @{$rh_main_hash->{$key}}, $value );
  120         428  
261             }
262             }
263              
264 24         104 return( scalar( @source ) );
265             }
266              
267             ######################################################################
268              
269             =head2 to_file( FH[, DELIM[, VALSEP[, REC_DELIM[, EMPTY]]]]] )
270              
271             This method encodes all of this object's keys and values using the
272             to_url_encoded_string( DELIM, VALSEP ) method and writes it to the filehandle
273             provided in FH. The optional argument REC_DELIM is a scalar value that will be
274             written to FH before this encoded object, and serves to delimit multiple encoded
275             objects of this class. The default values for [DELIM, VALSEP, REC_DELIM] are
276             ["\n", undef, "\n=\n"]. If the boolean argument EMPTY is true then this object
277             will be written to FH even if it is empty (has no keys), resulting in only a
278             REC_DELIM actually being written. The default behaviour of false prevents this
279             from happening, so only objects containing data are output. This method returns
280             1 on a successful write, 0 for an empty record that was skipped, and it returns
281             undef on a file-system error.
282              
283             =cut
284              
285             ######################################################################
286              
287             sub to_file {
288 0     0 1   my ($self, $fh, $delim_kvpair, $delim_values, $delim_recs, $use_empty) = @_;
289              
290 0 0         ref( $fh ) eq 'GLOB' or return( undef );
291              
292 0   0       $delim_kvpair ||= "\n";
293 0   0       $delim_values ||= undef;
294 0   0       $delim_recs ||= "\n=\n";
295              
296 0           local $\ = undef;
297              
298 0 0 0       !$self->keys_count() and !$use_empty and return( 0 );
299              
300 0           my $record_str =
301             $self->to_url_encoded_string( $delim_kvpair, $delim_values );
302              
303 0 0         print $fh "$delim_recs$record_str" or return( undef );
304              
305 0           return( 1 );
306             }
307              
308             ######################################################################
309              
310             =head2 from_file( FH[, DELIM[, VALSEP[, REC_DELIM[, EMPTY]]]]] )
311              
312             This method adds keys and values to this object from an encoded record read from
313             the filehandle provided in FH and parsed with from_url_encoded_string( ., DELIM,
314             VALSEP ). The optional argument REC_DELIM is a scalar value that delimits
315             encoded records in the file stream. The default values for [DELIM, VALSEP,
316             REC_DELIM] are ["\n", undef, "\n=\n"]. If the boolean argument EMPTY is true
317             then this object will be initialized to empty (has no keys) if the record
318             delimiter is encountered in the file stream before any valid encoded record. The
319             default behaviour of false prevents this from happening, so the file stream
320             continues to be read until a valid record is found. This method returns 1 on a
321             successful read, 0 for an empty record that was kept (may be end-of-file), and it
322             returns undef on a file-system error.
323              
324             =cut
325              
326             ######################################################################
327              
328             sub from_file {
329 0     0 1   my ($self, $fh, $delim_kvpair, $delim_values, $delim_recs, $use_empty) = @_;
330              
331 0 0         ref( $fh ) eq 'GLOB' or return( undef );
332              
333 0   0       $delim_kvpair ||= "\n";
334 0   0       $delim_values ||= undef;
335 0   0       $delim_recs ||= "\n=\n";
336              
337 0           local $/ = $delim_recs;
338              
339 0 0         GET_ANOTHER_REC: {
340 0           eof( $fh ) and return( 0 );
341              
342 0 0         defined( my $record_str = <$fh> ) or return( undef );
343              
344 0           $self->from_url_encoded_string(
345             $record_str, $delim_kvpair, $delim_values );
346              
347 0 0         $self->keys_count() and return( 1 );
348              
349 0 0         $use_empty and return( 0 );
350              
351 0           redo GET_ANOTHER_REC;
352             }
353             }
354              
355             ######################################################################
356              
357             =head2 to_html_encoded_table([ LINEBREAK ])
358              
359             This method returns a scalar containing table html with all of this object's keys
360             and values. The table has two columns, with keys on the left and values on the
361             right, and each row is one key and its values. By default, the values appear
362             comma-delimited, but if the optional boolean argument LINEBREAK is true, then
363             the value list is delimited with
tags instead, putting each value on its own
364             line. All keys and values are html-escaped such that any occurances of [&,",<,>]
365             are substitued with [&,",>,<].
366              
367             =cut
368              
369             ######################################################################
370              
371             sub to_html_encoded_table {
372 0     0 1   my ($self, $linebreak) = @_;
373 0           my $rh_main_hash = $self->{$KEY_MAIN_HASH};
374 0           my @result;
375              
376 0           CORE::push( @result, "\n" ); \n" );
377              
378 0           foreach my $key (sort (CORE::keys %{$rh_main_hash})) {
  0            
379 0           CORE::push( @result, "
\n" );
380              
381 0           my $key_enc = $key;
382 0           $key_enc =~ s/&/&/g;
383 0           $key_enc =~ s/\"/"/g;
384 0           $key_enc =~ s/>/>/g;
385 0           $key_enc =~ s/
386              
387 0           CORE::push( @result, $key_enc );
388              
389 0           CORE::push( @result, "\n" );
390              
391 0           my @enc_value_list;
392              
393 0           foreach my $value (@{$rh_main_hash->{$key}}) {
  0            
394 0           my $value_enc = $value; # s/// on $value changes original
395 0           $value_enc =~ s/&/&/g;
396 0           $value_enc =~ s/\"/"/g;
397 0           $value_enc =~ s/>/>/g;
398 0           $value_enc =~ s/
399              
400 0           CORE::push( @enc_value_list, $value_enc );
401             }
402              
403 0 0         CORE::push( @result, $linebreak ? join( "
\n", @enc_value_list ) :
404             join( ", \n", @enc_value_list ) );
405              
406 0           CORE::push( @result, "
407             }
408              
409 0           CORE::push( @result, "
\n" );
410              
411 0           return( join( '', @result ) );
412             }
413              
414             ######################################################################
415              
416             =head2 to_html_encoded_hidden_fields()
417              
418             This method returns a scalar containing html text which defines a list of hidden
419             form fields whose names and values are all of this object's keys and values.
420             Each list element looks like ''.
421             Where a key has multiple values, a hidden field is made for each value. All keys
422             and values are html-escaped such that any occurances of [&,",<,>] are substitued
423             with [&,",>,<]. In cases where this object was storing user input
424             that was submitted using 'post', this method can generate the content of a
425             self-referencing form, should the main program need to call itself. It would
426             handle persistant data which is too big to put in a self-referencing query
427             string.
428              
429             =cut
430              
431             ######################################################################
432              
433             sub to_html_encoded_hidden_fields {
434 0     0 1   my $self = CORE::shift( @_ );
435 0           my $rh_main_hash = $self->{$KEY_MAIN_HASH};
436 0           my @result;
437              
438 0           foreach my $key (sort (CORE::keys %{$rh_main_hash})) {
  0            
439 0           my $key_enc = $key;
440 0           $key_enc =~ s/&/&/g;
441 0           $key_enc =~ s/\"/"/g;
442 0           $key_enc =~ s/>/>/g;
443 0           $key_enc =~ s/
444              
445 0           foreach my $value (@{$rh_main_hash->{$key}}) {
  0            
446 0           my $value_enc = $value; # s/// on $value changes original
447 0           $value_enc =~ s/&/&/g;
448 0           $value_enc =~ s/\"/"/g;
449 0           $value_enc =~ s/>/>/g;
450 0           $value_enc =~ s/
451              
452 0           CORE::push( @result, <<__endquote );
453            
454             __endquote
455             }
456             }
457              
458 0           return( join( '', @result ) );
459             }
460              
461             ######################################################################
462              
463             =head2 trim_bounding_whitespace()
464              
465             This method cleans up all of this object's values by trimming any leading or
466             trailing whitespace. The keys are left alone. This would normally be done when
467             the object is representing user input from a form, including when they entered
468             nothing but whitespace, and the program should act like they left the field
469             empty.
470              
471             =cut
472              
473             ######################################################################
474              
475             sub trim_bounding_whitespace {
476 0     0 1   my $self = CORE::shift( @_ );
477 0           foreach my $ra_values (values %{$self->{$KEY_MAIN_HASH}}) {
  0            
478 0           foreach my $value (@{$ra_values}) {
  0            
479 0           $value =~ s/^\s+//;
480 0           $value =~ s/\s+$//;
481             }
482             }
483             }
484              
485             ######################################################################
486              
487             =head2 batch_to_file( FH, LIST[, DELIM[, VALSEP[, REC_DELIM[, EMPTY]]]]] )
488              
489             This batch function writes encoded MVH objects to the filehandle provided in the
490             first argument, FH. The second argument, LIST, is an array ref containing the
491             MVH objects or hash refs to be written. Symantecs are similar to calling
492             to_file( FH, * ) once on each MVH object; any remaining arguments are passed on
493             as is to to_file(). If any array elements aren't MVHs or HASH refs, they are
494             disregarded. This method returns 1 on success, even if there are no objects to
495             write. It returns undef on a file-system error, even if some of the objects were
496             written first.
497              
498             =cut
499              
500             ######################################################################
501              
502             sub batch_to_file {
503 0     0 1   my $class = CORE::shift( @_ );
504 0           my $fh = CORE::shift( @_ );
505 0 0         my @mvh_list = ref($_[0]) eq 'ARRAY' ? @{CORE::shift(@_)} : CORE::shift(@_);
  0            
506              
507 0 0         ref( $fh ) eq 'GLOB' or return( undef );
508              
509 0           foreach my $mvh (@mvh_list) {
510 0 0         ref( $mvh ) eq 'Data::MultiValuedHash' and
511             bless( $mvh, 'CGI::MultiValuedHash' );
512 0 0         ref( $mvh ) eq 'HASH' and $mvh =
513             CGI::MultiValuedHash->new( 0, $mvh );
514 0 0         ref( $mvh ) eq "CGI::MultiValuedHash" or next;
515              
516 0 0         defined( $mvh->to_file( $fh, @_ ) ) or return( undef );
517             }
518              
519 0           return( 1 );
520             }
521              
522             ######################################################################
523              
524             =head2 batch_from_file( FH, CASE[, MAX[, DELIM[, VALSEP[, REC_DELIM[, EMPTY]]]]] )
525              
526             This batch function reads encoded MVH objects from the filehandle provided in the
527             first argument, FH, and returns them in a list. The second argument, CASE,
528             specifies whether the new MVH objects are case-insensitive or not. The third
529             optional argument, MAX, specifies the maximum number of objects to read. If that
530             argument is undefined or less than 1, then all objects are read until the
531             end-of-file is reached. Symantecs are similar to calling from_file( FH, * ) once
532             on each MVH object; any remaining arguments are passed on as is to from_file().
533             This method returns an ARRAY ref containing the new records (as MVHs) on success,
534             even if the end-of-file is reached before we find any records. It returns undef
535             on a file-system error, even if some records were read first.
536              
537             =cut
538              
539             ######################################################################
540              
541             sub batch_from_file {
542 0     0 1   my $class = CORE::shift( @_ );
543 0           my $fh = CORE::shift( @_ );
544 0           my $case_inse = CORE::shift( @_ );
545 0           my $max_obj_num = CORE::shift( @_ ); # if <= 0, read all records
546 0           my $use_empty = $_[3]; # fourth remaining argument
547              
548 0 0         ref( $fh ) eq 'GLOB' or return( undef );
549              
550 0           my @mvh_list = ();
551 0 0         my $remaining_obj_count = ($max_obj_num <= 0) ? -1 : $max_obj_num;
552              
553 0 0         GET_ANOTHER_REC: {
554 0           eof( $fh ) and last;
555              
556 0           my $mvh = CGI::MultiValuedHash->new( $case_inse );
557              
558 0 0         defined( $mvh->from_file( $fh, @_ ) ) or return( undef );
559              
560 0           CORE::push( @mvh_list, $mvh );
561              
562 0 0         --$remaining_obj_count != 0 and redo GET_ANOTHER_REC;
563             }
564              
565             # if file is of nonzero length and contains no records, or if it has a
566             # record separator followed by no records, then we would end up with an
567             # empty last record in our list even if empty records aren't allowed,
568             # so we get rid of said disallowed here
569 0 0 0       if( !$use_empty and @mvh_list and !$mvh_list[-1]->keys_count() ) {
      0        
570 0           CORE::pop( @mvh_list );
571             }
572              
573 0           return( \@mvh_list );
574             }
575              
576             ######################################################################
577              
578             1;
579             __END__