File Coverage

blib/lib/XML/Handler/ExtOn.pm
Criterion Covered Total %
statement 31 33 93.9
branch n/a
condition n/a
subroutine 11 11 100.0
pod n/a
total 42 44 95.4


line stmt bran cond sub pod time code
1             package XML::Handler::ExtOn;
2              
3             #$Id: ExtOn.pm 368 2008-11-24 09:55:03Z zag $
4              
5             =pod
6              
7             =head1 NAME
8              
9             XML::Handler::ExtOn - The handler for expansion of Perl SAX by objects.
10              
11             =head1 SYNOPSYS
12              
13             use XML::Handler::ExtOn;
14              
15             For write XML:
16              
17             use XML::Handler::ExtOn;
18             my $buf;
19             my $wrt = XML::SAX::Writer->new( Output => \$buf );
20             my $ex_parser = new XML::Handler::ExtOn:: Handler => $wrt;
21             $ex_parser->start_document;
22             my $root = $ex_parser->mk_element("Root");
23             $root->add_namespace(
24             "myns" => 'http://example.com/myns',
25             "myns_test", 'http://example.com/myns_test'
26             );
27             $ex_parser->start_element( $root );
28             my $el = $root->mk_element('vars');
29             %{ $el->attrs_by_prefix("myns") } = ( v1 => 1, v2 => 3 );
30             %{ $el->attrs_by_prefix("myns_test") } =
31             ( var1 => "test ns", var2 => "2333" );
32             $root->add_content($el);
33             $ex_parser->end_element;
34             $ex_parser->end_document;
35             print $buf;
36              
37             Result:
38              
39            
40            
41             xmlns:myns_test="http://example.com/myns_test">
42            
43             myns_test:var1="test ns"
44             myns:v1="1" myns:v2="3"/>
45            
46              
47             For handle events
48              
49             use base 'XML::Handler::ExtOn';
50              
51             Begin method for handle SAX event start_element:
52              
53             sub on_start_element {
54             my ( $self, $elem ) = @_;
55              
56             ...
57              
58             Check localname for element and add tag C:
59              
60             if ( $elem->local_name eq 'gallery' ) {
61             $elem->add_content(
62             $self->mk_element('image')->add_content(
63             $self->mk_characters( "Image number: $_" )
64             )
65             ) for 1..2 ;
66             }
67              
68             XML Before:
69              
70            
71            
72            
73            
74              
75             After:
76              
77            
78            
79            
80             Image number: 1
81             Image number: 2
82            
83            
84              
85             Register namespace and set variables
86              
87             $elem->add_namespace('demons','http://example.org/demo_namespace');
88             $elem->add_namespace('ns2','http://example.org/ns2');
89             #set attributes for name space
90             my $demo_attrs = $elem->attrs_by_prefix('demons');
91             %{$demo_attrs} = ( variable1=>1, 'variable2'=>2);
92             #set attributes for namespace URI
93             my $ns2_attrs = $elem->attrs_by_ns_uri('http://example.org/ns2');
94             %{$ns2_attrs} = ( var=> 'ns1', 'raw'=>2);
95              
96             Result:
97              
98            
99             xmlns:ns2="http://example.org/ns2"
100             demons:variable2="2" ns2:var="ns1"
101             demons:variable1="1" ns2:raw="2"/>
102              
103             Delete content of element
104              
105             if ( $elem->local_name eq 'demo_delete') {
106             $elem->skip_content
107             }
108              
109             XML before:
110              
111            
112            
113            
114            

text

115            
116            
117              
118             After:
119              
120            
121            
122            
123            
124              
125             Add XML:
126              
127             $elem->add_content (
128             $self->mk_from_xml('

text

')
129             )
130             Can add element after current
131              
132             ...
133             return [ $elem, $self->mk_element("after") ];
134             }
135              
136             =head1 DESCRIPTION
137              
138             XML::Handler::ExtOn - SAX Handler designed for funny work with XML. It
139             provides an easy-to-use interface for XML applications by adding objects.
140              
141             XML::Handler::ExtOn override some SAX events. Each time an SAX event starts,
142             a method by that name prefixed with `on_' is called with the B<"blessed">
143             Element object to be processed.
144              
145             XML::Handler::ExtOn implement the following methods:
146              
147             =over
148              
149             =item * on_start_document
150              
151             =item * on_start_prefix_mapping
152              
153             =item * on_start_element
154              
155             =item * on_end_element
156              
157             =item * on_characters
158              
159             =item * on_cdata
160              
161             =back
162              
163             XML::Handler::ExtOn put all B characters into a single event C.
164              
165             It compliant XML namespaces (http://www.w3.org/TR/REC-xml-names/), by support
166             I and I.
167              
168             XML::Handler::ExtOn provide methods for create XML, such as C, C ...
169              
170             =head1 FUNCTIONS
171              
172             =cut
173              
174 4     4   164692 use strict;
  4         10  
  4         183  
175 4     4   22 use warnings;
  4         7  
  4         124  
176              
177 4     4   23 use Carp;
  4         7  
  4         257  
178 4     4   21 use Data::Dumper;
  4         9  
  4         169  
179              
180 4     4   6236 use XML::SAX::Base;
  4         98342  
  4         167  
181 4     4   3224 use XML::Handler::ExtOn::Element;
  4         12  
  4         112  
182 4     4   2326 use XML::Handler::ExtOn::Context;
  4         14  
  4         135  
183 4     4   2839 use XML::Handler::ExtOn::IncXML;
  4         10  
  4         118  
184 4     4   3543 use XML::Filter::SAX1toSAX2;
  4         18135  
  4         179  
185 4     4   2600 use XML::Handler::ExtOn::SAX12ExtOn;
  4         11  
  4         120  
186 4     4   1842 use XML::Parser::PerlSAX;
  0            
  0            
187              
188             require Exporter;
189             *import = \&Exporter::import;
190             @XML::Handler::ExtOn::EXPORT_OK = qw( create_pipe );
191              
192             =head1 create_pipe "flt_n1",$some_handler, $out_handler
193              
194             use last arg as handler for out.
195              
196             return parser ref.
197              
198             my $h1 = new MyHandler1::;
199             my $filter = create_pipe( 'MyHandler1', $h1 );
200             $filter->parse('

TEST

');
201              
202             =cut
203              
204             sub create_pipe {
205             my @args =
206             reverse( "XML::Parser::PerlSAX", "XML::Handler::ExtOn::SAX12ExtOn", @_ );
207             my $out_handler = shift @args;
208             foreach my $f (@args) {
209             unless ( ref($f) ) {
210             $out_handler = $f->new( Handler => $out_handler );
211             } elsif ( UNIVERSAL::isa( $f, 'XML::SAX::Base')) {
212             $f->set_handler( $out_handler );
213             $out_handler = $f
214            
215             }
216             }
217             return $out_handler;
218             }
219              
220             use base 'XML::SAX::Base';
221             use vars qw( $AUTOLOAD);
222             $XML::Handler::ExtOn::VERSION = '0.06';
223             ### install get/set accessors for this object.
224             for my $key (qw/ context _objects_stack _cdata_mode _cdata_characters/) {
225             no strict 'refs';
226             *{ __PACKAGE__ . "::$key" } = sub {
227             my $self = shift;
228             $self->{___EXT_on_attrs}->{$key} = $_[0] if @_;
229             return $self->{___EXT_on_attrs}->{$key};
230             }
231             }
232              
233             =head1 METHODS
234              
235             =cut
236              
237             sub new {
238             my $class = shift;
239             my $self = &XML::SAX::Base::new( $class, @_, );
240             $self->_objects_stack( [] );
241             $self->_cdata_mode(0);
242             my $buf;
243             $self->_cdata_characters( \$buf ); #setup cdata buffer
244             my $doc_context = new XML::Handler::ExtOn::Context::;
245             $self->context($doc_context);
246             return $self;
247             }
248              
249             =head2 on_start_document $document
250              
251             Method handle C event. Usually override for initialaize default
252             variables.
253              
254             sub on_start_document {
255             my $self = shift;
256             $self->{_LINKS_ARRAY} = [];
257             $self->SUPER::on_start_document(@_);
258             }
259              
260             =cut
261              
262             sub on_start_document {
263             my ( $self, $document ) = @_;
264             $self->SUPER::start_document($document);
265             }
266              
267             sub start_document {
268             my ( $self, $document ) = @_;
269             return if $self->{___EXT_on_attrs}->{_skip_start_docs}++;
270             $self->on_start_document($document);
271             }
272              
273             sub end_document {
274             my $self = shift;
275             my $var = --$self->{___EXT_on_attrs}->{_skip_start_docs};
276             return if $var;
277             $self->SUPER::end_document(@_);
278             }
279              
280             =head2 on_start_prefix_mapping prefix1=>ns_uri1[, prefix2=>ns_uri2]
281              
282             Called on C event.
283              
284             sub on_start_prefix_mapping {
285             my $self = shift;
286             my %map = @_;
287             $self->SUPER::start_prefix_mapping(@_)
288             }
289              
290             =cut
291              
292             sub on_start_prefix_mapping {
293             my $self = shift;
294             my %map = @_;
295             while ( my ( $pref, $ns_uri ) = each %map ) {
296             $self->add_namespace($pref, $ns_uri);
297             $self->SUPER::start_prefix_mapping(
298             {
299             Prefix => $pref,
300             NamespaceURI => $ns_uri
301             }
302             );
303             }
304             }
305              
306             #
307             # { Prefix => 'xlink', NamespaceURI => 'http://www.w3.org/1999/xlink' }
308             #
309              
310             sub start_prefix_mapping {
311             my $self = shift;
312              
313             #declare namespace for current context
314             # my $context = $self->context;
315             # if ( my $current = $self->current_element ) {
316             # $context = $current->ns;
317             # }
318             my %map = ();
319             foreach my $ref (@_) {
320             my ( $prefix, $ns_uri ) = @{$ref}{qw/Prefix NamespaceURI/};
321             # $context->declare_prefix( $prefix, $ns_uri );
322             $map{$prefix} = $ns_uri;
323             }
324             $self->on_start_prefix_mapping(%map);
325             }
326              
327             =head2 on_start_element $elem
328              
329             Method handle C event whith XML::Handler::ExtOn::Element object.
330              
331             Method must return C<$elem> or ref to array of objects.
332              
333             For example:
334              
335             sub on_start_element {
336             my $self = shift;
337             my $elem = shift;
338             $elem->add_content( $self->mk_cdata("test"));
339             return $elem
340             }
341             ...
342            
343             return [ $elem, ,$self->mk_element("after_start_elem") ]
344            
345             return [ $self->mk_element("before_start_elem"), $elem ]
346             ...
347              
348             =cut
349              
350             sub on_start_element {
351             shift;
352             return [@_];
353             }
354              
355             sub start_element {
356             my $self = shift;
357             my $data = shift;
358              
359             #check current element for skip_content
360             if ( my $current_element = $self->current_element ) {
361             my $skip_content = $current_element->is_skip_content;
362             if ($skip_content) {
363             $current_element->is_skip_content( ++$skip_content );
364             return;
365             }
366             }
367             my $current_obj =
368             UNIVERSAL::isa( $data, 'XML::Handler::ExtOn::Element' )
369             ? $data
370             : $self->__mk_element_from_sax2($data);
371             my $res = $self->on_start_element($current_obj);
372             my @stack = $res
373             ? ref($res) eq 'ARRAY' ? @{$res} : ($res)
374             : ();
375             push @stack, $current_obj;
376             my %uniq = ();
377              
378             #process answer
379             foreach my $elem (@stack) {
380              
381             #clean dups
382             next if $uniq{$elem}++;
383             unless ( $elem eq $current_obj ) {
384              
385             # warn "++".$elem->local_name;
386             $self->_process_comm($elem);
387             }
388             else {
389              
390             my $res_data = $self->__exp_element_to_sax2($current_obj);
391              
392             #register new namespaces
393             my $changes = $current_obj->ns->get_changes;
394             my $parent_map = $current_obj->ns->parent->get_map;
395              
396             #warn Dumper( { changes => $changes } );
397             for ( keys %$changes ) {
398              
399             # $self->SUPER::end_prefix_mapping(
400             $self->end_prefix_mapping(
401             {
402             Prefix => $_,
403             NamespaceURI => $parent_map->{$_},
404             }
405             )
406             if exists $parent_map->{$_};
407              
408             # $self->SUPER::start_prefix_mapping(
409             $self->start_prefix_mapping(
410             {
411             Prefix => $_,
412             NamespaceURI => $changes->{$_},
413             }
414             );
415             }
416              
417             #save element in stack
418             push @{ $self->_objects_stack() }, $current_obj;
419              
420             #skip deleted elements from xml stream
421             $self->SUPER::start_element($res_data)
422             unless $current_obj->is_delete_element;
423             unless ( $current_obj->is_skip_content ) {
424             $self->_process_comm($_) for @{ $current_obj->_stack };
425             $current_obj->_stack( [] );
426             }
427             }
428              
429             }
430             }
431              
432             =head2 on_end_element $elem
433              
434             Method handle C event whith XML::Handler::ExtOn::Element object.
435             It call before end if element.
436              
437             Method must return C<$elem> or ref to array of objects.
438              
439             For example:
440              
441             sub on_end_element {
442             my $self = shift;
443             my $elem = shift;
444             if ( $elem->is_delete_element ) {
445             warn $elem->local_name . " deleted";
446             return [ $elem, $self->mk_element("after_deleted_elem") ]
447             };
448             return $elem
449             }
450             ...
451            
452             return [ $elem, ,$self->mk_element("after_close_tag_of_elem") ]
453            
454             return [ $self->mk_element("before_close_tag_of_elem"), $elem ]
455             ...
456              
457             =cut
458              
459             sub on_end_element {
460             shift;
461             return [@_];
462             }
463              
464             sub end_element {
465             my $self = shift;
466             my $data = shift;
467              
468             #check current element for skip_content
469             if ( my $current_element = $self->current_element ) {
470             my $skip_content = $current_element->is_skip_content;
471             if ( $skip_content > 1 ) {
472             $current_element->is_skip_content( --$skip_content );
473             return;
474             }
475             }
476              
477             # warn Dumper($data);
478             #pop element from stack
479             my $current_obj = pop @{ $self->_objects_stack() };
480              
481             #setup default ns
482             $data = $current_obj->to_sax2;
483             delete $data->{Attributes};
484             $data->{NamespaceURI} = $current_obj->default_uri;
485              
486             my $res = $self->on_end_element($current_obj);
487             my @stack = $res
488             ? ref($res) eq 'ARRAY' ? @{$res} : ($res)
489             : ();
490             push @stack, $current_obj;
491             my %uniq = ();
492              
493             #process answer
494             foreach my $elem (@stack) {
495              
496             #clean dups
497             next if $uniq{$elem}++;
498             unless ( $elem eq $current_obj ) {
499             $self->_process_comm($elem);
500             }
501             else {
502             unless ( $current_obj->is_skip_content ) {
503             $self->_process_comm($_) for @{ $current_obj->_stack };
504             $current_obj->_stack( [] );
505             }
506             $self->SUPER::end_element($data)
507             unless $current_obj->is_delete_element;
508             my $changes = $current_obj->ns->get_changes;
509             my $parent_map = $current_obj->ns->parent->get_map;
510             for ( keys %$changes ) {
511             $self->end_prefix_mapping(
512             {
513             Prefix => $_,
514             NamespaceURI => $changes->{$_},
515             }
516             );
517             if ( exists( $parent_map->{$_} ) ) {
518             $self->start_prefix_mapping(
519             {
520             Prefix => $_,
521             NamespaceURI => $parent_map->{$_},
522             }
523             );
524             }
525             }
526             }
527             }
528             }
529              
530             =head2 on_characters( $self->current_element, $data->{Data} )
531              
532             Must return string for write to stream.
533              
534             sub on_characters {
535             my ( $self, $elem, $str ) = @_;
536             #lowercase all characters
537             return lc $str;
538             }
539              
540              
541             =cut
542              
543             sub on_characters {
544             my ( $self, $elem, $str ) = @_;
545             return $str;
546             }
547              
548             =head2 on_cdata ( $current_element, $data )
549              
550             Must return string for write to stream
551              
552             sub on_cdata {
553             my ( $self, $elem, $str ) = @_;
554             return lc $str;
555             }
556              
557             =cut
558              
559             sub on_cdata {
560             my ( $self, $elem, $str ) = @_;
561             return $str;
562             }
563              
564             #set flag for cdata content
565              
566             sub start_cdata {
567             my $self = shift;
568             $self->_cdata_mode(1);
569             return;
570             }
571              
572             #set flag to end cdata
573              
574             sub end_cdata {
575             my $self = shift;
576             if ( my $elem = $self->current_element
577             and defined( my $cdata_buf = ${ $self->_cdata_characters } ) )
578             {
579             if ( defined( my $data = $self->on_cdata( $elem, $cdata_buf ) ) ) {
580             $self->SUPER::start_cdata;
581             $self->SUPER::characters( { Data => $data } );
582             $self->SUPER::end_cdata;
583             }
584             }
585              
586             #after all clear cd_data_buffer and reset cd_data mode flag
587             my $new_buf;
588             $self->_cdata_characters( \$new_buf );
589             $self->_cdata_mode(0);
590             return;
591             }
592              
593             sub characters {
594             my $self = shift;
595             my ($data) = @_;
596             #skip childs elements characters ( > 1 ) and self text ( > 0)
597             # warn $self.Dumper([ map {[caller($_)]} (1..10)]) unless $self->current_element;
598             if ( $self->current_element ) {
599             return if $self->current_element->is_skip_content;
600             }
601             else {
602              
603             #skip characters without element
604             return
605              
606             # #warn "characters without element"
607             }
608              
609             #for cdata section collect characters in buffer
610             if ( $self->_cdata_mode ) {
611             ${ $self->_cdata_characters } .= $data->{Data};
612             return;
613             }
614              
615             #collect chars fo current element
616             if (
617             defined(
618             my $str =
619             $self->on_characters( $self->current_element, $data->{Data} )
620             )
621             )
622             {
623             return $self->SUPER::characters( { Data => $str } );
624             }
625             }
626              
627             =head2 mk_element
628              
629             Return object of element item for include to stream.
630              
631             =cut
632              
633             sub mk_element {
634             my $self = shift;
635             my $name = shift;
636             my %args = @_;
637             if ( my $current_element = $self->current_element ) {
638             $args{context} = $current_element->ns->sub_context();
639             }
640             $args{context} ||= $self->context->sub_context();
641             my $elem = new XML::Handler::ExtOn::Element::
642             name => $name,
643             %args;
644             return $elem;
645             }
646              
647             =head2 mk_from_xml
648              
649             Return command for include to stream.
650              
651             =cut
652              
653             sub mk_from_xml {
654             my $self = shift;
655             my $string = shift;
656             my $skip_tmp_root = XML::Handler::ExtOn::IncXML->new( Handler => $self );
657             my $sax2_filter = XML::Filter::SAX1toSAX2->new( Handler => $skip_tmp_root );
658             my $parser = XML::Parser::PerlSAX->new(
659             {
660             Handler => $sax2_filter,
661             Source => { String => "$string" }
662             }
663             );
664             return $parser;
665             }
666              
667             =head2 mk_cdata $string | \$string
668              
669             return command for insert cdata to stream
670              
671             =cut
672              
673             sub mk_cdata {
674             my $self = shift;
675             my $string = shift;
676             return { type => 'CDATA', data => ref($string) ? $string : \$string };
677             }
678              
679             =head2 mk_characters $string | \$string
680              
681             return command for insert characters to stream
682              
683             =cut
684              
685             sub mk_characters {
686             my $self = shift;
687             my $string = shift;
688             return { type => 'CHARACTERS', data => ref($string) ? $string : \$string };
689             }
690              
691             sub __mk_element_from_sax2 {
692             my $self = shift;
693             my $data = shift;
694             my $elem = $self->mk_element( $data->{LocalName}, sax2 => $data, @_ );
695             return $elem;
696             }
697              
698             sub __exp_element_to_sax2 {
699             my $self = shift;
700             my $elem = shift;
701             return $elem->to_sax2;
702             }
703              
704             =head2 current_element
705              
706             Return link to current processing element.
707              
708             =cut
709              
710             sub current_element {
711             my $self = shift;
712             if ( my $stack = $self->_objects_stack() ) {
713             return $stack->[-1];
714             }
715             return;
716             }
717              
718             # Private method for process commands
719              
720             sub _process_comm {
721             my $self = shift;
722             my $comm = shift || return;
723             if ( UNIVERSAL::isa( $comm, 'XML::Parser::PerlSAX' ) ) {
724             $comm->parse;
725             }
726             elsif ( UNIVERSAL::isa( $comm, 'XML::Handler::ExtOn::Element' ) ) {
727             $self->start_element($comm);
728              
729             while ( my $obj = shift @{ $comm->_stack } ) {
730             $self->_process_comm($obj);
731             }
732             $self->end_element($comm);
733             }
734             elsif ( ref($comm) eq 'HASH' and exists $comm->{type} ) {
735             if ( $comm->{type} eq 'CDATA' ) {
736             $self->start_cdata;
737             $self->characters( { Data => ${ $comm->{data} } } );
738             $self->end_cdata;
739             }
740             elsif ( $comm->{type} eq 'CHARACTERS' ) {
741             $self->characters( { Data => ${ $comm->{data} } } );
742             }
743             }
744             else {
745             warn " Unknown DATA $comm";
746             }
747             }
748              
749             =head2 add_namespace => , [ => , ... ]
750              
751             Add Namespace mapping. return C<$self>
752              
753             If C eq '', this namespace will then apply to all elements
754             that have no prefix.
755              
756             $elem->add_namespace(
757             "myns" => 'http://example.com/myns',
758             "myns_test", 'http://example.com/myns_test',
759             ''=>'http://example.com/new_default_namespace'
760             );
761              
762             =cut
763              
764             sub add_namespace {
765             my $self = shift;
766             my $context = $self->context;
767             if ( my $current = $self->current_element ) {
768             $context = $current->ns;
769             }
770             my %map = @_;
771             while ( my ($prefix, $ns_uri ) = each %map ) {
772             $context->declare_prefix( $prefix, $ns_uri );
773             }
774             }
775              
776             1;
777             __END__