File Coverage

blib/lib/XML/Parser/Lite/Tree.pm
Criterion Covered Total %
statement 153 154 99.3
branch 29 36 80.5
condition 11 13 84.6
subroutine 25 25 100.0
pod 3 6 50.0
total 221 234 94.4


line stmt bran cond sub pod time code
1             package XML::Parser::Lite::Tree;
2              
3 8     8   256464 use 5.006;
  8         32  
  8         349  
4 8     8   62 use strict;
  8         30  
  8         301  
5 8     8   41 use warnings;
  8         16  
  8         264  
6 8     8   4931 use XML::Parser::LiteCopy;
  8         166  
  8         432  
7              
8             our $VERSION = '0.14';
9              
10 8     8   57 use vars qw( $parser );
  8         15  
  8         15258  
11              
12             sub instance {
13 4 50   4 1 65 return $parser if $parser;
14 4         30 $parser = __PACKAGE__->new;
15             }
16              
17             sub new {
18 9     9 1 6218 my $class = shift;
19 9         36 my $self = bless {}, $class;
20              
21 9 0       134 my %opts = (ref $_[0]) ? ((ref $_[0] eq 'HASH') ? %{$_[0]} : () ) : @_;
  0 50       0  
22 9         66 $self->{opts} = \%opts;
23              
24             $self->{__parser} = new XML::Parser::LiteCopy
25             Handlers => {
26 29     29   102 Start => sub { $self->_start_tag(@_); },
27 21     21   67 Char => sub { $self->_do_char(@_); },
28 1     1   5 CData => sub { $self->_do_cdata(@_); },
29 29     29   84 End => sub { $self->_end_tag(@_); },
30 1     1   13 Comment => sub { $self->_do_comment(@_); },
31 2     2   6 PI => sub { $self->_do_pi(@_); },
32 1     1   6 Doctype => sub { $self->_do_doctype(@_); },
33 9         299 };
34 9   100     87 $self->{process_ns} = $self->{opts}->{process_ns} || 0;
35 9   100     132 $self->{skip_white} = $self->{opts}->{skip_white} || 0;
36              
37 9         42 return $self;
38             }
39              
40             sub parse {
41 9     9 1 574 my ($self, $content) = @_;
42              
43 9         44 my $root = {
44             'type' => 'root',
45             'children' => [],
46             };
47              
48 9         30 $self->{tag_stack} = [$root];
49              
50 9         70 $self->{__parser}->parse($content);
51              
52 9         55 $self->cleanup($root);
53              
54 9 100       34 if ($self->{skip_white}){
55 5         36 $self->strip_white($root);
56             }
57              
58 9 100       35 if ($self->{process_ns}){
59 1         3 $self->{ns_stack} = {};
60 1         5 $self->mark_namespaces($root);
61             }
62              
63 9         42 return $root;
64             }
65              
66             sub _start_tag {
67 29     29   43 my $self = shift;
68 29         34 shift;
69              
70 29         156 my $new_tag = {
71             'type' => 'element',
72             'name' => shift,
73             'attributes' => {},
74             'children' => [],
75             };
76              
77 29         99 while (my $a_name = shift @_){
78 14         24 my $a_value = shift @_;
79 14         75 $new_tag->{attributes}->{$a_name} = $a_value;
80             }
81              
82 29         36 push @{$self->{tag_stack}->[-1]->{children}}, $new_tag;
  29         74  
83 29         661 push @{$self->{tag_stack}}, $new_tag;
  29         69  
84 29         933 1;
85             }
86              
87             sub _do_char {
88 21     21   28 my $self = shift;
89 21         33 shift;
90              
91 21         46 for my $content(@_){
92              
93 21         78 my $new_tag = {
94             'type' => 'text',
95             'content' => $content,
96             };
97              
98 21         26 push @{$self->{tag_stack}->[-1]->{children}}, $new_tag;
  21         95  
99             }
100 21         939 1;
101             }
102              
103             sub _do_cdata {
104 1     1   1 my $self = shift;
105 1         2 shift;
106              
107 1         3 for my $content(@_){
108              
109 1         6 my $new_tag = {
110             'type' => 'cdata',
111             'content' => $content,
112             };
113              
114 1         2 push @{$self->{tag_stack}->[-1]->{children}}, $new_tag;
  1         5  
115             }
116 1         40 1;
117             }
118              
119             sub _end_tag {
120 29     29   45 my $self = shift;
121              
122 29         35 pop @{$self->{tag_stack}};
  29         67  
123 29         916 1;
124             }
125              
126             sub _do_comment {
127 1     1   3 my $self = shift;
128 1         2 shift;
129              
130 1         4 for my $content(@_){
131              
132 1         6 my $new_tag = {
133             'type' => 'comment',
134             'content' => $content,
135             };
136              
137 1         3 push @{$self->{tag_stack}->[-1]->{children}}, $new_tag;
  1         5  
138             }
139 1         45 1;
140             }
141              
142             sub _do_pi {
143 2     2   3 my $self = shift;
144 2         2 shift;
145              
146 2         3 push @{$self->{tag_stack}->[-1]->{children}}, {
  2         11  
147             'type' => 'pi',
148             'content' => shift,
149             };
150 2         43 1;
151             }
152              
153             sub _do_doctype {
154 1     1   2 my $self = shift;
155 1         2 shift;
156              
157 1         2 push @{$self->{tag_stack}->[-1]->{children}}, {
  1         8  
158             'type' => 'dtd',
159             'content' => shift,
160             };
161 1         41 1;
162             }
163              
164             sub mark_namespaces {
165 5     5 0 8 my ($self, $obj) = @_;
166              
167 5         7 my @ns_keys;
168              
169             #
170             # mark
171             #
172              
173 5 100       50 if ($obj->{type} eq 'element'){
174              
175             #
176             # first, add any new NS's to the stack
177             #
178              
179 4         5 my @keys = keys %{$obj->{attributes}};
  4         13  
180              
181 4         7 for my $k(@keys){
182              
183 4 100       16 if ($k =~ /^xmlns:(.*)$/){
184              
185 2         3 push @{$self->{ns_stack}->{$1}}, $obj->{attributes}->{$k};
  2         10  
186 2         6 push @ns_keys, $1;
187 2         4 delete $obj->{attributes}->{$k};
188             }
189              
190 4 100       20 if ($k eq 'xmlns'){
191              
192 2         3 push @{$self->{ns_stack}->{__default__}}, $obj->{attributes}->{$k};
  2         6  
193 2         4 push @ns_keys, '__default__';
194 2         6 delete $obj->{attributes}->{$k};
195             }
196             }
197              
198              
199             #
200             # now - does this tag have a NS?
201             #
202              
203 4 100       15 if ($obj->{name} =~ /^(.*?):(.*)$/){
204              
205 1         3 $obj->{local_name} = $2;
206 1         2 $obj->{ns_key} = $1;
207 1         4 $obj->{ns} = $self->{ns_stack}->{$1}->[-1];
208             }else{
209 3         7 $obj->{local_name} = $obj->{name};
210 3         8 $obj->{ns} = $self->{ns_stack}->{__default__}->[-1];
211             }
212              
213              
214             #
215             # finally, add xpath-style namespace nodes
216             #
217              
218 4         9 $obj->{namespaces} = {};
219              
220 4         5 for my $key (keys %{$self->{ns_stack}}){
  4         11  
221              
222 9 50       10 if (scalar @{$self->{ns_stack}->{$key}}){
  9         27  
223              
224 9         14 my $uri = $self->{ns_stack}->{$key}->[-1];
225 9         24 $obj->{namespaces}->{$key} = $uri;
226             }
227             }
228             }
229              
230              
231             #
232             # descend
233             #
234              
235 5 50 66     31 if ($obj->{type} eq 'root' || $obj->{type} eq 'element'){
236              
237 5         6 for my $child (@{$obj->{children}}){
  5         10  
238              
239 4         17 $self->mark_namespaces($child);
240             }
241             }
242              
243              
244             #
245             # pop from stack
246             #
247              
248 5         9 for my $k (@ns_keys){
249 4         6 pop @{$self->{ns_stack}->{$k}};
  4         21  
250             }
251             }
252              
253             sub strip_white {
254 20     20 0 33 my ($self, $obj) = @_;
255              
256 20 50 66     177 if ($obj->{type} eq 'root' || $obj->{type} eq 'element'){
257              
258 20         33 my $new_kids = [];
259              
260 20         26 for my $child (@{$obj->{children}}){
  20         43  
261              
262 38 100       168 if ($child->{type} eq 'text'){
    100          
263              
264 19 100       99 if ($child->{content} =~ m/\S/){
265              
266 1         2 push @{$new_kids}, $child;
  1         3  
267             }
268              
269             }elsif ($child->{type} eq 'element'){
270              
271 15         70 $self->strip_white($child);
272 15         35 push @{$new_kids}, $child;
  15         36  
273             }else{
274 4         49 push @{$new_kids}, $child;
  4         11  
275             }
276             }
277              
278 20         96 $obj->{children} = $new_kids;
279             }
280             }
281              
282             sub cleanup {
283 64     64 0 90 my ($self, $obj) = @_;
284              
285             #
286             # cleanup PIs
287             #
288              
289 64 100       152 if ($obj->{type} eq 'pi'){
290              
291 2         6 my ($x, $y) = split /\s+/, $obj->{content}, 2;
292 2         4 $obj->{target} = $x;
293 2         3 $obj->{content} = $y;
294             }
295              
296              
297             #
298             # cleanup DTDs
299             #
300              
301 64 100       150 if ($obj->{type} eq 'dtd'){
302              
303 1         6 my ($x, $y) = split /\s+/, $obj->{content}, 2;
304 1         2 $obj->{name} = $x;
305 1         3 $obj->{content} = $y;
306             }
307              
308              
309             #
310             # recurse
311             #
312            
313 64 100 100     323 if ($obj->{type} eq 'root' || $obj->{type} eq 'element'){
314              
315 38         45 for my $child (@{$obj->{children}}){
  38         101  
316              
317 55         247 $self->cleanup($child);
318             }
319             }
320             }
321              
322              
323             1;
324             __END__