File Coverage

blib/lib/XML/Handler/Trees.pm
Criterion Covered Total %
statement 9 281 3.2
branch 0 84 0.0
condition 0 59 0.0
subroutine 3 45 6.6
pod n/a
total 12 469 2.5


line stmt bran cond sub pod time code
1 1     1   29843 use strict;
  1         3  
  1         60  
2            
3             package XML::Handler::Trees;
4 1     1   9 use vars qw/$VERSION/;
  1         2  
  1         6744  
5             $VERSION = '0.02';
6            
7             package XML::Handler::Tree;
8            
9             sub new {
10 0   0 0     my $class = ref($_[0]) || $_[0];
11 0           bless {},$class;
12             }
13            
14             sub start_document {
15 0     0     my $self=shift;
16 0           $self->{Lists}=[];
17 0           $self->{Curlist}=$self->{Tree}=[];
18             }
19            
20             sub start_element {
21 0     0     my ($self,$element)=@_;
22 0           my $newlist;
23 0 0         if (exists $element->{LocalName}) {
    0          
24             # namespaces are available!
25 0           $newlist = [{}];
26 0           foreach my $attr (values %{$element->{Attributes}}) {
  0            
27 0 0         if ($attr->{NamespaceURI}) {
28 0           $newlist->[0]{"{$attr->{NamespaceURI}}$attr->{LocalName}"} = $attr->{Value};
29             }
30             else {
31 0           $newlist->[0]{$attr->{Name}} = $attr->{Value};
32             }
33             }
34             }
35             elsif (ref $element->{Attributes} eq 'HASH') {
36 0           $newlist=[{map {$_=>$element->{Attributes}{$_}} keys %{$element->{Attributes}}}];
  0            
  0            
37             }
38             else {
39 0           $newlist=[{map {$_=>$element->{Attributes}{$_}{Value}} keys %{$element->{Attributes}}}];
  0            
  0            
40             }
41 0           push @{ $self->{Lists} }, $self->{Curlist};
  0            
42 0 0 0       if (exists($element->{LocalName}) && $element->{NamespaceURI}) {
43 0           push @{ $self->{Curlist} }, "{$element->{NamespaceURI}}$element->{LocalName}" => $newlist;
  0            
44             }
45             else {
46 0           push @{ $self->{Curlist} }, $element->{Name} => $newlist;
  0            
47             }
48 0           $self->{Curlist} = $newlist;
49             }
50            
51             sub end_element {
52 0     0     my ($self,$element)=@_;
53 0           $self->{Curlist}=pop @{$self->{Lists}};
  0            
54             }
55            
56             sub characters {
57 0     0     my ($self,$text)=@_;
58 0           my $clist = $self->{Curlist};
59 0           my $pos = $#$clist;
60 0 0 0       if ($pos>0 and $clist->[$pos-1] eq '0') {
61 0           $clist->[$pos].=$text->{Data};
62             }
63             else {
64 0           push @$clist,0=>$text->{Data};
65             }
66             }
67            
68 0     0     sub comment {}
69            
70 0     0     sub processing_instruction {}
71            
72             sub end_document {
73 0     0     my $self=shift;
74 0           delete $self->{Curlist};
75 0           delete $self->{Lists};
76 0           $self->{Tree};
77             }
78            
79             package XML::Handler::EasyTree;
80            
81             sub new {
82 0     0     my $class=shift;
83 0   0       $class=ref($class) || $class;
84 0           my $self={Noempty=>0,Latin=>0,Searchable=>0,@_};
85 0   0       $self->{Noempty}||=$self->{Searchable};
86 0           bless $self,$class;
87             }
88            
89             sub start_document {
90 0     0     my $self = shift;
91 0           $self->{Lists} = [];
92 0           $self->{Curlist} = $self->{Tree} = [];
93             }
94            
95             sub start_element {
96 0     0     my ($self,$element)=@_;
97 0           $self->checkempty();
98 0           my $newlist=[];
99 0           my $newnode;
100 0 0         if ($self->{Searchable}) {
101 0           $newnode= XML::Handler::EasyTree::Searchable->new( Name => $self->nsname($element), Content => $newlist );
102             }
103             else {
104 0           $newnode={type=>'e',attrib=>{},name=>$self->nsname($element),content=>$newlist};
105             }
106 0 0         if (exists $element->{LocalName}) {
    0          
107 0           while (my ($name,$obj) = each %{$element->{Attributes}}) {
  0            
108 0           $newnode->{attrib}{$name} = $self->encode($obj->{Value});
109             }
110             }
111             elsif (ref $element->{Attributes} eq 'HASH') {
112 0           while (my ($name,$val)=each %{$element->{Attributes}}) {
  0            
113 0           $newnode->{attrib}{$self->nsname($name)}=$self->encode($val);
114             }
115             }
116             else {
117 0           foreach my $att (keys %{$element->{Attributes}}) {
  0            
118 0           $newnode->{attrib}{$self->nsname($element->{Attributes}{$att})}=$self->encode($element->{Attributes}{$att}{Value});
119             }
120             }
121 0           push @{ $self->{Lists} }, $self->{Curlist};
  0            
122 0           push @{ $self->{Curlist} }, $newnode;
  0            
123 0           $self->{Curlist} = $newlist;
124             }
125            
126             sub end_element {
127 0     0     my $self=shift;
128 0           $self->checkempty();
129 0           $self->{Curlist}=pop @{$self->{Lists}};
  0            
130             }
131            
132             sub characters {
133 0     0     my ($self,$text)=@_;
134 0           my $clist=$self->{Curlist};
135 0 0 0       if (!@$clist || $clist->[-1]{type} ne 't') {
136 0           push @$clist,{type=>'t',content=>''};
137             }
138 0           $clist->[-1]{content}.=$self->encode($text->{Data});
139             }
140            
141             sub processing_instruction {
142 0     0     my ($self,$pi)=@_;
143 0           $self->checkempty();
144 0           my $clist=$self->{Curlist};
145 0           push @$clist,{type=>'p',target=>$self->encode($pi->{Target}),content=>$self->encode($pi->{Data})};
146             }
147            
148 0     0     sub comment {}
149            
150             sub end_document {
151 0     0     my $self = shift;
152 0           $self->checkempty();
153 0           delete $self->{Curlist};
154 0           delete $self->{Lists};
155 0 0         if ($self->{Searchable}) {
156 0           return XML::Handler::EasyTree::Searchable->new( Name => '__TOPLEVEL__', Content => $self->{Tree} );
157             }
158 0           $self->{Tree};
159             }
160            
161             sub nsname {
162 0     0     my ($self,$name)=@_;
163 0 0         if (ref $name) {
164 0 0         if (defined $name->{NamespaceURI}) {
165 0           $name="{$name->{NamespaceURI}}$name->{LocalName}";
166             }
167             else {
168 0           $name=$name->{Name};
169             }
170             }
171 0           return $self->encode($name);
172             }
173            
174             sub encode {
175 0     0     my ($self,$text)=@_;
176 0 0         if ($self->{Latin}) {
177 0           $text=~s{([\xc0-\xc3])(.)}{
178 0           my $hi = ord($1);
179 0           my $lo = ord($2);
180 0           chr((($hi & 0x03) <<6) | ($lo & 0x3F))
181             }ge;
182             }
183 0           $text;
184             }
185            
186             sub checkempty() {
187 0     0     my $self=shift;
188 0 0         if ($self->{Noempty}) {
189 0           my $clist=$self->{Curlist};
190 0 0 0       if (@$clist && $clist->[-1]{type} eq 't' && $clist->[-1]{content}=~/^\s+$/) {
      0        
191 0           pop @$clist;
192             }
193             }
194             }
195            
196             package XML::Handler::EasyTree::Searchable;
197            
198             #
199             # new() returns a new node with the same structure at the `newnode'
200             # hashref
201             #
202             # Usage: XML::Handler::EasyTree::Searchable->new( Name => $name, Content => $content );
203             #
204             sub new {
205 0     0     my $type = shift;
206 0   0       my $class = ref($type) || $type || die "must supply a object type" ;
207            
208 0           my %opts = @_;
209            
210 0   0       my $name = $opts{Name} || '';
211 0   0       my $content = $opts{Content} || undef;
212            
213 0           return bless ( {
214             type => 'e',
215             attrib => {},
216             name => $name,
217             content => $content,
218             }, $class);
219             }
220            
221             #
222             # name() returns the name of the node. Ideally, it should return a
223             # "fully qualified" name, but it doesn't
224             #
225             sub name {
226 0     0     my $self = shift;
227 0           return $self->{name};
228             }
229            
230             #
231             # value() returns the value associated with an object
232             #
233             sub value {
234 0     0     my $self = shift;
235            
236             return( undef )
237 0 0 0       unless( ( exists $self->{content} ) && ( defined $self->{content} ) );
238            
239 0           my $possible = $self->{content};
240            
241 0 0         die "not an array" unless( "$possible" =~ /ARRAY/ );
242            
243 0           $possible = $possible->[0];
244            
245             return( undef )
246 0 0 0       unless( ( exists $possible->{type} ) && ( $possible->{type} eq 't' ) );
247            
248             return( undef )
249 0 0 0       unless( ( exists $possible->{content} ) && ( defined $possible->{content} ) );
250            
251 0           return $possible->{content};
252             }
253            
254             #
255             # usage: $newobj = $obj->child( $name );
256             #
257             # child() returns a child (elements only) of the object with the $name
258             #
259             # for the case where there is more than one child that match $name,
260             # the array context semantics haven't been completely worked out:
261             # - in an array context, all children are returned.
262             # - in scalar context, the first child matching $name is returned.
263             #
264             # In a scalar context, The XML::Parser::SimpleObj class returns an
265             # object containing all the children matching $name, unless there is
266             # only one child in which case it returns that child (see commented
267             # code). I find that behavior confusing.
268             #
269             sub child {
270 0     0     my $self = shift;
271 0   0       my $spec = shift || '';
272            
273 0           my $array = $self->{content};
274            
275 0           my @rv;
276 0 0         if( $spec ) {
277 0           @rv = grep { $_->{name} eq $spec } grep { $_->{type} eq 'e' } @$array;
  0            
  0            
278             } else {
279 0           @rv = grep { $_->{type} eq 'e' } @$array;
  0            
280             }
281            
282 0           my $num = scalar( @rv );
283            
284 0 0         if( wantarray() ) {
285 0           return @rv;
286             } else {
287 0 0         return '' unless( $num );
288 0 0         return $rv[0] if( $num == 1 );
289             # my $class = ref( $self );
290             # return $class->new( Name => "__magic_child_list_object__", Content => [ @rv ] );
291             }
292             }
293            
294             #
295             # usage: @children = $obj->children( $name );
296             #
297             # children() returns a list of all children (elements only) of the
298             # $obj that match $name -- in the order in which they appeared in the
299             # original xml text.
300             #
301             sub children {
302 0     0     my $self = shift;
303 0           my $array = $self->{content};
304 0   0       my $spec = shift || '';
305            
306            
307 0           my @rv;
308 0 0         if( $spec ) {
309 0           @rv = grep { $_->{name} eq $spec } grep { $_->{type} eq 'e' } @$array;
  0            
  0            
310             } else {
311 0           @rv = grep { $_->{type} eq 'e' } @$array;
  0            
312             }
313            
314 0           return @rv;
315             }
316            
317             #
318             # usage: @children_names = $obj->children_names();
319             #
320             # children_names() returns a list of all the names of the objects
321             # children (elements only) in the order in which they appeared in the
322             # original text
323             #
324             sub children_names {
325 0     0     my $self = shift;
326 0           my $array = $self->{content};
327            
328 0           return map { $_->{name} } grep { $_->{type} eq 'e' } @$array;
  0            
  0            
329             }
330            
331             #
332             # usage: $attrib = $obj->attribute( $att_name );
333             #
334             # attribute() returns the string associated with the attribute of the
335             # object. If not found returns a null string.
336             #
337             sub attribute {
338 0     0     my $self = shift;
339 0   0       my $spec = shift || return '';
340            
341 0 0 0       return '' unless( ( exists $self->{attrib} ) && ( defined $self->{attrib} ) );
342            
343 0           my $attrib = $self->{attrib};
344 0 0 0       return '' unless( ( exists $attrib->{$spec} ) && ( defined $attrib->{$spec} ) );
345            
346 0           return $attrib->{$spec};
347             }
348            
349             #
350             # usage: @attribute_list = $obj->attribute_list();
351             #
352             # attribute_list() returns a list (in no particular order) of the
353             # attribute names associated with the object
354             #
355             sub attribute_list {
356 0     0     my $self = shift;
357            
358 0 0 0       return '' unless( ( exists $self->{attrib} ) && ( defined $self->{attrib} ) );
359            
360 0           my $attrib = $self->{attrib};
361 0 0         return '' unless( "$attrib" =~ /HASH/ );
362            
363 0           return keys %$attrib;
364             }
365            
366             #
367             # usage: $text = $obj->dump_tree();
368             #
369             # dump_tree() returns a textual representation (in xml form) of the
370             # object's heirarchy. Only elements are processed.
371             #
372             #
373             sub dump_tree {
374 0     0     my $self = shift;
375 0           my %opts = @_;
376            
377 0           my $pretty = delete $opts{-pretty};
378            
379 0           my $name = $self->name();
380 0           my $value = $self->value();
381 0           my @children = $self->children();
382            
383 0           my $text = '';
384 0 0         unless( $name eq '__TOPLEVEL__' ) {
385 0           $text .= "<$name";
386 0           for my $att ( $self->attribute_list() ) {
387 0           $text .= sprintf( " %s=\"%s\"", $att, encode($self->attribute( $att )) );
388             }
389 0           $text .= ">";
390            
391 0 0         if( $value ) {
392 0           $text .= encode($value);
393             }
394             }
395            
396            
397 0           for my $child ( @children ) {
398 0           $text .= $child->dump_tree();
399             }
400            
401 0 0         unless( $name eq '__TOPLEVEL__' ) {
402 0           $text .= "";
403             }
404            
405 0           return $text;
406             }
407            
408             #
409             # usage: $text = $obj->pretty_dump_tree();
410             #
411             # pretty_dump_tree() is identical to dump_tree(), except that newline
412             # and indentation embellishments are added
413             #
414             sub pretty_dump_tree {
415 0     0     my $self = shift;
416 0   0       my $tab = shift || 0;
417            
418 0           my $indent = " " x ( 2 * $tab );
419            
420 0           my $name = $self->name();
421 0           my $value = $self->value();
422 0           my @children = $self->children();
423            
424 0           my $text = '';
425 0 0         unless( $name eq '__TOPLEVEL__' ) {
426 0           $text .= "$indent<$name";
427 0           for my $att ( $self->attribute_list() ) {
428 0           $text .= sprintf( " %s=\"%s\"", $att, encode($self->attribute( $att )) );
429             }
430 0           $text .= ">";
431            
432 0 0         if( defined $value ) {
433 0           $text .= encode($value);
434 0           $text .= "\n";
435 0           return $text;
436             } else {
437 0           $text .= "\n";
438             }
439             }
440            
441 0           for my $child ( @children ) {
442 0           $text .= $child->pretty_dump_tree( $tab + 1 );
443             }
444            
445 0 0         unless( $name eq '__TOPLEVEL__' ) {
446 0           $text .= "$indent\n";
447             }
448            
449 0           return $text;
450             }
451            
452             sub encode {
453 0     0     my $encstr=shift;
454 0           my %encodings=('&'=>'amp','<'=>'lt','>'=>'gt','"'=>'quot',"'"=>'apos');
455 0           $encstr=~s/([&<>"'])/&$encodings{$1};/g;
456 0           $encstr;
457             }
458            
459             package XML::Handler::TreeBuilder;
460            
461 1     1   17 use vars qw(@ISA);
  1         14  
  1         2052  
462             @ISA=qw(XML::Element);
463            
464             sub new {
465 0     0     require XML::Element;
466 0   0       my $class = ref($_[0]) || $_[0];
467 0           my $self = XML::Element->new('NIL');
468 0           $self->{'_element_class'} = 'XML::Element';
469 0           $self->{'_store_comments'} = 0;
470 0           $self->{'_store_pis'} = 0;
471 0           $self->{'_store_declarations'} = 0;
472 0           $self->{_stack}=[];
473 0           bless $self, $class;
474             }
475            
476 0     0     sub start_document {}
477            
478             sub start_element {
479 0     0     my ($self,$element)=@_;
480 0           my @attlist;
481 0 0         if (exists $element->{LocalName}) {
    0          
482 0           @attlist=map {$_=>$element->{Attributes}{$_}{Value}} keys %{$element->{Attributes}};
  0            
  0            
483             }
484             elsif (ref $element->{Attributes} eq 'HASH') {
485 0           @attlist=map {$_=>$element->{Attributes}{$_}} keys %{$element->{Attributes}};
  0            
  0            
486             }
487             else {
488 0           @attlist=map {$_=>$element->{Attributes}{$_}{Value}} keys %{$element->{Attributes}};
  0            
  0            
489             }
490 0 0         if(@{$self->{_stack}}) {
  0            
491 0           push @{$self->{_stack}}, $self->{'_element_class'}->new($element->{Name},@attlist);
  0            
492 0           $self->{_stack}[-2]->push_content( $self->{_stack}[-1] );
493             }
494             else {
495 0           $self->tag($element->{Name});
496 0           while(@attlist) {
497 0           $self->attr(splice(@attlist,0,2));
498             }
499 0           push @{$self->{_stack}}, $self;
  0            
500             }
501             }
502            
503             sub end_element {
504 0     0     my $self=shift;
505 0           pop @{$self->{_stack}};
  0            
506             return
507 0           }
508            
509             sub characters {
510 0     0     my ($self,$text)=@_;
511 0           $self->{_stack}[-1]->push_content($text->{Data});
512             }
513            
514             sub comment {
515 0     0     my ($self,$comment)=@_;
516 0 0         return unless $self->{'_store_comments'};
517 0 0         (@{$self->{_stack}} ? $self->{_stack}[-1] : $self)->push_content(
  0            
518             $self->{'_element_class'}->new('~comment', 'text' => $comment->{Data})
519             );
520 0           return;
521             }
522            
523             sub processing_instruction {
524 0     0     my ($self,$pi)=@_;
525 0 0         return unless $self->{'_store_pis'};
526 0 0         (@{$self->{_stack}} ? $self->{_stack}[-1] : $self)->push_content(
  0            
527             $self->{'_element_class'}->new('~pi', 'text' => "$pi->{Target} $pi->{Data}")
528             );
529 0           return;
530             }
531            
532             sub end_document {
533 0     0     my $self=shift;
534 0           return $self;
535             }
536            
537             sub _elem # universal accessor...
538             {
539 0     0     my($self, $elem, $val) = @_;
540 0           my $old = $self->{$elem};
541 0 0         $self->{$elem} = $val if defined $val;
542 0           return $old;
543             }
544            
545 0     0     sub store_comments { shift->_elem('_store_comments', @_); }
546 0     0     sub store_declarations { shift->_elem('_store_declarations', @_); }
547 0     0     sub store_pis { shift->_elem('_store_pis', @_); }
548            
549             1;
550             __END__