File Coverage

blib/lib/Data/Taxi.pm
Criterion Covered Total %
statement 153 188 81.3
branch 59 88 67.0
condition 9 18 50.0
subroutine 15 15 100.0
pod 2 5 40.0
total 238 314 75.8


line stmt bran cond sub pod time code
1             package Data::Taxi;
2 1     1   4825 use strict;
  1         2  
  1         32  
3 1     1   4 use vars qw[@ISA $VERSION $FORMAT_VERSION %HANDLE_FORMATS @EXPORT_OK %EXPORT_TAGS];
  1         2  
  1         64  
4 1     1   4 use Carp 'croak';
  1         6  
  1         43  
5 1     1   4 use Exporter;
  1         1  
  1         28  
6 1     1   1211 use Debug::ShowStuff ':all';
  1         43161  
  1         461  
7             @ISA = 'Exporter';
8 1     1   22 use 5.006;
  1         4  
  1         124  
9            
10            
11             =head1 NAME
12            
13             Data::Taxi - Taint-aware, XML-ish data serialization
14            
15             PLEASE NOTE: Data::Taxi is no longer being developed or supported.
16            
17             =head1 SYNOPSIS
18            
19             use Data::Taxi ':all';
20             my ($ob, $str);
21            
22             $ob = MyClass->new();
23             $str = freeze($ob);
24             $ob = thaw($str);
25            
26            
27            
28             =head1 INSTALLATION
29            
30             Data::Taxi can be installed with the usual routine:
31            
32             perl Makefile.PL
33             make
34             make test
35             make install
36            
37             You can also just copy Taxi.pm into the Data/ directory of one of your library trees.
38            
39            
40             =head1 DESCRIPTION
41            
42             Taxi (Baint-Bware BML-Bsh) is a data serializer with several handy features:
43            
44             =over
45            
46             =item Taint aware
47            
48             Taxi does not force you to trust the data you are serializing.
49             None of the input data is executed.
50            
51             =item Human readable
52            
53             Taxi produces a human-readable string that simplifies checking the
54             output of your objects.
55            
56             =item XML-ish
57            
58             While I don't (currently) promise full XML compliance, Taxi produces a block
59             of XML-ish data that could probably be read in by other XML parsers.
60            
61             =back
62            
63            
64             =cut
65            
66             #------------------------------------------------------------------------
67             # import/export
68             #
69            
70             =head1 EXPORT
71            
72             None by default. freeze and thaw with ':all':
73            
74             use Data::Taxi ':all';
75            
76             =cut
77            
78             @EXPORT_OK = qw[freeze thaw];
79             %EXPORT_TAGS = ('all' => [@EXPORT_OK]);
80             #
81             # import/export
82             #------------------------------------------------------------------------
83            
84            
85             # version
86             $VERSION = '0.96';
87             $FORMAT_VERSION = '1.00';
88             undef $HANDLE_FORMATS{$FORMAT_VERSION};
89            
90            
91             # constants
92 1     1   6 use constant HASHREF => 1;
  1         2  
  1         64  
93 1     1   6 use constant ARRREF => 2;
  1         2  
  1         50  
94 1     1   5 use constant SCAREF => 3;
  1         1  
  1         43  
95 1     1   6 use constant SCALAR => 4;
  1         4  
  1         2514  
96            
97            
98             =head1 Subroutines
99            
100             =cut
101            
102            
103            
104            
105             #-----------------------------------------------------------------------------------
106             # freeze
107             #
108            
109             =head2 freeze($ob, %opts)
110            
111             C serializes a single scalar, hash reference, array reference, or
112             scalar reference into an XML string, C can recurse any number of
113             levels of a nested tree and preserve multiple references to the same object.
114             Let's look at an example:
115            
116             my ($tree, $format, $members, $bool, $mysca);
117            
118             # anonymous hash
119             $format = {
120             'app'=>'trini',
121             'ver'=>'0.9',
122             'ver'=>'this & that',
123             };
124            
125             # anonymous array
126             $members = ['Starflower', 'Mary', 'Paul', 'Hallie', 'Ryan'];
127            
128             # blessed object
129             $bool = Math::BooleanEval->new('whatever');
130            
131             # scalar reference (to an anonymous hash, no less)
132             $mysca = {'name'=>'miko', 'email'=>'miko@idocs.com', };
133            
134             # the whole thing
135             $tree = {
136             'dataformat' => $format,
137             'otherdataformat' => $format,
138             'bool' => $bool,
139             'members' => $members,
140             'myscaref' => \$mysca,
141             };
142            
143             $frozen = freeze($tree);
144            
145             C accepts one object as input. The code above results in the following
146             XML-ish string:
147            
148            
149            
150            
151            
152            
153            
154            
155            
156            
157            
158            
159            
160            
161            
162            
163            
164            
165            
166            
167            
168            
169            
170            
171            
172            
173            
174            
175            
176            
177            
178            
179            
180            
181             =cut
182            
183             # Golly, and after all that POD, the subroutine is only a few lines
184             # long. All the work is done in obtag(), which recurses through the
185             # data to build the data string.
186            
187             sub freeze {
188 1     1 1 21 my ($ob, %opts) = @_;
189 1         4 my $rv = '';
190            
191             # if a declaration is wanted
192 1 50 33     10 if (
193             $opts{'declaration'} ||
194             (! defined $opts{'declaration'})
195             ) {
196 1         3 $rv .= qq|\n|;
197             }
198            
199             $rv .=
200 1         8 '\n" .
201             join('', obtag($ob, {}, 1, %opts)) .
202             "\n";
203            
204 1         9 return $rv;
205             }
206             #
207             # freeze
208             #-----------------------------------------------------------------------------------
209            
210            
211             #-----------------------------------------------------------------------------------
212             # obtag
213             #
214             # Private subroutine: recurses through data structure building the data string.
215             #
216             sub obtag {
217 10     10 0 24 my ($ob, $ids, $depth, %opts) = @_;
218 10         10 my (@rv, $indent, $allowed);
219            
220             # hash of allowed fields to save
221 10         23 $allowed = get_allowed(\%opts);
222            
223            
224             # get tied class
225 10 50       22 if (defined $opts{'tied'})
  0         0  
226             {$opts{'tied'} =~ s|\=.*||}
227            
228             # build the indentation string for this recursion.
229 10         17 $indent = "\t" x $depth;
230            
231             # if reference
232 10 100       21 if (my $ref = ref($ob)) {
233 4         8 my $tagname = "$ob";
234 4         6 my $org = $tagname;
235 4         4 my ($tie);
236            
237 4         7 $tagname =~ s|^[^\=]*\=||;
238 4         15 $tagname =~ s|\(.*||;
239 4         11 $tagname = lc($tagname) . 'ref';
240            
241             # open tag
242 4         8 push @rv, $indent, '<', $tagname;
243            
244 4 100       11 if (defined $opts{'name'} )
  3         8  
245             {push @rv, ' name="', mlesc( $opts{'name'} ), '"'}
246            
247             # if in $ids
248 4 100       11 if ($ids->{$ob})
  1         9  
249             {return @rv, ' id="', $ids->{$ob}, '" redundant="1"/>', "\n"}
250            
251             # store object in objects hash
252             # $ids->{$ob} = 1;
253 3         4 $ids->{$ob} = keys(%{$ids});
  3         10  
254            
255            
256             # output ID
257             # push @rv, ' id="', mlesc($ob), '"';
258 3         10 push @rv, ' id="', $ids->{$ob}, '"';
259            
260             # class
261 3 50       17 if ($ref !~ m/^(HASH|ARRAY|REF|SCALAR)$/)
  0         0  
262             {push @rv, ' class="', mlesc($ref), '"'}
263            
264             # tied hash
265 3 100       10 if ($ref eq 'HASH') {
    50          
266 2 50       2 if (my $tie = tied(%{$ob}) ) {
  2         9  
267 0         0 $tie =~ s|\=.*||;
268 0         0 push @rv, ' tied="', mlesc($tie), '"';
269             }
270             }
271            
272             # tied array
273             elsif ($ref eq 'ARRAY') {
274 1 50       1 if (my $tie = tied(@{$ob}) ) {
  1         4  
275 0         0 $tie =~ s|\=.*||;
276 0         0 push @rv, ' tied="', mlesc($tie), '"';
277             }
278             }
279            
280             # close tag
281 3         6 push @rv, ">\n";
282            
283             # output children: hashref
284 3 100       10 if ($tagname eq 'hashref') {
    50          
    0          
285 2         7 HASHLOOP:
286 2         2 foreach my $k (keys %{$ob} ){
287             # if not allowed
288 6 50 33     16 if ($allowed && (! exists $allowed->{$k}) )
  0         0  
289             {next HASHLOOP}
290            
291 6         43 push @rv, obtag($ob->{$k}, $ids, $depth + 1, 'name'=>$k, 'tied'=>tied($ob->{$k}));
292             }
293             }
294            
295             # output children: arrayref
296             elsif ($tagname eq 'arrayref') {
297 1         1 foreach my $v ( @{$ob} )
  1         2  
  3         7  
298             {push @rv, obtag($v, $ids, $depth + 1)}
299             }
300            
301             # output children: scalarref
302             elsif ($tagname eq 'scalarref')
303 0         0 { push @rv, obtag(${$ob}, $ids, $depth + 1, 'tied'=>tied(${$ob}) ) }
  0         0  
  0         0  
304            
305             # else don't know this type of reference
306             else
307 0         0 { croak "don't know this type of reference: $tagname" }
308            
309             # close tag
310 3         9 push @rv, $indent, '\n";
311             }
312            
313             # else output tag with self-ender
314             else {
315 6         10 push @rv, $indent, '
316            
317 6 100       13 if (defined $opts{'name'} )
  3         8  
318             {push @rv, ' name="', mlesc( $opts{'name'} ), '"'}
319            
320 6 50       14 if (defined $opts{'tied'} )
  0         0  
321             {push @rv, ' tied="', mlesc( $opts{'tied'} ), '"'}
322            
323 6 50       12 if (defined $ob)
  6         9  
324             {push @rv, ' value="', mlesc($ob), '"'}
325            
326 6         9 push @rv, "/>\n";
327             }
328            
329 9         104 return @rv;
330             }
331             #
332             # obtag
333             #-----------------------------------------------------------------------------------
334            
335            
336            
337             #------------------------------------------------------------------------------
338             # thaw data
339             #
340            
341             =head2 thaw
342            
343             C accepts one argument, the serialized data string, and returns a single
344             value, the reconstituted data, rebuilding the entire data structure including
345             blessed references.
346            
347             $tree = thaw($frozen);
348            
349             =cut
350            
351             sub thaw {
352 1     1 1 4 my ($raw) = @_;
353 1         2 my (@els, @stack, %ids, %esc, $quote, $left, $right, $amp, $firstdone);
354            
355             # remove XML document header, we're not s'fisticaded 'nuff for that
356             # kinda thang yet. XML gurus will wince at this code.
357 1 50       6 if ($raw =~ s|^\<\?||)
  1         5  
358             {$raw =~ s|^[^\>]*>||}
359            
360            
361             #-------------------------------------------------------------
362             # placeholders for un-escaping
363             #
364             # I'm sure this could be done more gracefully. Feel free to
365             # to tidy up the unescaping routine and submit back your code.
366             # :-) Miko
367             #
368 1         4 while (keys(%esc) < 4) {
369 4         43 my $str = rand;
370 4         32 $str =~ s|^0\.||;
371            
372 4 50       42 unless ($raw =~ m|$str|)
  4         14  
373             {undef $esc{$str}}
374             }
375            
376 1         2 ($quote, $left, $right, $amp) = keys(%esc);
377            
378 1         4 $raw =~ s|"|$quote|g;
379 1         2 $raw =~ s|<|$left|g;
380 1         3 $raw =~ s|>|$right|g;
381 1         2 $raw =~ s|&|$amp|g;
382             #
383             # placeholders for un-escaping
384             #-------------------------------------------------------------
385            
386            
387             # split into tags
388 1         4 $raw =~ s|^\s*\<||;
389 1         20 $raw =~ s|\s*\>$||;
390 1         19 @els = split(m|\>\s*\<|, $raw);
391 1         3 undef $raw; # don't need this anymore, might as well clean up now
392            
393             # loop through tags
394             TAGLOOP:
395 1         3 foreach my $el (@els) {
396             # if end tag
397 14 100       29 if ($el =~ m|^/|) {
398             # if stack is down to 1 element, we're done
399 3 100       12 (@stack == 1) && return $stack[0]->[0];
400            
401 2         2 pop @stack;
402 2         5 next TAGLOOP;
403             }
404            
405             # variables
406 11         10 my ($type, $new, $selfender, %atts, $ref, $tagname);
407            
408             # self-ender?
409 11         43 $selfender = $el =~ s|\s*\/$||s;
410            
411             # get tagname
412 11         28 $el =~ s|^\s*||;
413 11         57 $el =~ s|\s*$||;
414 11 50       37 $el =~ s|^([^\s\"]+)\s*||s
415             or die "invalid tag: $el";
416 11         25 $tagname = lc($1) . ($el x 0);
417            
418            
419             #-------------------------------------------------------------
420             # parse into hash
421             #
422 11         92 $el =~ s|(\S+)\s*\=\s*"([^"]*)"\s*|\L$1\E\<$2\<|g;
423            
424 36         45 %atts = grep {
425 11         25 s|$quote|"|g;
426 36         41 s|$left|<|g;
427 36         44 s|$right|>|g;
428 36         38 s|$amp|&|g;
429 36         53 1;
430             } split('<', $el);
431             #
432             # parse into hash
433             #-------------------------------------------------------------
434            
435            
436             #-------------------------------------------------------------
437             # hashrefs
438             #
439 11 100 33     46 if ($tagname eq 'hashref') {
    100          
    50          
    100          
    50          
440 3         5 $type = HASHREF;
441            
442             # if tied
443 3 50       7 if (defined $atts{'tied'}) {
  3         4  
444 0         0 my %hash;
445 0         0 tie %hash, $atts{'tied'};
446 0         0 $new = \%hash;
447             }
448            
449             # else not tied
450             else
451             {$new = {}}
452            
453 3         4 $ref = 1;
454             }
455             #
456             # hashrefs
457             #-------------------------------------------------------------
458            
459            
460             #-------------------------------------------------------------
461             # array refs
462             #
463             elsif ($tagname eq 'arrayref') {
464 1         1 $type = ARRREF;
465            
466             # if tied
467 1 50       3 if (defined $atts{'tied'}) {
  1         2  
468 0         0 my @arr;
469 0         0 tie @arr, $atts{'tied'};
470 0         0 $new = \@arr;
471             }
472            
473             # else not tied
474             else
475             {$new = []}
476            
477 1         1 $ref = 1;
478             }
479             #
480             # array refs
481             #-------------------------------------------------------------
482            
483            
484             #-------------------------------------------------------------
485             # scalarref
486             #
487             elsif ($tagname eq 'scalarref') {
488 0         0 $type = SCAREF;
489 0         0 $ref = 1;
490             }
491             #
492             # scalarref
493             #-------------------------------------------------------------
494            
495            
496             #-------------------------------------------------------------
497             # scalar
498             #
499             elsif ($tagname eq 'scalar') {
500 6         7 $type = SCALAR;
501             }
502             #
503             # scalar
504             #-------------------------------------------------------------
505            
506            
507             #-------------------------------------------------------------
508             # taxi
509             #
510 0         0 elsif ( (! $firstdone) && ($tagname eq 'taxi') ) {
511             # do nothing
512             }
513             #
514             # taxi
515             #-------------------------------------------------------------
516            
517            
518             # else I don't know this tag
519             else
520             {croak "do not understand tag: $tagname $el"}
521            
522             # if first tag
523 11 100       23 if (! $firstdone) {
524             # version check
525 1 50       8 unless (exists $Data::Taxi::HANDLE_FORMATS{$atts{'ver'}})
  0         0  
526             {croak "Do not know this format version: $atts{'ver'}"}
527            
528 1         2 $firstdone = 1;
529 1         4 next TAGLOOP;
530             }
531            
532             # if ID, and ID already exists, that's the new object
533 10 100 100     44 if ( defined($atts{'id'}) && $ids{$atts{'id'}} )
  1 50       3  
    100          
    50          
534 0         0 {$new = $ids{$atts{'id'}} }
535            
536             # if blessed reference
537             elsif (defined $atts{'class'})
538 6         6 {bless $new, $atts{'class'}}
539            
540             # if scalar
541             elsif ($type == SCALAR)
542             {$new = $atts{'value'}}
543            
544             # if scalar reference
545             elsif ($type == SCAREF) {
546 0         0 my $val;
547 0         0 $new = \$val;
548             }
549            
550             # if reference
551 10 100       17 if ($ref)
  4         9  
552             {$ids{$atts{'id'}} = $new}
553            
554 10 100       18 if ( @stack ) {
  0 50       0  
555             # get prev and prevtype
556 9         6 my($prev, $prevtype) = @{$stack[$#stack]};
  9         16  
557            
558             # if prevtype is array, push into prev
559 9 100       18 if ($prevtype == HASHREF) {
  3 50       5  
560            
561            
562 6         11 $prev->{$atts{'name'}} = $new;
563             }
564            
565             # if prevtype is array, push into prev
566             elsif ($prevtype == ARRREF)
567 3         2 {push @{$prev}, $new}
  0         0  
568            
569             # else set scalar reference
570             else
571             {$prev = \$new}
572             }
573            
574             # if this is a selfender
575             elsif ($selfender)
576             {return $new}
577            
578             # if ! self ender and current is hash or arr
579 10 50 33     35 if ( (! $selfender) && ( ($type == HASHREF) || ($type == ARRREF) || ($type == SCAREF) ) )
  3   66     10  
580             {push @stack, [$new, $type]}
581             }
582            
583             # if we get this far, that's an error
584 0         0 die 'invalid FreezDry data format';
585             }
586             #
587             # thaw data
588             #------------------------------------------------------------------------------
589            
590            
591             #-----------------------------------------------------------------------------------
592             # mlesc
593             # Private sub. Escapes &, <, >, and " so that they don't mess up my parser.
594             #
595             sub mlesc {
596 12     12 0 19 my ($rv) = @_;
597 12 50       39 return '' unless defined($rv);
598 12         16 $rv =~ s|&|&|g;
599 12         12 $rv =~ s|"|"|g;
600 12         13 $rv =~ s|<|<|g;
601 12         14 $rv =~ s|>|>|g;
602 12         27 return $rv;
603             }
604             #
605             # mlesc
606             #-----------------------------------------------------------------------------------
607            
608            
609             #-----------------------------------------------------------------------------------
610             # get_allowed
611             #
612             # Private sub. Returns a hash ref of allowed fields if such an options was sent.
613             #
614             sub get_allowed {
615 10     10 0 13 my ($opts) = @_;
616 10 50       27 exists($opts->{'allowed'}) or return undef;
617            
618 0           my ($rv, %alluse);
619 0 0         ref($opts->{'allowed'}) or $opts->{'allowed'} = [$opts->{'allowed'}];
620            
621 0           @alluse{@{$opts->{'allowed'}}} = ();
  0            
622 0           $rv = \%alluse;
623 0           delete $opts->{'allowed'};
624 0           return $rv;
625             }
626             #
627             # get_allowed
628             #-----------------------------------------------------------------------------------
629            
630            
631             # return true
632             1;
633            
634             __END__