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