File Coverage

blib/lib/XML/TreePuller.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package XML::TreePuller;
2              
3             our $VERSION = '0.1.3_01';
4              
5 7     7   194948 use strict;
  7         18  
  7         278  
6 7     7   39 use warnings;
  7         13  
  7         174  
7 7     7   7333 use Data::Dumper;
  7         86445  
  7         617  
8 7     7   66 use Carp qw(croak carp);
  7         13  
  7         408  
9              
10 7     7   10965 use XML::LibXML::Reader;
  0            
  0            
11              
12             use XML::TreePuller::Element;
13             use XML::TreePuller::Constants;
14             use XML::CompactTree;
15              
16             our $NO_XS;
17              
18             BEGIN {
19            
20             if ($ENV{XML_TREEPULLER_NO_XS}) {
21             $NO_XS = $ENV{XML_TREEPULLER_NO_XS};
22             }
23            
24             if (! $NO_XS && ! defined(eval { require XML::CompactTree::XS; })) {
25             $NO_XS = 1;
26             }
27              
28             }
29              
30             sub new {
31             my ($class, @args) = @_;
32             my $self = {};
33             my $reader;
34            
35             bless($self, $class);
36            
37             $self->{elements} = [];
38             $self->{config} = {};
39             $self->{finished} = 0;
40              
41             $Carp::CarpLevel++;
42             $reader = $self->{reader} = XML::LibXML::Reader->new(@args);
43             $Carp::CarpLevel--;
44            
45             #arg how do you get error messages out of libxml reader?
46             croak("could not construct libxml reader") unless defined $reader;
47            
48             return $self;
49             }
50              
51             sub parse {
52             my ($class, @args) = @_;
53            
54             return $class->new(@args)->next;
55             }
56              
57             sub iterate_at {
58             my ($self, $path, $todo) = @_;
59            
60             croak("must specify match and instruction") unless defined $path && defined $todo;
61            
62             $self->{config}->{$path} = $todo;
63            
64             return undef;
65             }
66              
67             sub config {
68             #turn this warning on later
69             #carp "config() is depreciated, use iterate_at() instead";
70            
71             return iterate_at(@_);
72             }
73              
74             sub next {
75             my ($self) = @_;
76             my $reader = $self->{reader};
77             my $elements = $self->{elements};
78             my $config = $self->{config};
79             my $ret;
80            
81             return () if $self->{finished};
82              
83             if ($reader->nodeType != XML_READER_TYPE_ELEMENT) {
84             if (! $self->_find_next_element) {
85             #no more elements available in the document
86             return ();
87             }
88             }
89            
90             #the reader came in already sitting on an element so we have to
91             #iterate at the end of the loop
92             do {
93             my $path;
94             my $todo;
95             my $ret;
96            
97             if(! $self->_sync) {
98             #ran out of data in the document
99             return ();
100             }
101            
102             push(@$elements, $reader->name);
103            
104             $path = '/' . join('/', @$elements);
105            
106             #handle the default case where no config is specified
107             if (scalar(keys(%$config)) == 0) {
108             $self->{finished} = 1;
109            
110             if (wantarray()) {
111             return($path, $self->_read_subtree);
112             }
113            
114             return $self->_read_subtree;
115             }
116            
117             #if this is converted over a dispatch hash then
118             #the keys in the hash can be used to validate items
119             #as they are passed to next() and allow this
120             #method to scale to more instructions
121             if (defined($todo = $config->{$path})) {
122             if ($todo eq 'short') {
123             $ret = $self->_read_element;
124             } elsif ($todo eq 'subtree') {
125             $ret = $self->_read_subtree;
126             } else {
127             die "invalid todo specified: $todo";
128             }
129            
130             if (wantarray()) {
131             return($path, $ret);
132             }
133            
134             return $ret;
135             }
136            
137             } while ($self->_find_next_element);
138            
139             return ();
140             }
141              
142             sub reader {
143             return $_[0]->{reader};
144             }
145              
146             #private methods
147              
148             #get the reader to a point where it is in sync with
149             #our internal element list
150             sub _sync {
151             my ($self) = @_;
152             my $reader = $self->{reader};
153             my $depth = $self->{reader}->depth;
154             my $elements = $self->{elements};
155              
156             #if we are at a higher level than we have
157             #tracked to we need to get back to the same
158             #depth as our element list to properly process
159             #data again
160             while(scalar(@$elements) < $reader->depth) {
161             my $ret = $reader->nextElement;
162            
163             if ($ret == -1) {
164             die "libxml read error";
165             } elsif ($ret == 0) {
166             $self->{finished} = 1;
167             return 0;
168             }
169             }
170              
171             #handle the case where the reader is at a lower
172             #depth than we have tracked to
173             splice(@$elements, $reader->depth);
174            
175             return 1;
176             }
177              
178              
179             sub _find_next_element {
180             my ($self) = @_;
181             my $reader = $self->{reader};
182             my $ret;
183            
184             if (! ($ret = $reader->nextElement)) {
185             $self->{finished} = 1;
186            
187             return 0;
188             } elsif ($ret == -1) {
189             die "libxml read error";
190             }
191            
192             return 1;
193             }
194              
195             sub _read_subtree {
196             my ($self) = @_;
197             my $reader = $self->{reader};
198             my $elements = $self->{elements};
199            
200             my $tree = XML::TreePuller::Element->new(_read_tree($reader));
201            
202             if (! defined($tree)) {
203             $self->{finished} = 1;
204             return undef;
205             }
206            
207             return $tree;
208             }
209              
210             sub _read_element {
211             my ($self) = @_;
212             my $reader = $self->{reader};
213             my $is_empty = $reader->isEmptyElement;
214             my $new;
215             my %attr;
216             my $node_type;
217             my $ret;
218            
219             $new->[XML_TREEPULLER_ELEMENT_TYPE] = 1;
220             $new->[XML_TREEPULLER_ELEMENT_NAME] = $reader->name;
221             $new->[XML_TREEPULLER_ELEMENT_NAMESPACE] = 0;
222             $new->[XML_TREEPULLER_ELEMENT_ATTRIBUTES] = \%attr;
223             $new->[XML_TREEPULLER_ELEMENT_CHILDREN] = [];
224            
225            
226             if ($reader->hasAttributes && $reader->moveToFirstAttribute == 1) {
227             do {
228             my $name = $reader->name;
229             my $val = $reader->value;
230            
231             $attr{$name} = $val;
232             } while($reader->moveToNextAttribute == 1);
233             }
234              
235              
236             $ret = $reader->read;
237            
238             if ($ret == -1) {
239             die "libxml read error";
240             } elsif ($ret == 0) {
241             return undef;
242             }
243              
244             if ($is_empty) {
245             return XML::TreePuller::Element->new($new);
246             }
247              
248             $node_type = $reader->nodeType;
249            
250             while($node_type != XML_READER_TYPE_ELEMENT && $node_type != XML_READER_TYPE_END_ELEMENT) {
251             $node_type = $reader->nodeType;
252            
253             if ($node_type == XML_READER_TYPE_TEXT || $node_type == XML_READER_TYPE_CDATA) {
254             push(@{$new->[XML_TREEPULLER_ELEMENT_CHILDREN]}, [ $node_type, $reader->value ]);
255             }
256              
257             $ret = $reader->read;
258            
259             if ($ret == -1) {
260             die "libxml read error";
261             } elsif ($ret == 0) {
262             return undef;
263             }
264            
265             $node_type = $reader->nodeType;
266              
267             }
268            
269             return XML::TreePuller::Element->new($new);
270             }
271              
272             sub _read_tree {
273             my ($r) = @_;
274            
275             if ($NO_XS) {
276             return XML::CompactTree::readSubtreeToPerl($r, 0);
277             }
278            
279             return XML::CompactTree::XS::readSubtreeToPerl($r, 0);
280             }
281              
282             1;
283              
284             __END__