File Coverage

blib/lib/XML/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::ExtOn;
2              
3             #$Id: ExtOn.pm 966 2011-08-07 18:07:19Z zag $
4              
5             =pod
6              
7             =head1 NAME
8              
9             XML::ExtOn - The handler for expansion of Perl SAX by objects.
10              
11             =head1 SYNOPSYS
12              
13             use XML::ExtOn;
14              
15             For write XML:
16              
17             use XML::ExtOn;
18             my $buf;
19             my $wrt = XML::ExtOn::Writer->new( Output => \$buf );
20             my $ex_parser = new XML::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::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::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::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::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::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::ExtOn provide methods for create XML, such as C, C ...
169              
170             =head1 FUNCTIONS
171              
172             =cut
173              
174 6     6   239503 use strict;
  6         18  
  6         290  
175 6     6   36 use warnings;
  6         15  
  6         4151  
176              
177 6     6   46 use Carp;
  6         10  
  6         453  
178 6     6   35 use Data::Dumper;
  6         10  
  6         243  
179              
180 6     6   15030 use XML::SAX::Base;
  6         143837  
  6         220  
181 6     6   4352 use XML::ExtOn::Element;
  6         20  
  6         155  
182 6     6   3219 use XML::ExtOn::Context;
  6         19  
  6         199  
183 6     6   3301 use XML::ExtOn::IncXML;
  6         13  
  6         154  
184 6     6   5118 use XML::Filter::SAX1toSAX2;
  6         26977  
  6         184  
185 6     6   3260 use XML::ExtOn::SAX12ExtOn;
  6         17  
  6         166  
186 6     6   2622 use XML::Parser::PerlSAX;
  0            
  0            
187             use Test::More;
188              
189             require Exporter;
190             *import = \&Exporter::import;
191             @XML::ExtOn::EXPORT_OK = qw( create_pipe split_pipe);
192              
193             sub _get_end_handler {
194             my $flt = shift;
195             my $handler = $flt->get_handler();
196              
197             return $handler if UNIVERSAL::isa( $handler, 'XML::ExtOn::Writer' );
198             return $handler if UNIVERSAL::isa( $handler, 'XML::SAX::Writer::XML' );
199             return $flt unless UNIVERSAL::isa( $handler, 'XML::SAX::Base' );
200             return &_get_end_handler($handler);
201             }
202              
203             =head1 create_pipe "flt_n1",$some_handler, $out_handler
204              
205             use last arg as handler for out.
206              
207             return parser ref.
208              
209             my $h1 = new MyHandler1::;
210             my $filter = create_pipe( 'MyHandler1', $h1 );
211             $filter->parse('

TEST

');
212             #also create pipe of pipes
213             my $filter1 = create_pipe( 'MyHandler1', 'MyHandler2' );
214             my $h1 = new MyHandler3::;
215             my $filter2 = create_pipe( $filter1, $h1);
216              
217             =cut
218              
219             sub create_pipe {
220              
221             my @args = reverse @_;
222              
223             my $out_handler;
224             foreach my $f (@args) {
225             unless ( ref($f) ) {
226             unless ($out_handler) {
227             $out_handler = $f->new();
228             }
229             else {
230             $out_handler = $f->new( Handler => $out_handler );
231             }
232             }
233             elsif ( UNIVERSAL::isa( $f, 'XML::SAX::Base' ) ) {
234             unless ($out_handler) {
235             $out_handler = $f;
236             }
237             else {
238             my $end_handler = &_get_end_handler($f);
239             $end_handler->set_handler($out_handler);
240             $out_handler = $f;
241             }
242             }
243             else {
244             die "$f not SAX Drv";
245             }
246             }
247             return $out_handler;
248             }
249              
250             =head1 split_pipe $filter
251              
252             return ref to array of filters in pipe
253              
254              
255             use XML::ExtOn qw(split_pipe create_pipe);
256             my $filter = create_pipe( 'MyHandler1', 'MyHandler2','MyHandler3');
257             my $ref = @{ split_pipe( $filter) } [-1];
258             isa_ok $ref, 'MyHandler3', 'check last element';
259              
260             =cut
261              
262             sub split_pipe {
263             my $filter = shift || return [];
264             my @res = ($filter);
265              
266             # use SAXed variable see XML::SAX::Base::get_handler()
267             if ( my $next = $filter->{Handler} ) {
268             #skip special SAX handlers
269             unless ( UNIVERSAL::isa( $next, 'XML::SAX::Base::NoHandler' ) ) {
270             push @res, @{ split_pipe($next) };
271             }
272             }
273             return \@res;
274             }
275              
276             use base 'XML::SAX::Base';
277             use vars qw( $AUTOLOAD);
278             $XML::ExtOn::VERSION = '0.17';
279             ### install get/set accessors for this object.
280             for my $key (
281             qw/ context _objects_stack _cdata_mode _cdata_characters _root_stack /)
282             {
283             no strict 'refs';
284             *{ __PACKAGE__ . "::$key" } = sub {
285             my $self = shift;
286             $self->{___EXT_on_attrs}->{$key} = $_[0] if @_;
287             return $self->{___EXT_on_attrs}->{$key};
288             }
289             }
290              
291             =head1 METHODS
292              
293             =cut
294              
295             sub new {
296             my $class = shift;
297             my $self = &XML::SAX::Base::new( $class, @_, );
298             $self->_objects_stack( [] );
299             $self->_root_stack( [] ); #init incoming stack of start end
300             $self->_cdata_mode(0);
301             my $buf;
302             $self->_cdata_characters( \$buf ); #setup cdata buffer
303             my $doc_context = new XML::ExtOn::Context::;
304             $self->context($doc_context);
305             return $self;
306             }
307              
308             =head2 on_start_document $document
309              
310             Method handle C event. Usually override for initialaize default
311             variables.
312              
313             sub on_start_document {
314             my $self = shift;
315             $self->{_LINKS_ARRAY} = [];
316             $self->SUPER::on_start_document(@_);
317             }
318              
319             =cut
320              
321             sub on_start_document {
322             my ( $self, $document ) = @_;
323             $self->SUPER::start_document($document);
324             }
325              
326             sub start_document {
327             my ( $self, $document ) = @_;
328             return if $self->{___EXT_on_attrs}->{_skip_start_docs}++;
329             $self->on_start_document($document);
330             }
331              
332             sub end_document {
333             my $self = shift;
334             my $var = --$self->{___EXT_on_attrs}->{_skip_start_docs};
335             return if $var;
336             $self->SUPER::end_document(@_);
337             }
338              
339             =head2 on_start_prefix_mapping prefix1=>ns_uri1[, prefix2=>ns_uri2]
340              
341             Called on C event.
342              
343             sub on_start_prefix_mapping {
344             my $self = shift;
345             my %map = @_;
346             $self->SUPER::start_prefix_mapping(@_)
347             }
348              
349             =cut
350              
351             sub on_start_prefix_mapping {
352             my $self = shift;
353             my %map = @_;
354             while ( my ( $pref, $ns_uri ) = each %map ) {
355             $self->add_namespace( $pref, $ns_uri );
356             $self->SUPER::start_prefix_mapping(
357             {
358             Prefix => $pref,
359             NamespaceURI => $ns_uri
360             }
361             );
362             }
363             }
364              
365             #
366             # { Prefix => 'xlink', NamespaceURI => 'http://www.w3.org/1999/xlink' }
367             #
368              
369             sub start_prefix_mapping {
370             my $self = shift;
371              
372             #declare namespace for current context
373             my %map = ();
374             foreach my $ref (@_) {
375             my ( $prefix, $ns_uri ) = @{$ref}{qw/Prefix NamespaceURI/};
376             $map{$prefix} = $ns_uri;
377             }
378             $self->on_start_prefix_mapping(%map);
379             }
380              
381             =head2 on_start_element $elem
382              
383             Method handle C event whith XML::ExtOn::Element object.
384              
385             Method must return C<$elem> or ref to array of objects.
386              
387             For example:
388              
389             sub on_start_element {
390             my $self = shift;
391             my $elem = shift;
392             $elem->add_content( $self->mk_cdata("test"));
393             return $elem
394             }
395             ...
396            
397             return [ $elem, ,$self->mk_element("after_start_elem") ]
398            
399             return [ $self->mk_element("before_start_elem"), $elem ]
400             ...
401              
402             =cut
403              
404             sub on_start_element {
405             shift;
406             return [@_];
407             }
408              
409             sub __expand_on_start {
410             my $self = shift;
411             my $obj = shift || return [];
412             # warn "before _expand $obj".Dumper($obj) if $obj->local_name eq 'feed';
413             my $res = $self->on_start_element($obj);
414             # warn "_expand $obj".Dumper($res , $obj) if $obj->local_name eq 'feed';
415             my @stack =
416             $res
417             ? ref($res) eq 'ARRAY'
418             ? @{$res}
419             : ($res)
420             : ();
421              
422             #add self object
423             push @stack, $obj;
424              
425             #expand wrap_around and insert_to
426             # also remove dups for $obj
427             my %uniq = ();
428             my @res = ();
429             foreach my $o (@stack) {
430              
431             # also remove dups for $obj
432             next if $uniq{$o}++;
433             unless ( UNIVERSAL::isa( $o, 'XML::ExtOn::Element' ) ) {
434              
435             #don'n touch any events
436             push @res, $o;
437             }
438             else {
439              
440             #convert any object to events (exept $obj)
441             unless ( $o eq $obj ) {
442             push @res, $self->mk_start_element($o),
443             $self->mk_process_stack($o), $self->mk_end_element($o);
444             }
445             else {
446              
447             #expand $insert_to
448             my $insert_to = $o->_wrap_begin || [];
449             if ( scalar @{$insert_to} ) {
450             for ( @{$insert_to} ) {
451             push @res, $self->mk_start_element($_);
452             }
453             }
454              
455             # $o->_wrap_begin([]);
456             #insert result event to write tag
457             push @res, $self->_mk_event_start_element($o);
458              
459             #process elemet's stack (add_content)
460             push @res, $self->mk_process_stack($o);
461              
462             #ad wrap_around started
463             my $waround = $o->_wrap_around_start || [];
464             if ( scalar @{$waround} ) {
465             for ( @{$waround} ) {
466             push @res, $self->mk_start_element($_);
467             }
468             }
469              
470             # $o->_wrap_around_start([]);
471             }
472             }
473             }
474              
475             #now expand
476             return \@res;
477             }
478              
479             sub start_element {
480             my $self = shift;
481             my $current_obj = shift;
482              
483             die "empty" . Dumper( [ map { [ caller($_) ] } ( 0 .. 4 ) ] )
484             unless defined $current_obj;
485              
486             unless ( UNIVERSAL::isa( $current_obj, 'XML::ExtOn::Element' ) ) {
487             my $context;
488             if ( my $current_root_element = $self->current_root_element ) {
489             $context = $current_root_element->ns->sub_context();
490             }
491             $current_obj =
492             $self->__mk_element_from_sax2( $current_obj, context => $context );
493             }
494             else {
495              
496             #set new context
497             my $new_context;
498             if ( my $current_root_element = $self->current_root_element ) {
499             $new_context = $current_root_element->ns->sub_context();
500             }
501             $new_context ||= $self->context->sub_context();
502             #save changes (for namespaces)
503             my $changes = $current_obj->ns->get_changes();
504             while (my ($prefix, $val) = each %$changes) {
505             $new_context->declare_prefix($prefix, $val);
506             }
507             $current_obj->_context($new_context);
508             }
509              
510             my $current_root_element = $self->current_root_element;
511              
512             #push to stack of incoming objects
513             push @{ $self->_root_stack() }, $current_obj;
514              
515             #=comment check skip
516             #check current root element for skip_content
517             if ($current_root_element) {
518             my $skip_content = $current_root_element->is_skip_content;
519             if ($skip_content) {
520             $current_root_element->is_skip_content( ++$skip_content );
521             return;
522             }
523             }
524              
525             #=cut
526             #warn ref($self).":START for " . $current_obj->local_name;
527             return $self->__start_element($current_obj);
528             }
529              
530             sub __start_element {
531             my $self = shift;
532             my $current_obj = shift;
533              
534             #check current element for skip_content
535             if ( my $current_element = $self->current_element ) {
536             my $skip_content = $current_element->is_skip_content;
537             if ( $skip_content > 1 ) {
538             $current_element->is_skip_content( --$skip_content );
539             return;
540             }
541             }
542              
543             #call __start_element
544             my $res = $self->__expand_on_start($current_obj);
545             $current_obj->{_expanded_on_start} = scalar(@$res);
546             # warn ref($self) . "start_exp: " . $current_obj->local_name . ": " . Dumper(
547             # [
548             # map {
549             # ref($_) eq 'HASH'
550             # ? $_->{type} . ":" . $_->{data}->local_name
551             # : $_->local_name
552             # } @$res
553             # ]
554             # );
555              
556             #walk via array
557             foreach my $elem (@$res) {
558              
559             unless ( UNIVERSAL::isa( $elem, 'XML::ExtOn::Element' ) ) {
560              
561             #run event
562             #warn $elem->{type};
563             $self->_process_comm($elem);
564             }
565             else {
566              
567             #register new namespaces
568             my $changes = $current_obj->ns->get_changes;
569             my $parent_map = $current_obj->ns->parent->get_map;
570              
571             for ( keys %$changes ) {
572             $self->end_prefix_mapping(
573             {
574             Prefix => $_,
575             NamespaceURI => $parent_map->{$_},
576             }
577             ) if exists $parent_map->{$_};
578             $self->start_prefix_mapping(
579             {
580             Prefix => $_,
581             NamespaceURI => $changes->{$_},
582             }
583             );
584             }
585              
586             #save element in stack
587             push @{ $self->_objects_stack() }, $current_obj;
588             my @object_stack = @{ $current_obj->_stack };
589             $current_obj->_stack( [] );
590              
591             #skip deleted elements from xml stream
592             unless ( $current_obj->is_delete_element ) {
593              
594             # warn "$self: process start ".$current_obj->local_name;
595             if ( UNIVERSAL::isa( $self->{Handler}, 'XML::ExtOn' ) ) {
596             my $cloned = $current_obj->__clone;
597             unless ( $self->{__make_self_events} ) {
598             $self->{Handler}->start_element($cloned);
599             }
600             else {
601             $self->{Handler}->__start_element($cloned);
602              
603             }
604             }
605             else {
606             my $res_data = $self->__exp_element_to_sax2($current_obj);
607             $self->SUPER::start_element($res_data);
608             }
609             }
610             unless ( $current_obj->is_skip_content ) {
611             $self->_process_comm($_) for @object_stack;
612             }
613             }
614              
615             }
616             }
617              
618             =head2 on_end_element $elem
619              
620             Method handle C event whith XML::ExtOn::Element object.
621             It call before end if element.
622              
623             Method must return C<$elem> or ref to array of objects.
624              
625             For example:
626              
627             sub on_end_element {
628             my $self = shift;
629             my $elem = shift;
630             if ( $elem->is_delete_element ) {
631             warn $elem->local_name . " deleted";
632             return [ $elem, $self->mk_element("after_deleted_elem") ]
633             };
634             return $elem
635             }
636             ...
637            
638             return [ $elem, ,$self->mk_element("after_close_tag_of_elem") ]
639            
640             return [ $self->mk_element("before_close_tag_of_elem"), $elem ]
641             ...
642              
643             =cut
644              
645             sub on_end_element {
646             shift;
647             return [@_];
648             }
649              
650             sub __expand_on_end {
651             my $self = shift;
652             my $obj = shift || return [];
653              
654             #
655             my $res = $self->on_end_element($obj);
656             my @stack =
657             $res
658             ? ref($res) eq 'ARRAY'
659             ? @{$res}
660             : ($res)
661             : ();
662              
663             #add self object
664             push @stack, $obj;
665              
666             #expand wrap_around and insert_to
667             # also remove dups for $obj
668             my %uniq = ();
669             my @res = ();
670             foreach my $o (@stack) {
671              
672             # also remove dups for $obj
673             next if $uniq{$o}++;
674             unless ( UNIVERSAL::isa( $o, 'XML::ExtOn::Element' ) ) {
675              
676             #don'n touch any events
677             push @res, $o;
678             }
679             else {
680              
681             #convert any object to events (exept $obj)
682             unless ( $o eq $obj ) {
683             push @res, $self->mk_start_element($o),
684             $self->mk_process_stack($o), $self->mk_end_element($o);
685             }
686             else {
687              
688             #ad wrap_around started
689             my $waround = $o->_wrap_around_end || [];
690             if ( scalar @{$waround} ) {
691             for ( reverse @{$waround} ) {
692             push @res, $self->mk_end_element($_);
693             }
694             }
695              
696             # push @res, $o; #add object
697             #process elemet's stack (add_content)
698             push @res, $self->mk_process_stack($o);
699              
700             #expand $insert_to
701             push @res, $self->_mk_event_end_element($o);
702              
703             my $insert_to = $o->_wrap_end || [];
704             if ( scalar @{$insert_to} ) {
705             for ( reverse @{$insert_to} ) {
706             push @res, $self->mk_end_element($_);
707             }
708             }
709             }
710             }
711             }
712              
713             #now expand
714             return \@res;
715             }
716              
717             sub end_element {
718             my $self = shift;
719             my $data = shift;
720              
721             #get current element
722             #pop from stack of incoming objects
723             $data = pop @{ $self->_root_stack() };
724             die " $self empty stack" . Dumper( [ map { [ caller($_) ] } ( 0 .. 4 ) ] )
725             unless defined $data;
726              
727             # warn "do __end; for "
728             # . $data->local_name
729             # . " {_expanded_on_start}"
730             # . $data->{_expanded_on_start};
731              
732             #check current element for skip_content
733             if ( my $current_root_element = $self->current_root_element ) {
734             my $skip_content = $current_root_element->is_skip_content;
735             if ( $skip_content > 1 ) {
736             $current_root_element->is_skip_content( --$skip_content );
737             return;
738             }
739             }
740              
741             # warn ref($self).":END for " . $data->local_name;
742             # if ( my $started = $data->{_expanded_on_start} ) {
743             # for ( 1..$started-1 ) {
744             # $self->__end_element($data);
745             # }
746             # }
747             return $self->__end_element($data);
748             }
749              
750             sub __end_element {
751             my $self = shift;
752              
753             my $current_obj = shift; #may be use for control stack
754             #pop element from stack
755              
756             # my $current_obj1 = pop @{ $self->_objects_stack() };
757              
758             my $res = $self->__expand_on_end($current_obj);
759              
760             # warn ref($self)."end_exp: "
761             # . $current_obj->local_name . ": "
762             # . Dumper(
763             # [
764             # map { ref($_) eq 'HASH' ? $_->{type}.":".$_->{data}->local_name : $_->local_name }
765             # @$res
766             # ]
767             # );
768              
769             foreach my $elem (@$res) {
770             unless ( UNIVERSAL::isa( $elem, 'XML::ExtOn::Element' ) ) {
771              
772             #run event
773             $self->_process_comm($elem);
774             }
775             else {
776             die "END!!";
777              
778             #setup default ns
779             my $data = $current_obj->to_sax2;
780             delete $data->{Attributes};
781             $data->{NamespaceURI} = $current_obj->default_uri;
782              
783             # if skip
784             #check current element for skip_content
785             if ( my $current_element = $self->current_element ) {
786             my $skip_content = $current_element->is_skip_content;
787             if ( $skip_content > 1 ) {
788             $current_element->is_skip_content( --$skip_content );
789             return;
790             }
791             }
792              
793             unless ( $current_obj->is_skip_content ) {
794             $self->_process_comm($_) for @{ $current_obj->_stack };
795             $current_obj->_stack( [] );
796             }
797              
798             unless ( $current_obj->is_delete_element ) {
799              
800             # warn "$self: process end ".$current_obj->local_name;
801             unless ( $self->{__make_self_events} ) {
802             $self->SUPER::end_element($data);
803             }
804             else {
805             $self->{Handler}->__end_element($data);
806             }
807             }
808              
809             my $changes = $current_obj->ns->get_changes;
810             my $parent_map = $current_obj->ns->parent->get_map;
811             for ( keys %$changes ) {
812             $self->end_prefix_mapping(
813             {
814             Prefix => $_,
815             NamespaceURI => $changes->{$_},
816             }
817             );
818             if ( exists( $parent_map->{$_} ) ) {
819             $self->start_prefix_mapping(
820             {
821             Prefix => $_,
822             NamespaceURI => $parent_map->{$_},
823             }
824             );
825             }
826             }
827             }
828             }
829             }
830              
831             =head2 on_characters( $self->current_element, $data->{Data} )
832              
833             Must return string for write to stream.
834              
835             sub on_characters {
836             my ( $self, $elem, $str ) = @_;
837             #lowercase all characters
838             return lc $str;
839             }
840              
841              
842             =cut
843              
844             sub on_characters {
845             my ( $self, $elem, $str ) = @_;
846             return $str;
847             }
848              
849             =head2 on_cdata ( $current_element, $data )
850              
851             Must return string for write to stream
852              
853             sub on_cdata {
854             my ( $self, $elem, $str ) = @_;
855             return lc $str;
856             }
857              
858             =cut
859              
860             sub on_cdata {
861             my ( $self, $elem, $str ) = @_;
862             return $str;
863             }
864              
865             #set flag for cdata content
866              
867             sub start_cdata {
868             my $self = shift;
869             $self->_cdata_mode(1);
870             return;
871             }
872              
873             #set flag to end cdata
874              
875             sub end_cdata {
876             my $self = shift;
877             if ( my $elem = $self->current_element
878             and defined( my $cdata_buf = ${ $self->_cdata_characters } ) )
879             {
880              
881             if ( defined( my $data = $self->on_cdata( $elem, $cdata_buf ) ) ) {
882             $self->SUPER::start_cdata;
883             $self->SUPER::characters( { Data => $data } );
884             $self->SUPER::end_cdata;
885             }
886             }
887              
888             #after all clear cd_data_buffer and reset cd_data mode flag
889             my $new_buf;
890             $self->_cdata_characters( \$new_buf );
891             $self->_cdata_mode(0);
892             return;
893             }
894              
895             sub characters {
896             my $self = shift;
897             my ($data) = @_;
898              
899             # warn "$self do chars" . $data->{Data};
900              
901             #skip childs elements characters ( > 1 ) and self text ( > 0)
902             if ( $self->current_element ) {
903             return if $self->current_element->is_skip_content;
904             }
905             else {
906              
907             #skip characters without element
908             return;
909             }
910              
911             #for cdata section collect characters in buffer
912             if ( $self->_cdata_mode ) {
913              
914             # warn "$self do CDATA" . $data->{Data};
915             # warn " $self CDTATA" . Dumper( [ map { [ caller($_) ] } ( 0 .. 10 ) ] );
916             # unless defined $data;
917              
918             ${ $self->_cdata_characters } .= $data->{Data};
919             return;
920             }
921              
922             #collect chars fo current element
923             if (
924             defined(
925             my $str =
926             $self->on_characters( $self->current_element, $data->{Data} )
927             )
928             )
929             {
930             return $self->SUPER::characters( { Data => $str } );
931             }
932             }
933              
934             =head2 mk_element
935              
936             Return object of element item for include to stream.
937              
938             =cut
939              
940             sub mk_element {
941             my $self = shift;
942             my $name = shift;
943             my %args = @_;
944             if ( my $current_element = $self->current_element ) {
945             $args{context} = $current_element->ns->sub_context();
946             }
947             $args{context} ||= $self->context->sub_context();
948             my $elem = new XML::ExtOn::Element::
949             name => $name,
950             %args;
951             return $elem;
952             }
953              
954             =head2 mk_from_xml
955              
956             Return command for include to stream.
957              
958             =cut
959              
960             sub mk_from_xml {
961             my $self = shift;
962             my $string = shift;
963             my $skip_tmp_root =
964             XML::ExtOn::IncXML->new( Handler => $self, __make_self_events => 1 );
965             my $sax2_filter = XML::Filter::SAX1toSAX2->new( Handler => $skip_tmp_root );
966             my $parser = XML::Parser::PerlSAX->new(
967             {
968             Handler => $sax2_filter,
969             Source => { String => "$string" },
970             }
971             );
972             return $parser;
973             }
974              
975             =head2 mk_cdata $string | \$string
976              
977             return command for insert cdata to stream
978              
979             =cut
980              
981             sub mk_cdata {
982             my $self = shift;
983             my $string = shift;
984             return { type => 'CDATA', data => ref($string) ? $string : \$string };
985             }
986              
987             =head2 mk_characters $string | \$string
988              
989             return command for insert characters to stream
990              
991             =cut
992              
993             sub mk_characters {
994             my $self = shift;
995             my $string = shift;
996             return { type => 'CHARACTERS', data => ref($string) ? $string : \$string };
997             }
998              
999             =head2 mk_start_element
1000              
1001             return command for start element event
1002              
1003             =cut
1004              
1005             sub mk_start_element {
1006             my $self = shift;
1007             my $elem = shift;
1008             return { type => 'START_ELEMENT', data => $elem };
1009             }
1010              
1011             =head2 mk_event_element
1012              
1013             return command for expand stack for element
1014              
1015             =cut
1016              
1017             sub mk_process_stack {
1018             my $self = shift;
1019             my $elem = shift;
1020             my @objects = @{ $elem->_stack };
1021             $elem->_stack( [] );
1022             return { type => 'STACK', data => $elem, objects => \@objects };
1023             }
1024              
1025             =head2 _mk_event_start_element
1026              
1027             return start tag command. (internal)
1028              
1029             =cut
1030              
1031             sub _mk_event_start_element {
1032             my $self = shift;
1033             my $elem = shift;
1034             return { type => 'EV_START_ELEMENT', data => $elem };
1035             }
1036              
1037             =head2 _mk_event_end_element
1038              
1039             return end tag command. (internal)
1040              
1041             =cut
1042              
1043             sub _mk_event_end_element {
1044             my $self = shift;
1045             my $elem = shift;
1046             return { type => 'EV_END_ELEMENT', data => $elem };
1047             }
1048              
1049             =head2 mk_end_element
1050              
1051             return command for end element event
1052              
1053             =cut
1054              
1055             sub mk_end_element {
1056             my $self = shift;
1057             my $elem = shift;
1058             return { type => 'END_ELEMENT', data => $elem };
1059             }
1060              
1061             sub __mk_element_from_sax2 {
1062             my $self = shift;
1063             my $data = shift;
1064             my $elem = $self->mk_element( $data->{LocalName}, sax2 => $data, @_ );
1065             return $elem;
1066             }
1067              
1068             sub __exp_element_to_sax2 {
1069             my $self = shift;
1070             my $elem = shift;
1071             return $elem->to_sax2;
1072             }
1073              
1074             =head2 current_element
1075              
1076             Return link to current processing element.
1077              
1078             =cut
1079              
1080             sub current_element {
1081             my $self = shift;
1082             if ( my $stack = $self->_objects_stack() ) {
1083             return $stack->[-1];
1084             }
1085             return;
1086             }
1087              
1088             =head2 current_root_element
1089              
1090             Return link to current root element in incoming stack.
1091             Used in start_element and end_element methods
1092              
1093             =cut
1094              
1095             sub current_root_element {
1096             my $self = shift;
1097             if ( my $stack = $self->_root_stack() ) {
1098             return $stack->[-1];
1099             }
1100             return;
1101             }
1102              
1103             # Private method for process commands
1104              
1105             sub _process_comm {
1106             my $self = shift;
1107             my $comm = shift || return;
1108             if ( UNIVERSAL::isa( $comm, 'XML::Parser::PerlSAX' ) ) {
1109             $comm->parse();
1110             }
1111             elsif ( UNIVERSAL::isa( $comm, 'XML::Parser' ) ) {
1112             warn "parser!";
1113             $comm->parse();
1114             }
1115             elsif ( UNIVERSAL::isa( $comm, 'XML::ExtOn::Element' ) ) {
1116              
1117             # warn ref($self)."start ELEMENT " . $comm->local_name;
1118             $self->__start_element($comm);
1119              
1120             # while ( my $obj = shift @{ $comm->_stack } ) {
1121             # $self->_process_comm($obj);
1122             # }
1123             $self->__end_element($comm);
1124              
1125             # warn ref($self)."end ELEMENT " . $comm->local_name;
1126             ; # unless shift; #if exists extra param not end elem
1127             }
1128             elsif ( ref($comm) eq 'HASH' and exists $comm->{type} ) {
1129             if ( $comm->{type} eq 'CDATA' ) {
1130              
1131             #warn "$self : DO CDATA!!!";
1132             $self->start_cdata;
1133             $self->characters( { Data => ${ $comm->{data} } } );
1134             $self->end_cdata;
1135             }
1136             elsif ( $comm->{type} eq 'CHARACTERS' ) {
1137             unless ( ref( $comm->{data} ) eq 'SCALAR' ) {
1138             warn "NOT REF" . Dumper $comm;
1139             warn "empty" . Dumper( [ map { [ caller($_) ] } ( 0 .. 16 ) ] );
1140             exit;
1141              
1142             }
1143             $self->characters( { Data => ${ $comm->{data} } } );
1144             }
1145             elsif ( $comm->{type} eq 'START_ELEMENT' ) {
1146             my $current_obj = $comm->{data};
1147             $self->__start_element( $comm->{data} );
1148             }
1149             elsif ( $comm->{type} eq 'END_ELEMENT' ) {
1150             my $current_obj = $comm->{data};
1151             $self->__end_element( $comm->{data} );
1152             }
1153             elsif ( $comm->{type} eq 'STACK' ) {
1154             my $stack = $comm->{objects};
1155             my $comm = $comm->{data};
1156              
1157             # warn "$self: ",
1158             # $comm->local_name . " stack: " . scalar( @{$stack} ) . Dumper(
1159             # [
1160             # map {
1161             # ref($_) eq 'HASH'
1162             # ? $_->{type} . ":" . '$_->{data}->local_name'
1163             # : $_->local_name
1164             # } @$stack
1165             # ]
1166             # );
1167             # warn ref($self)."START PROCESS STACK ".$comm->local_name;
1168             while ( my $obj = shift @{$stack} ) {
1169              
1170             # warn "$self start STACK: ".$obj;
1171             $self->_process_comm($obj);
1172              
1173             # warn "$self end STACK: ".$obj;
1174             }
1175              
1176             # warn ref($self)."END PROCESS STACK ".$comm->local_name;
1177              
1178             }
1179             elsif ( $comm->{type} eq 'EV_START_ELEMENT' ) {
1180             my $current_obj = $comm->{data};
1181              
1182             # warn "$self: ev_START".$current_obj->local_name;
1183             #register new namespaces
1184             my $changes = $current_obj->ns->get_changes;
1185             my $parent_map = $current_obj->ns->parent->get_map;
1186              
1187             for ( keys %$changes ) {
1188             $self->end_prefix_mapping(
1189             {
1190             Prefix => $_,
1191             NamespaceURI => $parent_map->{$_},
1192             }
1193             ) if exists $parent_map->{$_};
1194              
1195             $self->start_prefix_mapping(
1196             {
1197             Prefix => $_,
1198             NamespaceURI => $changes->{$_},
1199             }
1200             );
1201             }
1202              
1203             #save element in stack
1204             push @{ $self->_objects_stack() }, $current_obj;
1205              
1206             #warn ref($self) . ": <" . $comm->{data}->local_name . ">";
1207              
1208             #skip deleted elements from xml stream
1209             unless ( $current_obj->is_delete_element ) {
1210             if ( UNIVERSAL::isa( $self->{Handler}, 'XML::ExtOn' ) ) {
1211             my $cloned = $current_obj->__clone;
1212             unless ( $self->{__make_self_events} ) {
1213             $self->{Handler}->start_element($cloned);
1214             }
1215             else {
1216             $self->{Handler}->__start_element($cloned);
1217              
1218             }
1219             }
1220             else {
1221             my $res_data = $self->__exp_element_to_sax2($current_obj);
1222             $self->SUPER::start_element($res_data);
1223             }
1224             }
1225             }
1226             elsif ( $comm->{type} eq 'EV_END_ELEMENT' ) {
1227             my $current_obj = $comm->{data};
1228             my $current_obj1 = pop @{ $self->_objects_stack() };
1229              
1230             #warn "END_E: ".$current_obj->local_name;
1231             # if skip
1232             #check current element for skip_content
1233             # if ( my $current_element = $self->current_element ) {
1234             # my $skip_content = $current_element->is_skip_content;
1235             # if ( $skip_content > 1 ) {
1236             # $current_element->is_skip_content( --$skip_content );
1237             # return;
1238             # }
1239             # }
1240              
1241             unless ( $current_obj->is_delete_element ) {
1242             unless ( $self->{__make_self_events} ) {
1243              
1244             #convert to SAX2
1245             my $data = $current_obj->to_sax2;
1246             delete $data->{Attributes};
1247             $data->{NamespaceURI} = $current_obj->default_uri;
1248             $self->SUPER::end_element($data);
1249             }
1250             else {
1251              
1252             #call with object
1253             $self->{Handler}->__end_element($current_obj1);
1254             }
1255             }
1256              
1257             my $changes = $current_obj->ns->get_changes;
1258             my $parent_map = $current_obj->ns->parent->get_map;
1259             for ( keys %$changes ) {
1260             $self->end_prefix_mapping(
1261             {
1262             Prefix => $_,
1263             NamespaceURI => $changes->{$_},
1264             }
1265             );
1266             if ( exists( $parent_map->{$_} ) ) {
1267             $self->start_prefix_mapping(
1268             {
1269             Prefix => $_,
1270             NamespaceURI => $parent_map->{$_},
1271             }
1272             );
1273             }
1274             }
1275              
1276             #warn ref($self) . ": {data}->local_name . ">";
1277              
1278             }
1279             }
1280             else {
1281             warn " Unknown DATA $comm";
1282             }
1283             }
1284              
1285             =head2 add_namespace => , [ => , ... ]
1286              
1287             Add Namespace mapping. return C<$self>
1288              
1289             If C eq '', this namespace will then apply to all elements
1290             that have no prefix.
1291              
1292             $elem->add_namespace(
1293             "myns" => 'http://example.com/myns',
1294             "myns_test", 'http://example.com/myns_test',
1295             ''=>'http://example.com/new_default_namespace'
1296             );
1297              
1298             =cut
1299              
1300             sub add_namespace {
1301             my $self = shift;
1302             my $context = $self->context;
1303             if ( my $current = $self->current_element ) {
1304             $context = $current->ns;
1305             }
1306             my %map = @_;
1307             while ( my ( $prefix, $ns_uri ) = each %map ) {
1308             $context->declare_prefix( $prefix, $ns_uri );
1309             }
1310             }
1311              
1312             #overload sub parse
1313              
1314             =head2 parse | <\*GLOB> | |
1315              
1316              
1317             =cut
1318              
1319             sub parse {
1320             my ( $self, $in ) = @_;
1321             my $sax2_filter = XML::Filter::SAX1toSAX2->new( Handler => $self );
1322             my $parser = XML::Parser::PerlSAX->new( { Handler => $sax2_filter } );
1323             unless ( ref($in) ) {
1324              
1325             # $self->_process_comm( $self->mk_from_xml($in) );
1326             $parser->parse( Source => { String => $in } );
1327             }
1328             elsif (UNIVERSAL::isa( $in, 'IO::Handle' )
1329             or ( ( ref $in ) eq 'GLOB' )
1330             or UNIVERSAL::isa( $in, 'Tie::Handle' ) )
1331             {
1332             $parser->parse( Source => { ByteStream => $in } )
1333              
1334             }
1335             else {
1336             die "unknown params";
1337             }
1338             }
1339              
1340             1;
1341             __END__