File Coverage

blib/lib/MediaWiki/DumpFile/Pages.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             package MediaWiki::DumpFile::Pages;
2              
3             our $VERSION = '0.2.2';
4             our $TESTED_SCHEMA_VERSION = 0.5;
5              
6 8     8   45 use strict;
  8         14  
  8         274  
7 8     8   43 use warnings;
  8         14  
  8         243  
8 8     8   45 use Scalar::Util qw(reftype);
  8         14  
  8         464  
9 8     8   45 use Carp qw(croak);
  8         12  
  8         348  
10 8     8   61 use Data::Dumper;
  8         43  
  8         372  
11              
12 8     8   9750 use XML::TreePuller;
  0            
  0            
13             use XML::LibXML::Reader;
14             use IO::Uncompress::AnyUncompress qw($AnyUncompressError);
15              
16             sub new {
17             my ($class, @args) = @_;
18             my $self = {};
19             my $reftype;
20             my $xml;
21             my $input;
22             my %conf;
23             my $io;
24            
25             bless($self, $class);
26            
27             $self->{siteinfo} = undef;
28             $self->{version} = undef;
29             $self->{fast_mode} = undef;
30             $self->{version_ignore} = 1;
31            
32             if (scalar(@args) == 0) {
33             croak "must specify a file path or open file handle object or a hash of options";
34             } elsif (scalar(@args) == 1) {
35             $input = $args[0];
36             } elsif (! scalar(@args) % 2) {
37             croak "must specify a hash as an argument";
38             } else {
39             %conf = @args;
40            
41             if (! defined($input = $conf{input})) {
42             croak "input is a required option";
43             }
44            
45             if (defined($conf{fast_mode})) {
46             $self->{fast_mode} = $conf{fast_mode};
47             }
48            
49             if (defined($conf{strict})) {
50             $self->{version_ignore} = $conf{version_ignore};
51             }
52             }
53            
54             $reftype = reftype($input);
55            
56             if (! defined($reftype)) {
57             if (! -e $input) {
58             croak("$input is not a file");
59             }
60            
61             } elsif ($reftype ne 'GLOB') {
62             croak('must provide a GLOB reference');
63             }
64            
65             $self->{input} = $input;
66             $io = IO::Uncompress::AnyUncompress->new($input);
67             $xml = $self->_new_puller(IO => $io);
68            
69             if (exists($ENV{MEDIAWIKI_DUMPFILE_VERSION_IGNORE})) {
70             $self->{version_ignore} = $ENV{MEDIAWIKI_DUMPFILE_VERSION_IGNORE};
71             }
72            
73             if (exists($ENV{MEDIAWIKI_DUMPFILE_FAST_MODE})) {
74             $self->{fast_mode} = $ENV{MEDIAWIKI_DUMPFILE_FAST_MODE};
75             }
76            
77             $self->{xml} = $xml;
78             $self->{reader} = $xml->reader;
79             $self->{input} = $input;
80             $self->{io} = $io;
81              
82             $self->_init_xml;
83            
84             return $self;
85             }
86              
87             sub next {
88             my ($self, $fast) = @_;
89             my $version;
90             my $new;
91            
92             if ($fast || $self->{fast_mode}) {
93             my ($title, $text);
94            
95             if ($self->{finished}) {
96             return ();
97             }
98            
99             eval { ($title, $text) = $self->_fast_next; };
100            
101             if ($@) {
102             chomp($_);
103             croak("E_XML_PARSE_FAILED \"$@\" see the ERRORS section of the MediaWiki::DumpFile::Pages Perl module documentation for what to do");
104             }
105            
106             unless (defined($title)) {
107             $self->{finished} = 1;
108             return ();
109             }
110            
111             return MediaWiki::DumpFile::Pages::FastPage->new($title, $text);
112             }
113              
114             if ($self->{finished}) {
115             return undef;
116             }
117            
118             $version = $self->{version};
119             eval { $new = $self->{xml}->next; };
120            
121             if ($@) {
122             chomp($_);
123             croak("E_XML_PARSE_FAILED \"$@\" see the ERRORS section of the MediaWiki::DumpFile::Pages Perl module documentation for what to do");
124             }
125            
126             unless (defined($new)) {
127             $self->{finished} = 1;
128             return undef;
129             }
130            
131             return MediaWiki::DumpFile::Pages::Page->new($new, $version);
132             }
133              
134             sub size {
135             my $source = $_[0]->{input};
136            
137             unless(defined($source) && ref($source) eq '') {
138             return undef;
139             }
140            
141             #if we are decompressing a file on the fly then don't report the size
142             #of the file because we don't actually know the uncompressed size,
143             #only the compressed size
144             if (defined($_[0]->{io}->getHeaderInfo)) {
145             return undef;
146             }
147            
148             my @stat = stat($source);
149             return $stat[7];
150             }
151              
152             sub current_byte {
153             return $_[0]->{xml}->reader->byteConsumed;
154             }
155              
156             sub completed {
157             my ($self) = @_;
158             my $size = $self->size;
159             my $current = $self->current_byte;
160            
161             return -1 unless (defined($size) && defined($current));
162            
163             return int($current / $size * 100);
164             }
165              
166             sub version {
167             return $_[0]->{version};
168             }
169              
170             #private methods
171              
172             sub _init_xml {
173             my ($self) = @_;
174             my $xml = $self->{xml};
175             my $version;
176            
177             $xml->iterate_at('/mediawiki', 'short');
178             $xml->iterate_at('/mediawiki/siteinfo', 'subtree');
179             $xml->iterate_at('/mediawiki/page', 'subtree');
180            
181             $version = $self->{version} = $xml->next->attribute('version');
182              
183             unless ($self->{version_ignore}) {
184             $self->_version_enforce($version);
185             }
186            
187             if ($version > 0.2) {
188             $self->{siteinfo} = $xml->next;
189            
190             bless($self, 'MediaWiki::DumpFile::PagesSiteinfo');
191             }
192            
193             return undef;
194             }
195              
196             sub _version_enforce {
197             my ($self, $version) = @_;
198              
199             if ($version > $TESTED_SCHEMA_VERSION) {
200             my $filename;
201             my $msg;
202            
203             if (ref($self->{input}) eq '') {
204             $filename = $self->{input};
205             } else {
206             $filename = ref($self->{input});
207             }
208            
209             $msg = "E_UNTESTED_DUMP_VERSION Version $version dump file \"$filename\" has not been tested with ";
210             $msg .= __PACKAGE__ . " version $VERSION; see the ERRORS section of the MediaWiki::DumpFile::Pages Perl module documentation for what to do";
211              
212             die $msg;
213             }
214              
215             }
216              
217             sub _new_puller {
218             my ($self, @args) = @_;
219             my $ret;
220            
221             eval { $ret = XML::TreePuller->new(@args) };
222            
223             if ($@) {
224             chomp($@);
225             croak("E_XML_CREATE_FAILED \"$@\" see the ERRORS section of the MediaWiki::DumpFile::Pages Perl module documentation for what to do")
226             }
227            
228             return $ret;
229             }
230              
231             sub _get_text {
232             my ($self) = @_;
233             my $r = $self->{reader};
234             my @buffer;
235             my $type;
236              
237             while($r->nodeType != XML_READER_TYPE_TEXT && $r->nodeType != XML_READER_TYPE_END_ELEMENT) {
238             $r->read or die "could not read";
239             }
240              
241             while($r->nodeType != XML_READER_TYPE_END_ELEMENT) {
242             if ($r->nodeType == XML_READER_TYPE_TEXT) {
243             push(@buffer, $r->value);
244             }
245            
246             $r->read or die "could not read";
247             }
248              
249             return join('', @buffer);
250             }
251              
252             sub _fast_next {
253             my ($self) = @_;
254             my $reader = $self->{reader};
255             my ($title, $text);
256            
257             if ($self->{finished}) {
258             return ();
259             }
260            
261             while(1) {
262             my $type = $reader->nodeType;
263            
264             if ($type == XML_READER_TYPE_ELEMENT) {
265             if ($reader->name eq 'title') {
266             $title = $self->_get_text();
267             last unless $reader->nextElement('text') == 1;
268             next;
269             } elsif ($reader->name eq 'text') {
270             $text = $self->_get_text();
271             $reader->nextElement('page');
272             last;
273             }
274             }
275            
276             last unless $reader->nextElement == 1;
277             }
278            
279             if (! defined($title) || ! defined($text)) {
280             $self->{finished} = 1;
281             return ();
282             }
283              
284             return($title, $text);
285             }
286              
287             package MediaWiki::DumpFile::PagesSiteinfo;
288              
289             use base qw(MediaWiki::DumpFile::Pages);
290              
291             use Data::Dumper;
292              
293             use MediaWiki::DumpFile::Pages::Lib qw(_safe_text);
294              
295             sub _site_info {
296             my ($self, $name) = @_;
297             my $siteinfo = $self->{siteinfo};
298            
299             return _safe_text($siteinfo, $name);
300             }
301              
302             sub sitename {
303             return $_[0]->_site_info('sitename');
304             }
305              
306             sub base {
307             return $_[0]->_site_info('base');
308             }
309              
310             sub generator {
311             return $_[0]->_site_info('generator');
312             }
313              
314             sub case {
315             return $_[0]->_site_info('case');
316             }
317              
318             sub namespaces {
319             my ($self) = @_;
320             my @e = $self->{siteinfo}->get_elements('namespaces/namespace');
321             my %ns;
322            
323             map({ $ns{ $_->attribute('key') } = $_->text } @e);
324              
325             return %ns;
326             }
327              
328             package MediaWiki::DumpFile::Pages::Page;
329              
330             use strict;
331             use warnings;
332             use Data::Dumper;
333              
334             use MediaWiki::DumpFile::Pages::Lib qw(_safe_text);
335              
336             sub new {
337             my ($class, $element, $version) = @_;
338             my $self = { tree => $element };
339            
340             bless($self, $class);
341            
342             if ($version >= 0.4) {
343             bless ($self, 'MediaWiki::DumpFile::Pages::Page000004000');
344             }
345            
346             return $self;
347             }
348              
349             sub title {
350             return _safe_text($_[0]->{tree}, 'title');
351             }
352              
353             sub id {
354             return _safe_text($_[0]->{tree}, 'id');
355             }
356              
357             sub revision {
358             my ($self) = @_;
359             my @revisions;
360            
361             foreach ($self->{tree}->get_elements('revision')) {
362             push(@revisions, MediaWiki::DumpFile::Pages::Page::Revision->new($_));
363             }
364            
365             if (wantarray()) {
366             return (@revisions);
367             }
368            
369             return pop(@revisions);
370             }
371              
372             package MediaWiki::DumpFile::Pages::Page000004000;
373              
374             use base qw(MediaWiki::DumpFile::Pages::Page);
375              
376             use strict;
377             use warnings;
378              
379             sub redirect {
380             return 1 if defined $_[0]->{tree}->get_elements('redirect');
381             return 0;
382             }
383              
384              
385             package MediaWiki::DumpFile::Pages::Page::Revision;
386              
387             use strict;
388             use warnings;
389              
390             use MediaWiki::DumpFile::Pages::Lib qw(_safe_text);
391              
392             sub new {
393             my ($class, $tree) = @_;
394             my $self = { tree => $tree };
395            
396             return bless($self, $class);
397             }
398              
399             sub text {
400             return _safe_text($_[0]->{tree}, 'text');
401             }
402              
403             sub id {
404             return _safe_text($_[0]->{tree}, 'id');
405             }
406              
407             sub timestamp {
408             return _safe_text($_[0]->{tree}, 'timestamp');
409             }
410              
411             sub comment {
412             return _safe_text($_[0]->{tree}, 'comment');
413             }
414              
415             sub minor {
416             return 1 if defined $_[0]->{tree}->get_elements('minor');
417             return 0;
418             }
419              
420             sub contributor {
421             return MediaWiki::DumpFile::Pages::Page::Revision::Contributor->new(
422             $_[0]->{tree}->get_elements('contributor') );
423             }
424              
425             package MediaWiki::DumpFile::Pages::Page::Revision::Contributor;
426              
427             use strict;
428             use warnings;
429              
430             use Carp qw(croak);
431              
432             use overload
433             '""' => 'astext',
434             fallback => 'TRUE';
435              
436             sub new {
437             my ($class, $tree) = @_;
438             my $self = { tree => $tree };
439            
440             return bless($self, $class);
441             }
442              
443             sub astext {
444             my ($self) = @_;
445            
446             if (defined($self->ip)) {
447             return $self->ip;
448             }
449            
450             return $self->username;
451             }
452              
453             sub username {
454             my $user = $_[0]->{tree}->get_elements('username');
455            
456             return undef unless defined $user;
457            
458             return $user->text;
459             }
460              
461             sub id {
462             my $id = $_[0]->{tree}->get_elements('id');
463            
464             return undef unless defined $id;
465            
466             return $id->text;
467             }
468              
469             sub ip {
470             my $ip = $_[0]->{tree}->get_elements('ip');
471            
472             return undef unless defined $ip;
473            
474             return $ip->text;
475             }
476              
477             package MediaWiki::DumpFile::Pages::FastPage;
478              
479             sub new {
480             my ($class, $title, $text) = @_;
481             my $self = { title => $title, text => $text };
482            
483             bless($self, $class);
484            
485             return $self;
486             }
487              
488             sub title {
489             return $_[0]->{title};
490             }
491              
492             sub text {
493             return $_[0]->{text};
494             }
495              
496             sub revision {
497             return $_[0];
498             }
499              
500             1;
501              
502             __END__