File Coverage

blib/lib/Parse/MediaWikiDump/XML.pm
Criterion Covered Total %
statement 19 32 59.3
branch n/a
condition n/a
subroutine 7 12 58.3
pod n/a
total 26 44 59.0


line stmt bran cond sub pod time code
1             #this is set to become a new module on CPAN after
2             #testing is done and documentation is written
3              
4             #this module is a thin wrapper around XML::Accumulator that
5             #provides a tree interface for the event handlers. The engine
6             #follows the tree as it receives events from XML::Accumulator
7             #so that context can be pulled out from the location in the
8             #tree.
9              
10             #Handlers for this module are also registered as callbacks but
11             #exist at a specific node on the tree. Each handler is invoked
12             #with the same information that came from the XML::Parser event
13             #but is also given an additional argument that is an accumulator
14             #variable to store data in.
15             package Parse::MediaWikiDump::XML::Accumulator;
16              
17 8     8   31 use warnings;
  8         10  
  8         219  
18 8     8   33 use strict;
  8         10  
  8         1260  
19              
20             sub new {
21 0     0     my ($class) = @_;
22 0           my $self = {};
23            
24 0           bless($self, $class);
25             }
26              
27             sub engine {
28 0     0     shift(@_);
29 0           return Parse::MediaWikiDump::XML::Accumulator::Engine->new(@_);
30             }
31              
32             sub node {
33 0     0     shift(@_);
34 0           return Parse::MediaWikiDump::XML::Accumulator::Node->new(@_);
35             }
36              
37             sub root {
38 0     0     shift(@_);
39 0           return Parse::MediaWikiDump::XML::Accumulator::Root->new(@_);
40            
41             }
42              
43             sub textcapture {
44 0     0     shift(@_);
45 0           return Parse::MediaWikiDump::XML::Accumulator::TextCapture->new(@_);
46             }
47              
48             package Parse::MediaWikiDump::XML::Accumulator::Engine;
49              
50 8     8   38 use strict;
  8         8  
  8         147  
51 8     8   24 use warnings;
  8         10  
  8         219  
52 8     8   29 use Carp qw(croak);
  8         14  
  8         446  
53              
54 8     8   35 use Scalar::Util qw(weaken);
  8         9  
  8         565  
55 8     8   5791 use XML::Parser;
  0            
  0            
56              
57             sub new {
58             my ($class, $root, $accum) = @_;
59             my $self = {};
60            
61             croak "must specify a tree root" unless defined $root;
62            
63             eval { $root->validate; };
64             die "root node failed validation: $@" if $@;
65            
66             bless($self, $class);
67            
68             $self->{parser} = $self->init_parser;
69             $self->{root} = $root;
70             $self->{element_stack} = [];
71             $self->{accum} = $accum;
72             $self->{char_buf} = [];
73             $self->{node_stack} = [ $root ];
74            
75             return $self;
76             }
77              
78             sub init_parser {
79             my ($self) = @_;
80            
81             #stop a giant memory leak
82             weaken($self);
83            
84             my $parser = XML::Parser->new(
85             Handlers => {
86             #Init => sub { handle_init_event($self, @_) },
87             #Final => sub { handle_final_event($self, @_) },
88             Start => sub { handle_start_event($self, @_) },
89             End => sub { handle_end_event($self, @_) },
90             Char => sub { handle_char_event($self, @_); },
91             }
92             );
93            
94             return $parser;
95             }
96              
97             sub parser {
98             my ($self) = @_;
99            
100             return $self->{parser};
101             }
102              
103             sub handle_init_event {
104             my ($self, $expat) = @_;
105             my $root = $self->{root};
106             my $handlers = $root->{handlers};
107            
108             if (defined(my $cb = $handlers->{Init})) {
109             &cb($self);
110             }
111             }
112              
113             sub handle_final_event {
114             my ($self, $expat) = @_;
115             my $root = $self->{root};
116             my $handlers = $root->{handlers};
117            
118             if (defined(my $cb = $handlers->{Final})) {
119             &cb($self);
120             }
121             }
122              
123             sub handle_start_event {
124             my ($self, $expat, $element, %attrs) = @_;
125             my $element_stack = $self->{element_stack};
126             my $node = $self->node;
127             my $matched = $node->{children}->{$element};
128             my $handler;
129            
130             $handler = $matched->{handlers}->{Start};
131            
132             $self->flush_chars;
133             defined $handler && &$handler($self, $self->{accum}, $element, \%attrs);
134            
135             push(@{$self->{node_stack}}, $matched);
136             push(@$element_stack, [$element, \%attrs]);
137            
138             }
139              
140             sub handle_end_event {
141             my ($self, $expat, $element) = @_;
142             my $handler = $self->node->{handlers}->{End};
143             my $node_stack = $self->{node_stack};
144            
145             $self->flush_chars;
146              
147             defined $handler && &$handler($self, $self->{accum}, @{$self->element});
148            
149             pop(@$node_stack);
150             pop(@{$self->{element_stack}});
151            
152             }
153              
154             sub handle_char_event {
155             push(@{$_[0]->{char_buf}}, $_[2]);
156             }
157              
158             sub flush_chars {
159             my ($self) = @_;
160             my ($handler, $cur_element);
161            
162             $handler = $self->node->{handlers}->{Character};
163             $cur_element = $self->element;
164            
165             if (! defined($cur_element = $self->element)) {
166             $cur_element = [];
167             }
168            
169             defined $handler && &$handler($self, $self->{accum}, join('', @{$self->{char_buf}}), @$cur_element);
170            
171             $self->{char_buf} = [];
172            
173             return undef;
174             }
175              
176             sub node {
177             my ($self) = @_;
178             my $stack = $self->{node_stack};
179             my $size = scalar(@$stack);
180              
181             return $$stack[$size - 1];
182             }
183              
184             sub element {
185             my ($self) = @_;
186             my $stack = $self->{element_stack};
187             my $size = scalar(@$stack);
188             my $return = $$stack[$size - 1];
189            
190             return $return;
191             }
192              
193             sub accumulator {
194             my ($self, $new) = @_;
195            
196             if (defined($new)) {
197             $self->{accum} = $new;
198             }
199            
200             return $self->{accum};
201             }
202              
203             package Parse::MediaWikiDump::XML::Accumulator::Node;
204              
205             use strict;
206             use warnings;
207              
208             use Carp qw(croak cluck);
209              
210             sub new {
211             my ($class, $name, %handlers) = @_;
212             my $self = {};
213            
214             croak("must specify a node name") unless defined $name;
215            
216             $self->{name} = $name;
217             $self->{handlers} = \%handlers;
218             $self->{children} = {};
219             $self->{debug} = 1;
220            
221             bless($self, $class);
222            
223             return $self;
224             }
225              
226             sub name {
227             my ($self) = @_;
228             return $self->{name};
229             }
230              
231             sub handlers {
232             my ($self) = @_;
233             return $self->{handlers};
234             }
235              
236             sub unset_handlers {
237             my ($self) = @_;
238            
239             $self->{handlers} = undef;
240            
241             foreach (values(%{ $self->{children} })) {
242             $_->unset_handlers;
243             }
244            
245             return 1;
246             }
247              
248             sub error {
249             my ($self, $path, $string) = @_;
250             my $name = $self->{name};
251            
252             if (ref($path) ne 'ARRAY') {
253             cluck "must specify an array ref for node path in tree";
254             }
255            
256             if ($self->{debug}) {
257             print "Fatal error in node $name: $string\n";
258             print "Node tree path:\n";
259            
260             $self->print_path($path);
261             }
262            
263             die "fatal error: $string";
264             }
265              
266             sub print_path {
267             my ($self, $path) = @_;
268             my $i = 0;
269            
270             foreach (@$path) {
271             my ($name) = $_->name;
272             print "$i: $name\n";
273             }
274            
275             return undef;
276             }
277              
278             sub validate {
279             my ($self, $path) = @_;
280             my ($handlers) = $self->{handlers};
281             my (%ok);
282            
283             map({$ok{$_} = 1} $self->ok_handlers);
284            
285             if (! defined($path)) {
286             $path = [];
287             }
288            
289             push(@$path, $self);
290            
291             foreach (keys(%$handlers)) {
292             my $check = $handlers->{$_};
293            
294             if (! defined($check) || ref($check) ne 'CODE') {
295             $self->error($path, "Handler $_: not a code reference");
296             next;
297             }
298            
299             if (! $ok{$_}) {
300             $self->error($path, "$_ is not a valid event name");
301             next;
302             }
303             }
304            
305             foreach (values(%{$self->{children}})) {
306             $_->validate($path);
307             }
308              
309             return undef;
310             }
311              
312             sub ok_handlers {
313             return qw(Character Start End);
314            
315             }
316              
317             sub print {
318             my ($self, $level) = @_;
319            
320             if (! defined($level)) {
321             $level = 1;
322             }
323              
324             print ' ' x $level, "$level: ", $self->name, "\n";
325            
326             $level++;
327            
328             foreach (values(%{$self->{children} } )) {
329             $_->print($level);
330             }
331            
332             $level--;
333             }
334              
335             sub add_child {
336             my ($self, @children) = @_;
337            
338             foreach my $child (@children) {
339             my $name = $child->{name};
340             $self->{children}->{$name} = $child;
341             }
342            
343             return $self;
344             }
345              
346             package Parse::MediaWikiDump::XML::Accumulator::Root;
347              
348             use strict;
349             use warnings;
350              
351             use base qw(Parse::MediaWikiDump::XML::Accumulator::Node);
352              
353             sub new {
354             my ($class) = @_;
355             my $self = $class->SUPER::new('[root container]');
356            
357             bless($self, $class);
358             }
359              
360             sub ok_handlers {
361             return qw(Init Final);
362             }
363              
364             package Parse::MediaWikiDump::XML::Accumulator::TextCapture;
365              
366             use base qw(Parse::MediaWikiDump::XML::Accumulator::Node);
367              
368             use strict;
369             use warnings;
370              
371             sub new {
372             my ($class, $name, $store_as) = @_;
373             my $self = $class->SUPER::new($name);
374            
375             bless($self, $class);
376              
377             if (! defined($store_as)) {
378             $store_as = $name;
379             }
380            
381             $self->{handlers} = {
382             Character => sub { char_handler($store_as, @_); },
383             };
384            
385             return $self;
386             }
387              
388             sub char_handler {
389             my ($store_as, $parser, $a, $chars, $element) = @_;
390            
391             $a->{$store_as} = $chars;
392             }
393              
394             1;