File Coverage

blib/lib/XML/Flow.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package XML::Flow;
2              
3             #$Id: Flow.pm 833 2010-08-24 12:23:53Z zag $
4              
5             =pod
6              
7             =head1 NAME
8              
9             XML::Flow - Store (restore) perl data structures in XML stream.
10              
11             =head1 SYNOPSIS
12              
13             #read - write by imported functions ref2xml() and xml2ref()
14             use XML::Flow qw( ref2xml xml2ref);
15             my $data = {1=>2,4=>[1,2,3]};
16             my $xml_string = ref2xml($data);
17             my $data_restored = xml2ref($xml_string);
18             my $ref1 = xml2ref(\*DATA); #from embedded __DATA__
19              
20             #Write XML
21             use XML::Flow;
22             my $wr = new XML::Flow:: "test.xml";
23             $wr->startTag("Root"); #start root tag
24             $wr->startTag("Data");
25             $wr->write({1=>2},[4..6]);
26             $wr->closeTag("Data");
27             $wr->closeTag("Root");
28             $wr->close;
29              
30              
31             #Read
32             my $fs = new IO::File:: "
33             my $rd = new XML::Flow:: $fs;
34             my %tags = (
35             Root=>undef,
36             Data=>sub { print Dumper(\@_) },
37             );
38             $rd->read(\%tags);
39             $fs->close;
40              
41             =head1 DESCRIPTION
42              
43             Easy store and restore perl data structures. It use XML::Parser for read and XML::Writer for write
44             xml.
45              
46             =cut
47              
48 7     7   268798 use XML::Parser;
  0            
  0            
49             use XML::Writer;
50             use IO::File;
51             use Data::Dumper;
52             use warnings;
53             use Carp;
54             use Encode;
55             use strict;
56             require Exporter;
57             *import = \&Exporter::import;
58             @XML::Flow::EXPORT_OK = qw(ref2xml xml2ref);
59             $XML::Flow::VERSION = '0.86';
60             my $attrs = {
61             _file => undef,
62             _file_handle => undef,
63             _writer => undef,
64             _events => {},
65             _need_close => undef
66             };
67             ### install get/set accessors for this object.
68             for my $key ( keys %$attrs ) {
69             no strict 'refs';
70             *{ __PACKAGE__ . "::$key" } = sub {
71             my $self = shift;
72             $self->{$key} = $_[0] if @_;
73             return $self->{$key};
74             }
75             }
76              
77             =head1 FUNCTIONS
78              
79             =cut
80              
81             =head2 ref2xml( $ref )
82              
83             Serilize reference to XML string. Where $ref is reference to SCALAR, HASH or ARRAY. This function will return XML string.
84              
85             use XML::Flow qw( ref2xml xml2ref);
86             my $test = {1=>2,4=>[1,2,3]};
87             print ref2xml($test);
88              
89             The above example would print out the message:
90              
91            
92            
93            
94            
95            
96            
97             2
98             1
99             3
100            
101            
102             2
103            
104            
105            
106              
107             =cut
108              
109             sub ref2xml {
110             my $ref = shift || return;
111             my $result;
112             my $flow = ( new XML::Flow:: \$result );
113             $flow->startTag("XML-FLow-Data");
114             $flow->write($ref);
115             $flow->endTag("XML-FLow-Data");
116             return $result;
117             }
118              
119             =head2 xml2ref($string || reference to GLOB)
120              
121             This function will deserilize string generated by ref2xml.Return reference.
122             For example:
123              
124             use XML::Flow qw( ref2xml xml2ref);
125             use Data::Dumper;
126             my $testxml = q{
127            
128            
129            
130            
131            
132             2
133             1
134             3
135            
136            
137             2
138            
139            
140             };
141             print Dumper(xml2ref($testxml))
142              
143             will print:
144              
145             $VAR1 = {
146             '1' => '2',
147             '4' => [
148             '1',
149             '2',
150             '3'
151             ]
152             };
153              
154             =cut
155              
156             sub xml2ref {
157             my $xml = shift || return;
158             my $result;
159             my $flow = new XML::Flow:: ref($xml) ? $xml : \$xml;
160             $flow->read(
161             {
162             'XML-FLow-Data' => sub { shift; ($result) = @_ }
163             }
164             );
165             return $result;
166             }
167              
168             =head1 METHODS
169              
170             =cut
171              
172             =head2 new($filehandle|$filename| a reference to a text string )
173              
174             Create a new XML::Flow object. The first parameter should either be a string containing filename, a reference to a text string or it should be an open IO::Handle. For example:
175              
176             my $wr = new XML::Flow:: "test.xml";
177              
178             or
179              
180             my $rd = new XML::Flow:: \$string_with_xml;
181              
182             or
183              
184             my $fs = new IO::File:: "
185             my $rd = new XML::Flow:: $fs;
186              
187             or
188              
189             my $fz = IO::Zlib->new($file, "wb9");
190             my $wr = new XML::Flow:: $fz;
191              
192             or
193              
194             my $string_for_write_xml;
195             my $wr = new XML::Flow:: \$string_buffer_for_write_xml;
196              
197              
198             =cut
199              
200             sub new {
201             my $class = shift;
202             $class = ref $class if ref $class;
203             my $self = bless( {}, $class );
204             if (@_) {
205             my $file = shift;
206             if (
207             ref $file
208             and
209             ( UNIVERSAL::isa( $file, 'IO::Handle' ) or ( ref $file ) eq 'GLOB' )
210             or UNIVERSAL::isa( $file, 'Tie::Handle' )
211             )
212             {
213             $self->_file_handle($file);
214             }
215             else {
216             $self->_file($file);
217             }
218             }
219             else {
220             carp "need filename or filehandle";
221             return;
222             }
223             return $self;
224             }
225              
226             sub _get_handle {
227             my $self = shift;
228             my $mode = shift;
229             unless ( $self->_file_handle ) {
230             return if ref( $self->_file ) eq 'SCALAR';
231             $self->_file_handle(
232             new IO::File::( $mode ? ">" : "<" ) . $self->_file );
233             $self->_need_close(1); #close FH when close
234             }
235             return $self->_file_handle;
236             }
237              
238             sub _get_writer {
239             my $self = shift;
240             unless ( $self->_writer ) {
241             my $fh = $self->_get_handle(1) || $self->_file;
242             my $writer = new XML::Writer:: OUTPUT => $fh;
243             $writer->xmlDecl("UTF-8");
244             $self->_writer($writer)
245              
246             }
247             return $self->_writer;
248             }
249              
250             =head2 startTag($name [, $aname1 => $value1, ...])
251              
252             Add a start tag to an XML document. This method is wraper for XML::Writer::startTag.
253              
254              
255             =cut
256              
257             sub startTag {
258             my $self = shift;
259             my $writer = $self->_get_writer;
260             return $writer->startTag(@_);
261             }
262              
263             sub closeTag {
264             my $self = shift;
265             my $writer = $self->_get_writer;
266             return $writer->endTag(@_);
267             }
268              
269             =head2 endTag([$name])
270              
271             Add a end tag to an XML document. This method is wraper for XML::Writer::endTag.
272              
273              
274             =cut
275              
276             sub endTag {
277             my $self = shift;
278             my $writer = $self->_get_writer;
279             return $writer->endTag(@_);
280             }
281              
282             sub __ref2xml {
283             my $self = shift;
284             my $writer = shift;
285             my $ref = shift;
286             return unless ref $ref;
287             my $type = 'hashref';
288             my $res_as_hash = $ref;
289             if ( ref $ref eq 'ARRAY' ) {
290             $res_as_hash = {};
291             my $key = 0;
292             foreach my $val (@$ref) {
293             $res_as_hash->{ $key++ } = $val;
294             }
295             $type = 'arrayref';
296             }
297             if ( ref $ref eq 'SCALAR' ) {
298             $res_as_hash = {};
299             $res_as_hash->{scalar} = $$ref;
300             $type = 'scalarref';
301             }
302              
303             $writer->startTag( 'value', type => $type );
304             while ( my ( $key, $val ) = each %$res_as_hash ) {
305             unless ( defined $val ) {
306             $writer->startTag( 'key', name => $key, value => "undef" );
307             $writer->endTag('key');
308             next;
309             }
310             $writer->startTag( 'key', name => $key );
311             if ( ref($val) ) {
312             $self->__ref2xml( $writer, $val );
313             }
314             else {
315             $writer->characters( $self->_utfx2utf($val) );
316             }
317             $writer->endTag('key');
318             }
319             $writer->endTag('value');
320             }
321              
322             sub _utfx2utf {
323             my ( $self, $str ) = @_;
324             $str = encode( 'utf8', $str ) if utf8::is_utf8($str);
325             return $str;
326             }
327              
328             sub _utf2utfx {
329             my ( $self, $str ) = @_;
330             $str = decode( 'utf8', $str ) unless utf8::is_utf8($str);
331             return $str;
332             }
333              
334             =head2 write($ref1[, $ref2, ...])
335              
336             Serilize references to XML. Where $ref is reference to SCALAR, HASH or ARRAY. This method used only for write XML mode.
337              
338             $wr->write({1=>2},[4..6]);
339             my $a="1";
340             $wr->write(\$a);
341              
342             =cut
343              
344             sub write {
345             my $self = shift;
346             my $writer = $self->_get_writer;
347             foreach (@_) {
348             $writer->startTag('flow_data_struct');
349             $self->__ref2xml( $writer, $_ );
350             $writer->endTag('flow_data_struct');
351              
352             }
353             return;
354             }
355              
356             sub _xml2hash_handler {
357             my $self = shift;
358             my ( $struct, $data, $elem, %attr ) = @_;
359             my ( $state, $shared ) = @{$struct}{ 'state', 'shared' };
360             my $tag_stack = $shared->{tag_stack} || [];
361             $shared->{tag_stack} = $tag_stack;
362             for ($state) {
363              
364             /1/ && do {
365             my $new = { name => $elem, 'attr' => \%attr };
366             push @$tag_stack, $new;
367             if ( $elem eq 'value' ) {
368             $new->{type} = $attr{type};
369             for ( $new->{type} ) {
370             /hashref/ && do { $new->{value} = {} }
371             || /arrayref/ && do { $new->{value} = [] }
372             }
373             }
374             }
375             || /2/ && do {
376             if ( my $current = pop @{$tag_stack} ) {
377             push @{$tag_stack}, $current;
378             if ( $current->{name} eq 'key' ) {
379             unless ( ref $current->{value} ) {
380             $current->{value} .= $elem;
381             return; #clear return value
382             }
383             }
384              
385             }
386              
387             }
388             || /3/ && do {
389             if ( my $current = pop @{$tag_stack} ) {
390             my $parent = pop @{$tag_stack};
391             die "Stack error " . Dumper() unless $current->{name} eq $elem;
392             if ( $elem eq 'key' ) {
393             push @{$tag_stack}, $parent;
394             my $ref_val;
395             if ( exists $current->{attr}->{value}
396             and $current->{attr}->{value} eq 'undef' )
397             {
398             $current->{value} = undef;
399             }
400             else {
401             $current->{value} = '' unless defined $current->{value};
402             }
403             for ( $parent->{type} ) {
404             /hashref/ && do {
405             $parent->{value} ||= {};
406             $parent->{value}->{ $current->{attr}->{name} } =
407             $current->{value};
408             }
409             || /arrayref/ && do {
410             $parent->{value} ||= [];
411             ${ $parent->{value} }[ $current->{attr}->{name} ] =
412             $current->{value};
413             }
414             || /scalarref/ && do {
415             $parent->{value} = \$current->{value};
416             }
417             }
418              
419             }
420             elsif ( $elem eq 'value' ) {
421             if ($parent) {
422             push @{$tag_stack}, $parent;
423             $parent->{value} = $current->{value};
424             }
425             else {
426             $self->_parse_stream( { %$struct, state => 4 },
427             $current->{value} );
428             }
429              
430             }
431              
432             }
433             else { die "empty stack !" . Dumper( \@_ ) }
434             }
435             } #for
436             } #sub
437              
438             sub _parse_stream {
439             my $self = shift;
440             my ( $struct, $data, $elem, %attr ) = @_;
441             my ( $state, $shared, $tags ) = @{$struct}{ 'state', 'shared', 'tags' };
442             my $have_default = exists( $tags->{'*'} );
443             my $stream_stack = $shared->{stream_stack} || [];
444             $shared->{stream_stack} = $stream_stack;
445             if ( $state == 4 ) {
446             my $current = pop @{$stream_stack};
447             push @{ $current->{value} }, $data;
448             push @{$stream_stack}, $current;
449             $self->_events(
450             {
451             'curr' => sub { $self->_parse_stream(@_) }
452             }
453             );
454             return;
455             }
456             if ( $elem eq 'flow_data_struct' ) {
457             if ( $state == 1 ) {
458             $self->_events(
459             {
460             'curr' => sub { $self->_xml2hash_handler(@_) }
461             }
462             );
463             }
464             else {
465              
466             # Close flow;
467             }
468             return;
469             }
470             if ( $state == 2 && ( my $current = pop @{$stream_stack} ) ) {
471             unless ( exists $current->{fake} ) {
472             $current->{text} = '' unless exists $current->{text};
473             $current->{text} .= $elem;
474             }
475             push @{$stream_stack}, $current;
476             }
477              
478             if ( $state == 1 ) {
479             push @{$stream_stack},
480             exists( $tags->{$elem} )
481             || $have_default
482             ? { name => $elem, attr => \%attr }
483             : { fake => 1 };
484             }
485             if ( $state == 3 ) {
486             my $current = pop @{$stream_stack};
487             my $handler; #handler for tag
488             my $default_handler_selected = 0;
489             unless ($have_default) {
490             return unless defined( $tags->{$elem} );
491             return unless $handler = $tags->{ $current->{name} };
492             }
493             else {
494             unless ( $handler = $tags->{ $current->{name} } ) {
495             $handler = $tags->{'*'};
496             $default_handler_selected = 1;
497             }
498             }
499             print 'ERROR stack for ' . $elem . "->" . $current->{name}
500             unless $current->{name} eq $elem;
501              
502             #before call handler push to stack text values
503             my $text = delete $current->{text};
504              
505             # not save format text
506             push @{ $current->{value} }, $text
507             if defined $text && $text !~ /^\s+$/s;
508             my @res = (
509             $handler->(
510             $default_handler_selected
511             ? ( $current->{name} )
512             : ( ), $current->{attr},
513             ref( $current->{value} ) ? @{ $current->{value} }
514             : defined( $current->{text} ) ? $current->{text}
515             : ()
516             )
517             );
518             if ( my $parent = pop @{$stream_stack} ) {
519             if ( scalar @res && not exists $parent->{fake} ) {
520              
521             # store braked chars streams to values
522             # text text some continued text
523             my $text = delete $parent->{text};
524              
525             # not save format text
526             push @{ $parent->{value} }, $text
527             if defined $text && $text !~ /^\s+$/s;
528             push @{ $parent->{value} }, @res;
529             }
530             push @{$stream_stack}, $parent;
531             }
532             }
533             }
534              
535             sub _handle_ev {
536             my $self = shift;
537             my $events = $self->_events;
538             return $events->{'curr'}->(@_);
539             }
540              
541             =head2 read({tag1=>sub1{}[, tag2=>\&sub2 })
542              
543             Run XML parser. Argument is a reference to hash with tag => handler.
544             If handler eq undef, then tag ignore. If subroutine return non undef result, it passed to parent
545             tag handler. Handler called with args: ( {hash of attributes}, [,] ).
546             For example:
547              
548             Source xml :
549              
550            
551            
552            
553            
554            
555            
556             3
557            
558            
559            
560            
561            
562            
563            
564            
565            
566            
567              
568             Read code:
569              
570             my $rd = new XML::Flow:: "test.xml";
571             my %tags = (
572             Root=>undef,
573             Obj=>sub { print Dumper(\@_) },
574             Also=>sub {
575             shift; #reference to hash of attributes
576             return @_},
577             );
578             $rd->read(\%tags);
579             $rd->close;
580              
581             Output:
582              
583             $VAR1 = [
584             {}, #reference to hash of xml tag attributes
585             \'3',
586             {
587             '1' => undef
588             }
589             ];
590              
591             =cut
592              
593             sub read {
594             my $self = shift;
595             my $tags = shift or return;
596             $self->_events(
597             {
598             'curr' => sub { $self->_parse_stream(@_) }
599             }
600             );
601             my $shared = {};
602             my $parser = new XML::Parser(
603             Handlers => {
604             Start => sub {
605             $self->_handle_ev(
606             { state => 1, shared => $shared, tags => $tags }, @_ );
607             },
608             Char => sub {
609             $self->_handle_ev(
610             { state => 2, shared => $shared, tags => $tags }, @_ );
611             },
612             End => sub {
613             $self->_handle_ev(
614             { state => 3, shared => $shared, tags => $tags }, @_ );
615             },
616             }
617             );
618             $parser->parse( $self->_get_handle() || ${ $self->_file } );
619             }
620              
621             =head2 close()
622              
623             Close all handlers (including internal).
624              
625             =cut
626              
627             sub close {
628             my $self = shift;
629             $self->_file_handle->close if $self->_need_close and $self->_file_handle;
630             }
631              
632             1;
633             __END__