File Coverage

lib/XML/Parser/Style/ETree.pm
Criterion Covered Total %
statement 12 117 10.2
branch 0 52 0.0
condition 0 8 0.0
subroutine 4 9 44.4
pod 0 5 0.0
total 16 191 8.3


line stmt bran cond sub pod time code
1             package XML::Parser::Style::ETree;
2              
3 1     1   19 use 5.006002;
  1         4  
  1         37  
4 1     1   5 use strict;
  1         1  
  1         26  
5 1     1   4 use warnings;
  1         2  
  1         23  
6 1     1   5 use Scalar::Util ();
  1         1  
  1         1637  
7              
8             =head1 NAME
9              
10             XML::Parser::Style::ETree - Parse xml to simple tree
11              
12             =head1 VERSION
13              
14             Version 0.09
15              
16             =cut
17              
18             our $VERSION = '0.09';
19              
20             =head1 SYNOPSIS
21              
22             use XML::Parser;
23             my $p = XML::Parser->new( Style => 'ETree' );
24              
25             =head1 EXAMPLE
26              
27            
28            
29             first
30             a
31             mid
32             b
33            
34             last
35            
36            
37              
38             will be
39              
40             {
41             root => {
42             '-at' => 'key',
43             nest => {
44             '#text' => 'firstmidlast',
45             vv => '',
46             v => [
47             'a',
48             {
49             '-at' => 'a',
50             '#text' => 'b'
51             }
52             ]
53             }
54             }
55             }
56              
57             =head1 SPECIAL VARIABLES
58              
59             =over 4
60              
61             =item $TEXT{ATTR} [ = '-' ]
62              
63             Allow to set prefix for name of attribute nodes;
64              
65            
66             # will be
67             item => { -attr => 'value' };
68              
69             # with
70             $TEXT{ATTR} = '+';
71             # will be
72             item => { '+attr' => 'value' };
73            
74             =item $TEXT{NODE} [ = '#text' ]
75              
76             Allow to set name for text nodes
77              
78             Text value
79             # will be
80             item => { sub => { -attr => "t" }, #text => 'Text value' };
81              
82             # with
83             $TEXT{NODE} = '';
84             # will be
85             item => { sub => { -attr => "t" }, '' => 'Text value' };
86              
87             =item $TEXT{JOIN} [ = '' ]
88              
89             Allow to set join separator for text node, splitted by subnodes
90              
91             Test1Test2
92             # will be
93             item => { sub => '', #text => 'Test1Test2' };
94              
95             # with
96             $TEXT{JOIN} = '+';
97             # will be
98             item => { sub => '', #text => 'Test1+Test2' };
99              
100             =item $TEXT{TRIM} [ = 1 ]
101              
102             Trim leading and trailing whitespace from text nodes
103              
104             Test1 Test2
105             # will be
106             item => { sub => '', #text => 'Test1Test2' };
107              
108             # with
109             $TEXT{TRIM} = 0;
110             # will be
111             item => { sub => '', #text => ' Test1 Test2 ' };
112              
113             =item %FORCE_ARRAY
114              
115             Allow to force nodes to be represented always as arrays. If name is empty string, then ot means ALL
116              
117             Text value
118              
119             # will be
120             item => { sub => { -attr => "t" }, #text => 'Text value' };
121              
122             # with
123             $FORCE_ARRAY{sub} = 1;
124             # will be
125             item => { sub => [ { -attr => "t" } ], #text => 'Text value' };
126              
127             # with
128             $FORCE_ARRAY{''} = 1;
129             # will be
130             item => [ { sub => [ { -attr => "t" } ], #text => 'Text value' } ];
131              
132             =item %FORCE_HASH
133              
134             Allow to force text-only nodes to be represented always as hashes. If name is empty string, then ot means ALL
135              
136             Text valueText value
137              
138             # will be
139             item => { sub => 'Text value', any => 'Text value' };
140              
141             # with
142             $FORCE_HASH{sub} = 1;
143             # will be
144             item => { sub => { #text => 'Text value' }, any => 'Text value' };
145              
146             # with
147             $FORCE_HASH{''} = 1;
148             # will be
149             item => { sub => { #text => 'Text value' }, any => { #text => 'Text value' } };
150              
151             =item @STRIP_KEY
152              
153             Allow to strip something from tag names by regular expressions
154              
155             Text value
156              
157             # will be
158             'a:item' => { 'b:sub' => 'Text value' };
159              
160             # with
161             @STRIP_KEY = (qr/^[^:]+:/);
162             # will be
163             'item' => { 'sub' => 'Text value' };
164              
165             =back
166              
167             =cut
168              
169             sub DEBUG () { 0 };
170              
171             our %TEXT = (
172             ATTR => '-',
173             NODE => '#text',
174             JOIN => '',
175             TRIM => 1,
176             );
177              
178             our @STRIP_KEY;
179              
180             # '' means all since this can't be a name of tag
181              
182             our %FORCE_ARRAY = ( '' => 0 );
183             our %FORCE_HASH = ( '' => 0 );
184              
185              
186             sub Init {
187 0     0 0   my $xp = shift;
188 0   0       my $t = $xp->{FunTree} ||= {};
189 0           $t->{stack} = [];
190 0           $t->{tree} = {};
191 0           $t->{context} = { tree => {}, text => [] };
192 0           $t->{opentag} = undef;
193 0           $t->{depth} = 0 if DEBUG;
194 0           return;
195             }
196              
197             sub Start {
198 0     0 0   my $xp = shift;
199 0           my $t = $xp->{FunTree};
200            
201             #if ($enc) { @_ = @_; $_ = $enc->encode($_) for @_ };
202 0           my $tag = shift;
203 0           $tag =~ s{$_}{} for @STRIP_KEY;
204 0           warn "++"x(++$t->{depth}) . $tag if DEBUG;
205            
206 0           my $node = {
207             name => $tag,
208             tree => undef,
209             text => [],
210             textflag => 0,
211             };
212 0           Scalar::Util::weaken($node->{parent} = $t->{context});
213 0 0         if (@_) {
214 0           my %attr;
215 0           while (my ($k,$v) = splice @_,0,2) {
216 0           $attr{ $TEXT{ATTR}.$k } = $v;
217             }
218             #$flat[$#flat]{attributes} = \%attr;
219 0           $node->{attrs} = \%attr;
220             #warn "Need something to do with attrs on $tag\n";
221             };
222 0           $t->{opentag} = 1;
223             {
224 0 0         if (@{ $t->{context}{text} }) {
  0            
  0            
225 0 0         ${ $t->{context}{text} }[ $#{ $t->{context}{text} } ] =~ s{[\t\s\r\n]+$}{}s if $TEXT{TRIM};
  0            
  0            
226             # warn "cleaning trailing whitespace on $#{ $t->{context}{text} } : ${ $t->{context}{text} }[ $#{ $t->{context}{text} } ]";
227 0 0         pop (@{ $t->{context}{text} }),redo unless length ${ $t->{context}{text} }[ $#{ $t->{context}{text} } ];
  0            
  0            
  0            
228             }
229             }
230             #push @{ $t->{context}{text} }, $TEXT{JOIN} if $t->{context}{textflag} and length $TEXT{JOIN};
231 0           $t->{context}{textflag} = 0;
232            
233 0           push @{ $t->{stack} }, $t->{context} = $node;
  0            
234             }
235              
236             sub End {
237 0     0 0   my $xp = shift;
238 0           my $t = $xp->{FunTree};
239            
240             #if ($enc) { @_ = @_; $_ = $enc->encode($_) for @_ };
241 0           my $name = shift;
242 0           $name =~ s{$_}{} for @STRIP_KEY;
243            
244             #my $node = pop @stack;
245 0           my $text = $t->{context}{text};
246 0           $t->{opentag} = 0;
247            
248 0           my $tree = $t->{context}{tree};
249              
250 0           my $haschild = scalar keys %$tree;
251 0 0         if ( ! $FORCE_ARRAY{''} ) {
252 0           foreach my $key ( keys %$tree ) {
253             #warn "$key for $name\n";
254 0 0         next if $FORCE_ARRAY{$key};
255 0 0         next if ( 1 < scalar @{ $tree->{$key} } );
  0            
256 0           $tree->{$key} = shift @{ $tree->{$key} };
  0            
257             }
258             }
259 0 0         if ( @$text ) {
    0          
260             {
261 0 0         ${ $text }[ $#$text ] =~ s{[\t\s\r\n]+$}{}s if $TEXT{TRIM};
  0            
  0            
262             # warn "cleaning trailing whitespace on $#$text :${ $text }[ $#$text ]";
263 0 0         pop (@$text),redo unless length ${ $text }[ $#$text ];
  0            
264             }
265             #warn "node $name have text '@$text'";
266 0 0         if ( @$text == 1 ) {
267             # one text node (normal)
268 0           $text = shift @$text;
269             }
270             else {
271             # some text node splitted
272 0           $text = join( $TEXT{JOIN}, @$text );
273             }
274 0 0         if ( $haschild ) {
275             # some child nodes and also text node
276 0           $tree->{$TEXT{NODE}} = $text;
277             }
278             else {
279             # only text node without child nodes
280 0           $tree = $text;
281             }
282             }
283             elsif ( ! $haschild ) {
284             # no child and no text
285 0           $tree = "";
286             }
287            
288             # Move up!
289 0           my $child = $tree;
290             #warn "parent for $name = $context->{parent}\n";
291 0           my $elem = $t->{context}{attrs};
292 0 0         my $hasattr = scalar keys %$elem if ref $elem;
293             # my $forcehash = $FORCE_HASH_ALL || ( $t->{context}{parent}{name} && $FORCE_HASH{$t->{context}{parent}{name}} ) || 0;
294 0   0       my $forcehash = $FORCE_HASH{''} || ( $name && $FORCE_HASH{$name} ) || 0;
295             #warn "$t->{context}{parent}{name} => $name forcehash = $forcehash\n";
296 0           $t->{context} = $t->{context}{parent};
297            
298             #warn "$context->{name} have ".Dumper ($elem);
299 0 0         if ( ref $child eq "HASH" ) {
300 0 0         if ( $hasattr ) {
301             # some attributes and some child nodes
302 0           %$elem = ( %$elem, %$child );
303             }
304             else {
305             # some child nodes without attributes
306 0           $elem = $child;
307             }
308             }
309             else {
310 0 0         if ( $hasattr ) {
    0          
311             # some attributes and text node
312             #warn "${name}: some attributes and text node";
313 0           $elem->{$TEXT{NODE}} = $child;
314             }
315             elsif ( $forcehash ) {
316             # only text node without attributes
317 0           $elem = { $TEXT{NODE} => $child };
318             }
319             else {
320             # text node without attributes
321 0           $elem = $child;
322             }
323             }
324            
325 0           warn "--"x($t->{depth}--) . $name if DEBUG;
326 0   0       push @{ $t->{context}{tree}{$name} ||= [] },$elem;
  0            
327 0           $name = $t->{context}{name};
328 0   0       $tree = $t->{context}{tree} ||= {};
329            
330 0 0         warn "unused args on /$name: @_" if @_;
331             }
332              
333             sub Char {
334 0     0 0   my $xp = shift;
335 0           my $t = $xp->{FunTree};
336             #if ($enc) { @_ = @_; $_ = $enc->encode($_) for @_ };
337 0           my $text = shift;
338 0 0         unless ($t->{context}{textflag}) {
339 0 0         $text =~ s{^[\t\s\r\n]+}{}s if $TEXT{TRIM};
340             }
341 0 0         if ( length $text ){
342 0           warn ".."x(1+$t->{depth}) . $text if DEBUG;
343 0 0         if ($t->{context}{textflag}) {
344 0           ${ $t->{context}{text} }[ $#{ $t->{context}{text} } ] .= $text;
  0            
  0            
345             } else {
346 0           push @{ $t->{context}{text} }, $text;
  0            
347             }
348 0           $t->{context}{textflag} = 1;
349             };
350             }
351              
352             sub Final {
353 0     0 0   my $tree = $_[0]{FunTree}{context}{tree};
354 0           delete $_[0]{FunTree};
355 0 0         if ( ! $FORCE_ARRAY{''} ) {
356 0           foreach my $key ( keys %$tree ) {
357 0 0         next if $FORCE_ARRAY{$key};
358 0 0         next if ( 1 < scalar @{ $tree->{$key} } );
  0            
359 0           $tree->{$key} = shift @{ $tree->{$key} };
  0            
360             }
361             }
362 0           return $tree;
363             }
364              
365             =head1 SEE ALSO
366              
367             =over 4
368              
369             =item * L
370              
371             The parser itself
372              
373             =item * L
374              
375             Another EasyTree (I didn't found it before my first commit of this package because of missing '::Style' in it's name)
376              
377             But since L and L use same style name, they're mutual exclusive ;(
378              
379             So, all the functionality was moved to ETree, and EasyTree was kept as a compatibility wrapper
380              
381             =item * L
382              
383             Very-very fast XML parser. Recommend to look
384              
385             =item * L
386              
387             Similar behaviour, same output, but using L
388              
389             =back
390              
391             =head1 AUTHOR
392              
393             Mons Anderson,
394              
395             =head1 BUGS
396              
397             None known
398              
399             =head1 COPYRIGHT & LICENSE
400              
401             Copyright 2009 Mons Anderson
402              
403             This program is free software; you can redistribute it and/or modify it
404             under the same terms as Perl itself.
405              
406             =cut
407              
408             1;