File Coverage

blib/lib/Data/URIEncode.pm
Criterion Covered Total %
statement 100 102 98.0
branch 64 76 84.2
condition 13 22 59.0
subroutine 9 9 100.0
pod 4 4 100.0
total 190 213 89.2


line stmt bran cond sub pod time code
1             package Data::URIEncode;
2              
3             =head1 NAME
4              
5             Data::URIEncode - Allow complex data structures to be encoded using flat URIs.
6              
7             =cut
8              
9 1     1   1112 use strict;
  1         2  
  1         45  
10 1     1   5 use base qw(Exporter);
  1         1  
  1         90  
11 1         140 use vars qw($VERSION
12             @EXPORT_OK
13             $MAX_ARRAY_EXPAND
14             $DUMP_BLESSED_DATA
15             $qr_chunk
16             $qr_chunk_quoted
17 1     1   20 );
  1         1  
18              
19             BEGIN {
20 1     1   3 $VERSION = '0.11';
21 1         3 @EXPORT_OK = qw(flat_to_complex complex_to_flat query_to_complex complex_to_query);
22 1         2 $MAX_ARRAY_EXPAND = 100;
23 1 50       4 $DUMP_BLESSED_DATA = 1 if ! defined $DUMP_BLESSED_DATA;
24 1         2 $qr_chunk = "([^.:]*)";
25 1         1425 $qr_chunk_quoted = "'((?:[^']*|\\\\')+)(?
26             }
27              
28             ###----------------------------------------------------------------###
29              
30             sub flat_to_complex {
31 69   50 69 1 1206 my $in = shift || die "Missing hashref";
32              
33 69         102 my $out = {};
34              
35 69         251 foreach my $key (sort keys %$in) {
36 87 100       304 my $copy = ($key =~ /^[.:]/) ? $key : ".$key";
37 87         96 my $ref = $out;
38 87         109 my $name = 'root';
39              
40 87   100     738 while ($copy =~ s/^ ([.:]) $qr_chunk_quoted//xo
41             || $copy =~ s/^ ([.:]) $qr_chunk//xo) {
42 162         398 my ($sep, $next) = ($1, $2);
43 162 100       383 $next =~ s/\\\'/\'/g if $3;
44              
45 162 100       700 if (ref $ref eq 'ARRAY') {
    50          
46 2 50       8 if (! exists $ref->[$name]) {
47 2 50       9 $ref->[$name] = $sep eq ':' ? [] : {};
48             }
49 2 50       11 die "Can't use $name as index value for an array while unfolding $key"
50             if $name !~ /^\d+$/;
51 2 50       8 die "Can't expand array in $key by more than $MAX_ARRAY_EXPAND"
52             if $name - $#$ref > $MAX_ARRAY_EXPAND;
53 2         4 $ref = $ref->[$name];
54 2         3 $name = $next;
55             } elsif (ref $ref eq 'HASH') {
56 160 100       341 if (! exists $ref->{$name}) {
57 125 100       352 $ref->{$name} = $sep eq ':' ? [] : {};
58             }
59 160         238 $ref = $ref->{$name};
60 160         267 $name = $next;
61             } else {
62 0         0 die "Unknown type during unfold of $key";
63             }
64              
65 162 100       255 if ($sep eq ':') {
66 33 100       227 die "Can't coerce hash into array near \"$name\" while unfolding $key"
67             if ref $ref eq 'HASH';
68             } else {
69 129 100       999 die "Can't coerce array into hash near \"$name\" while unfolding $key"
70             if ref $ref eq 'ARRAY';
71             }
72             }
73              
74              
75 84 100       176 if (ref $ref eq 'HASH') {
    100          
76 54         205 $ref->{$name} = $in->{$key};
77             } elsif (ref $ref eq 'ARRAY') {
78 29 100       288 die "Can't use $name as index value for an array while unfolding $key"
79             if $name !~ /^\d+$/;
80 26 100       87 die "Can't expand array in $key by more than $MAX_ARRAY_EXPAND"
81             if $name - $#$ref > $MAX_ARRAY_EXPAND;
82 25         79 $ref->[$name] = $in->{$key};
83             } else {
84 1         15 die "Can't unfold $key at level $name (scalar value exists)";
85             }
86             }
87              
88 61         378 return $out->{'root'};
89             }
90              
91             ###----------------------------------------------------------------###
92              
93             sub complex_to_flat {
94 53     53 1 503 my $in = shift;
95 53   100     184 my $out = shift || {};
96 53         71 my $prefix = shift;
97 53 100       129 $prefix = '' if ! defined $prefix;
98              
99 53 100       200 if (UNIVERSAL::isa($in, 'ARRAY')) {
    100          
100 7 100 66     39 die "Not handling blessed ARRAY" if ref $in ne 'ARRAY' && ! $DUMP_BLESSED_DATA;
101 6         15 foreach my $i (0 .. $#$in) {
102 13 100 33     64 if (ref $in->[$i]) {
    50          
103 1         4 complex_to_flat($in->[$i], $out, "$prefix:"._flatten_escape($i));
104             } elsif (defined $in->[$i] || $i == $#$in) {
105 12         27 my $key = "$prefix:"._flatten_escape($i);
106 12         36 $key =~ s/^\.//; # leading . is not necessary (it is the default)
107 12         43 $out->{$key} = $in->[$i];
108             }
109             }
110             } elsif (UNIVERSAL::isa($in, 'HASH')) {
111 43 100 66     144 die "Not handling blessed HASH" if ref $in ne 'HASH' && ! $DUMP_BLESSED_DATA;
112 42         112 foreach my $key (keys %$in) {
113 43         63 my $val = $in->{$key};
114 43 100       78 if (ref $val) {
115 22         46 complex_to_flat($val, $out, "$prefix."._flatten_escape($key));
116             } else {
117 21         42 $key = "$prefix."._flatten_escape($key);
118 21         77 $key =~ s/^\.//; # leading . is not necessary (it is the default)
119 21         85 $out->{$key} = $val;
120             }
121             }
122             } else {
123 3 100       18 die "Need a hash or array" if ! defined $in;
124 2         22 die "Not sure how to handle that type ($in)";
125             }
126              
127 48         183 return $out;
128             }
129              
130             sub _flatten_escape {
131 56     56   70 my $val = shift;
132 56 50       108 return undef if ! defined $val;
133 56 100       122 return "''" if ! length $val;
134 52 100       219 return $val if $val !~ /[.:\']/;
135 9         19 $val =~ s/\'/\\\'/g;
136 9         30 return "'".$val."'";
137             }
138              
139             ###----------------------------------------------------------------###
140              
141             sub complex_to_query {
142 3     3 1 10 my $flat = complex_to_flat(@_);
143 6         8 return join "&", map {
144 3         19 my $key = $_;
145 6         12 my $val = $flat->{$_};
146 6         10 foreach ($key, $val) {
147 12 50       26 $_ = '' if ! defined;
148 12         17 s/([^\w.\-\ \:])/sprintf('%%%02X', ord $1)/eg;
  0         0  
149 12         27 y/ /+/;
150             }
151 6         35 "$key=$val";
152             } sort keys %$flat;
153             }
154              
155             sub query_to_complex {
156 13     13 1 21228 my $q;
157 13         20 my $str = shift;
158              
159 13 100       59 if (! ref $str) { # normal string
    100          
    100          
    100          
160 3 50 33     18 return {} if ! defined $str || ! length $str;
161 3         35 require CGI;
162 3         14 $q = CGI->new(\$str);
163              
164             } elsif (ref $str eq 'SCALAR') { # ref to a string
165 3 50 33     27 return {} if ! defined $$str || ! length $$str;
166 3         16 require CGI;
167 3         11 $q = CGI->new($str);
168              
169             } elsif (ref $str eq 'HASH') { # passed a data hash instead
170 3         6 return flat_to_complex($str);
171              
172             } elsif (UNIVERSAL::can($str, 'param')) { # CGI looking object
173 3         4 $q = $str;
174              
175             } else {
176 1         15 die "Not sure how to handle \"$str\" - should pass a string, ref to a string, a hashref, or a CGI compatible object";
177             }
178              
179 9         5620 my %hash = ();
180 9         21 foreach my $key ($q->param) {
181 18         190 my @val = $q->param($key);
182 18 50       352 $hash{$key} = ($#val <= 0) ? $val[0] : \@val;
183             }
184              
185 9         24 return flat_to_complex(\%hash);
186             }
187              
188             ###----------------------------------------------------------------###
189              
190             1;
191              
192             __END__