File Coverage

blib/lib/XHTML/MediaWiki.pm
Criterion Covered Total %
statement 414 608 68.0
branch 83 204 40.6
condition 21 71 29.5
subroutine 80 109 73.3
pod 10 10 100.0
total 608 1002 60.6


line stmt bran cond sub pod time code
1 2     2   62794 use warnings;
  2         5  
  2         172  
2 2     2   13 use strict;
  2         4  
  2         208  
3              
4             package XHTML::MediaWiki;
5             #XHTML::MediaWiki::
6              
7             =head1 NAME
8              
9             XHTML::MediaWiki - Translate Wiki markup into xhtml
10              
11             =cut
12              
13             our $VERSION = '0.11';
14             $VERSION = eval $VERSION;
15              
16             our $DEBUG = 0;
17              
18             =head1 SYNOPSIS
19              
20             use XHTML::MediaWiki;
21             my $mediawiki = XHTML::MediaWiki->new( link_path => "http://example.com/base/" );
22             my $xhtm = $mediawiki->format($text);
23              
24             =head1 DESCRIPTION
25              
26             L and its sister projects use the PHP
27             Mediawiki to format their pages. This module attempts to duplicate the
28             Mediawiki formatting rules. Those formatting rules can be simple and
29             easy to use, while providing more advanced options for the power user.
30              
31             =cut
32              
33 2     2   11 use Carp qw(carp confess croak);
  2         4  
  2         559  
34 2     2   6370 use CGI qw(:standard);
  2         70192  
  2         18  
35 2     2   12341 use Scalar::Util qw(blessed);
  2         7  
  2         438  
36              
37 2     2   3226 use HTML::Parser;
  2         20343  
  2         473  
38              
39             =head2 Constructors
40              
41             =over 4
42              
43             =item * new( link_path => 'base path' )
44              
45             Create a new XHTML:;MediaWiki object. C is used as the base
46             for hyperlinks.
47              
48             =back
49              
50             =cut
51              
52             sub new
53             {
54 2     2 1 808 my $class = shift;
55              
56 2         15 bless {
57             link_path => '',
58             @_
59             }, $class;
60             }
61              
62             =head2 Methods
63              
64             =over 4
65              
66             =item * format()
67              
68             The format method is the only method that needs to be called for the
69             normal operation of this object. You call format() with the raw I and
70             it returns the xhtml representation of that I.
71              
72             =cut
73              
74             sub format
75             {
76 1     1 1 7 my $self = shift;
77 1         2 my $raw = shift;
78              
79 1         4 my $cooked = $self->_format($raw);
80              
81 1         4 return $cooked;
82             }
83              
84             =item * reset_counters()
85              
86             Call this method to reset the footnote counter.
87              
88             =back
89              
90             =cut
91              
92             sub reset_counters
93             {
94 0     0 1 0 my $self = shift;
95              
96 0         0 $self->{footnote} = 0;
97             }
98              
99             =head2 Overridable Methods
100              
101             The following methods can be overridden to change the functionality of
102             the object.
103              
104             =over 4
105              
106             =item * get_block()
107              
108             If you would like to override the Block objects you can override this method.
109              
110             =cut
111              
112             sub get_block
113             {
114 5     5 1 7 my $self = shift;
115 5         6 my $type = shift;
116              
117 5   50     21 my $ret = 'XHTML::MediaWiki::Block::' . ucfirst($type || 'special');
118             }
119              
120             # This sub recognizes three states:
121             #
122             # 1. undef
123             # Normal wiki processing will be done on this line.
124             #
125             # 2. html
126             # Links and phrasal processing will be done, but formatting should be
127             # ignored.
128             #
129             # 3. nowiki
130             # No further wiki processing should be done.
131             #
132             # Each state may override the lower ones if already set on a given line.
133             #
134              
135             {
136             package XHTML::MediaWiki::Parser::Block::Line;
137              
138 2     2   2051 use Params::Validate qw (validate);
  2         26345  
  2         440  
139              
140             sub new
141             {
142 9     9   11 my $class = shift;
143 9         150 my %p = validate(@_, {
144             state => 1,
145             text => {
146             default => '',
147             },
148             eol => 0,
149             });
150              
151 9         68 my $self = bless { %p }, $class;
152              
153 9         77 return $self;
154             }
155              
156             sub state
157             {
158 10     10   73 shift->{state};
159             }
160              
161             sub append
162             {
163 26     26   27 my $self = shift;
164 26         30 my $text = shift;
165 26         100 $self->{text} .= $text;
166             }
167              
168             }
169             {
170             package XHTML::MediaWiki::Parser::Block;
171              
172 2     2   18 use Params::Validate qw (validate);
  2         5  
  2         82  
173 2     2   9 use Carp qw(croak);
  2         5  
  2         3841  
174              
175             sub new
176             {
177 6     6   7 my $class = shift;
178 6         92 my %p = validate(@_, {
179             type => 1,
180             level => 0,
181             });
182 6 50 33     40 croak("internal error") if ($p{type} eq 'unordered' && !$p{level});
183 6         36 my $self =
184             bless {
185             lines => [],
186             %p,
187             }, $class;
188              
189 6         21 return $self;
190             }
191              
192             sub block_type
193             {
194 5     5   10 shift->{type};
195             }
196              
197             sub args
198             {
199 5     5   7 my $self = shift;
200 5 50       20 push(@{$self->{lines}}, $self->{line}) if $self->{line};
  5         13  
201             return (
202 5         35 lines => $self->{lines},
203             (level => $self->{level}) x!! $self->{level},
204             );
205             }
206              
207             sub get_line
208             {
209 15     15   15 my $self = shift;
210              
211 15   66     53 $self->{line} ||= XHTML::MediaWiki::Parser::Block::Line->new( state => 'wiki');
212             }
213              
214             sub get_state
215             {
216 10     10   9 my $self = shift;
217              
218 10         20 $self->{type};
219             }
220              
221             sub in_nowiki
222             {
223 10     10   11 my $self = shift;
224 10         11 my $line = $self->{line};
225              
226 10 50       16 if ($line) {
227 10         18 return $line->state eq 'nowiki';
228             } else {
229 0         0 return 0;
230             }
231             }
232              
233             sub append_text
234             {
235 15     15   12 my $self = shift;
236 15         20 my $text = shift;
237 15 50       27 die "extra arguments" if @_;
238              
239 15         28 my $line = $self->get_line();
240 15         33 $line->append($text);
241             }
242              
243             sub set_nowiki
244             {
245 0     0   0 my $self = shift;
246              
247 0 0       0 push(@{$self->{lines}}, $self->{line}) if $self->{line};
  0         0  
248 0         0 $self->{line} = XHTML::MediaWiki::Parser::Block::Line->new(state => 'nowiki');
249             }
250              
251             sub set_wiki
252             {
253 0     0   0 my $self = shift;
254              
255 0 0       0 push(@{$self->{lines}}, $self->{line}) if $self->{line};
  0         0  
256 0         0 $self->{line} = XHTML::MediaWiki::Parser::Block::Line->new(state => 'wiki');
257             }
258              
259             sub is_paragraph
260             {
261 0     0   0 my $self = shift;
262              
263 0         0 return $self->{type} eq 'paragraph';
264             }
265              
266             sub is_prewiki
267             {
268 8     8   9 my $self = shift;
269              
270 8         21 return $self->{type} eq 'prewiki';
271             }
272              
273             sub set_end_line
274             {
275 11     11   12 my $self = shift;
276 11 50       26 my $cnt = shift or croak "need count";
277              
278 11         14 my $line = $self->{line};
279 11 100       21 if (!defined $line) {
280 3   33     83 $line = $self->{lines}[-1] || XHTML::MediaWiki::Parser::Block::Line->new(state => 'dummy');
281 3         11 $line->{eol} = $cnt;
282             }
283 11         25 for (my $x = 0; $x < $cnt; $x++) {
284 11         19 $line->append("\n");;
285             }
286 11         27 $self;
287             }
288             }
289              
290             =item * encode()
291              
292             You can override the encode function if you would like to change
293             what is encoded. Currently only &, <, and > are encoded.
294              
295             =cut
296              
297             sub encode
298             {
299 13     13 1 17 my $text = shift;
300 13 50       24 if (defined $text) {
301 13         17 $text =~ s{&}{&}gso;
302 13         14 $text =~ s{<}{<}gso;
303 13         17 $text =~ s{>}{>}gso;
304             }
305 13         23 return $text;
306             }
307              
308             sub _close_to
309             {
310 1     1   2 my $parser = shift;
311 1         2 my $tag = shift;
312 1         2 my $tagstack = $parser->{tag_stack};
313 1         2 my $text = '';
314              
315 1 50       3 if (!@$tagstack) {
316 0 0       0 $text .= "" if $DEBUG;
317             # ignore extra closing tags
318             } else {
319 1         4 while (my $toptag = pop @$tagstack) {
320 1         2 $text .= "";
321 1 50       4 last if $tag eq $toptag;
322             }
323             }
324              
325 1         3 return $text;
326             }
327              
328             sub _html_tag
329             {
330 2     2   6 my ($parser, $type, $tagname, $orig, $attr) = @_;
331 2         3 $tagname =~ s|/$||;
332              
333 2 0 0     5 if ($parser->in_nowiki && ($type ne 'E' || $tagname ne 'nowiki')) {
      33        
334 0         0 $parser->append_text(encode($orig));
335 0         0 return;
336             }
337 2 0 0     6 if ($parser->in_state('pre') && ($type ne 'E' || $tagname ne 'pre')) {
      33        
338 0         0 $parser->append_text(encode($orig));
339 0         0 return;
340             }
341 2 50       7 if (my $info = $parser->{tags}{$tagname}) {
342 2         3 my $tagstack = $parser->{tag_stack};
343 2 100       6 if ($type eq 'E') {
344 1 50       11 if ($info->{empty}) {
    50          
    50          
    50          
    50          
345 0         0 warn "empty tags";
346             #skip empty tags;
347             } elsif ($info->{nowiki}) {
348             # my $text = _close_to($parser, $tagname);
349 0         0 $parser->end_nowiki();
350             } elsif ($info->{block}) {
351 0         0 $parser->close_block();
352             } elsif ($info->{phrase}) {
353 0         0 my $text = _close_to($parser, $tagname);
354 0         0 $parser->append_text($text);
355             } elsif ($info->{special}) {
356 1         3 $parser->close_block();
357 1         4 my $text = _close_to($parser, $tagname);
358 1         3 $parser->add_block($text);
359             } else {
360 0         0 die "helpme $tagname";
361             }
362             } else {
363 1 50       11 if ($info->{empty}) {
    50          
    50          
    50          
    50          
364 0         0 $parser->append_text("<$tagname/>");
365             } elsif ($info->{nowiki}) {
366 0         0 $parser->start_nowiki();
367             # push @$tagstack, $tagname;
368             } elsif (my $blockname = $info->{block}) {
369 0         0 $parser->close_block( new_state => $blockname );
370              
371             # $parser->{state} = $blockname;
372 0 0       0 unless ($info->{notag}) {
373 0         0 $parser->append_text("<$tagname>");
374             }
375 0         0 push @$tagstack, $tagname;
376             } elsif ($info->{phrase}) {
377 0         0 push(@$tagstack, $tagname);
378 0         0 my $text = "<$tagname>";
379 0         0 $parser->append_text($text);
380             } elsif ($info->{special}) {
381 1         12 $parser->close_block();
382 1         2 push(@$tagstack, $tagname);
383 1         3 my $text = "<$tagname>";
384 1         3 $parser->add_block($text);
385             } else {
386 0         0 die "helpme $tagname";
387 0         0 push @$tagstack, $tagname;
388             }
389             }
390             } else {
391 0         0 $parser->append_text($parser, encode($orig));
392             }
393              
394 2         6 return;
395             }
396              
397             sub _html_comment
398 0     0   0 {
399             # warn "_html_comment: " . join(' ', @_);
400             }
401              
402             sub _html_text
403             {
404 13     13   25 my ($parser, $dtext, $skipped_text, $is_cdata) = @_;
405 13         14 my @tagstack = @{$parser->{tag_stack}};
  13         29  
406 13         14 my ($newtext, $newstate);
407              
408 13 100       48 if (my ($leading) = ($dtext =~ /^(\n+)/m)) {
409 11         18 my $x = length($leading);
410 11         20 $parser->end_line($x);
411 11         21 $dtext = substr($dtext, $x);
412             }
413              
414 13 50 33     32 if ($is_cdata && $parser->can_cdata) {
415 0         0 $newtext = $dtext;
416             } else {
417 13         22 $newtext = encode($dtext);
418             }
419              
420 13         28 $parser->append_text($newtext);
421              
422             # warn "Got skipped_text: `$skipped_text'\n[$dtext]\n" if $skipped_text;
423             }
424              
425             {
426             package XHTML::MediaWiki::Parser;
427              
428 2     2   31 use base 'HTML::Parser';
  2         3  
  2         358  
429              
430 2     2   12 use Params::Validate qw (validate);
  2         3  
  2         7900  
431              
432             sub can_cdata
433             {
434 0     0   0 my $self = shift;
435 0 0       0 if (my $current = $self->check_current_block) {
436 0         0 return $self->{tags}{$current->{type}}{can_cdata};
437             }
438 0         0 return 0;
439             }
440              
441             sub end_line
442             {
443 11     11   12 my $self = shift;
444              
445 11         19 my $block = $self->get_last_line_block;
446              
447 11         23 $block->set_end_line(@_);;
448             }
449              
450             sub state
451             {
452 13     13   14 my $self = shift;
453              
454 13         22 my $block = $self->check_current_block;
455 13 100       29 return "none" unless $block;
456 10         26 return $block->get_state;
457             }
458              
459             sub in_state
460             {
461 13     13   13 my $self = shift;
462 13         14 my $state = shift;
463 13 50       27 die if @_;
464 13         21 my $cstate = $self->state;
465              
466 13 50       79 $cstate && $cstate eq $state;
467             }
468              
469             sub in_paragraph
470             {
471 0     0   0 my $self = shift;
472 0         0 my $ret = 0;
473 0 0       0 if (my $block = $self->check_current_block) {
474 0         0 $ret = $block->is_paragraph;
475             }
476 0         0 return $ret;
477             }
478              
479             sub in_prewiki
480             {
481 11     11   12 my $self = shift;
482 11         11 my $ret = 0;
483 11 100       20 if (my $block = $self->check_current_block) {
484 8         14 $ret = $block->is_prewiki;
485             }
486 11         30 return $ret;
487             }
488              
489             sub noformat
490             {
491 11     11   12 my $self = shift;
492              
493 11 50       20 $self->in_state('pre') or $self->in_nowiki();
494             }
495              
496             sub add_block
497             {
498 2     2   2 my $self = shift;
499              
500 2 50       6 if ($self->{current_block}) {
501 0         0 push(@{$self->{blocks}},
  0         0  
502             $self->{current_block}
503             );
504 0         0 die "This should have been handled by close_block";
505             }
506 2         6 my $block = $self->{current_block} = XHTML::MediaWiki::Parser::Block->new(type => 'special');
507 2         9 $block->append_text(join('', @_));
508 2         3 push(@{$self->{blocks}},
  2         5  
509             $self->{current_block}
510             );
511 2         7 $self->{current_block} = undef;
512             }
513              
514             sub close_block
515             {
516 3     3   5 my $self = shift;
517 3         64 my %p = validate(@_, {
518             new_state => {
519             optional => 1,
520             },
521             indent => {
522             optional => 1,
523             },
524             auto_merge => {
525             optional => 1,
526             },
527             });
528              
529 3         15 my $tagstack = $self->{tag_stack};
530 3         4 my $find = undef;
531 3         4 my $text = '';
532 3 50       6 if (!@$tagstack) {
533             # nothing to close;
534             } else {
535 3         5 for my $tagname (@$tagstack) {
536 4         8 my $info = $self->{tags}{$tagname};
537 4 100       10 if ($info->{block}) {
538 3         7 $find = $tagname;
539             }
540             }
541             }
542 3 50       8 if ($find) {
543 3         7 $text = $self->close_to($find);
544 3 50       13 if ($text) {
545 0         0 $self->append_text($text);
546             }
547             }
548              
549 3 50       7 if (my $current = $self->{current_block}) {
    0          
550 3 50 33     9 if ($p{auto_merge} && $p{new_state} eq $self->{current_block}->block_type) {
551 0 0       0 push(@{$current->{lines}}, $current->{line}) if ($current->{line});
  0         0  
552 0         0 $current->{line} = undef;
553             } else {
554 3         4 push(@{$self->{blocks}},
  3         7  
555             $self->{current_block}
556             );
557 3         6 $self->{current_block} = undef;
558 3 50       8 if (my $state = $p{new_state}) {
559 0 0 0     0 if ($state eq 'ordered' || $state eq 'unordered') {
560 0 0       0 die "Need indent" unless exists $p{indent};
561 0         0 $self->{indent} = $p{indent};
562             }
563 0         0 $self->{current_block} = XHTML::MediaWiki::Parser::Block->new(
564             type => $state,
565             level => $p{indent},
566             );
567             }
568             }
569             } elsif (my $state = $p{new_state}) {
570 0         0 $self->{current_block} = XHTML::MediaWiki::Parser::Block->new(
571             type => $state,
572             level => $p{indent},
573             );
574             }
575              
576 3         7 return $self;
577             }
578              
579             sub close_to
580             {
581 3     3   5 my $parser = shift;
582 3         4 my $tag = shift;
583 3         4 my $tagstack = $parser->{tag_stack};
584 3         4 my $text = '';
585              
586 3 50       6 if (!@$tagstack) {
587 0 0       0 $text .= "" if $DEBUG;
588             # ignore extra closing tags
589             } else {
590 3         7 while (my $toptag = pop @$tagstack) {
591 3 50       10 if (! $parser->{tags}{$toptag}{notag}) {
592 0         0 $text .= "";
593             }
594 3 50       8 last if $tag eq $toptag;
595             }
596             }
597 3         7 return $text;
598             }
599              
600             sub start_nowiki
601             {
602 0     0   0 my $self = shift;
603 0         0 my $block = $self->get_current_block;
604              
605 0         0 $block->set_nowiki;
606             }
607              
608             sub end_nowiki
609             {
610 0     0   0 my $self = shift;
611 0         0 my $block = $self->get_current_block;
612              
613 0         0 $block->set_wiki;
614             }
615              
616             sub in_nowiki
617             {
618 13     13   14 my $self = shift;
619              
620 13 100       21 if (my $block = $self->check_current_block) {
621 10         25 return $block->in_nowiki;
622             } else {
623 3         11 return 0;
624             }
625             }
626              
627             sub check_current_block
628             {
629 37     37   37 my $self = shift;
630              
631 37         78 $self->{current_block};
632             }
633              
634             sub get_current_block
635             {
636 24     24   25 my $self = shift;
637              
638 24 100       55 if (!$self->{current_block}) {
639 4         6 my $tagstack = $self->{tag_stack};
640 4   50     18 my $new_state = $self->{state} || 'paragraph';
641 4         6 delete $self->{state};
642 4 50       10 croak() if $new_state eq 'unordered';
643 4         14 $self->{current_block} = XHTML::MediaWiki::Parser::Block->new(type => $new_state);
644 4         5 push @{$self->{tag_stack}}, 'paragraph';
  4         10  
645             }
646 24         41 return $self->{current_block};
647             }
648              
649             sub get_last_line_block
650             {
651 11     11   12 my $self = shift;
652 11         14 my $block = $self->get_current_block;
653              
654 11 50       23 if (! defined $block) {
655 0         0 $block = $self->{blocks}[-1];
656             }
657 11         14 return $block;
658             }
659              
660             sub append_text
661             {
662 13     13   14 my $self = shift;
663 13         14 my $text = shift;
664              
665 13         22 my $block = $self->get_current_block;
666              
667 13         26 $block->append_text($text);
668             }
669              
670             sub get_blocks
671             {
672 1     1   2 my $self = shift;
673 1         1 my @blocks;
674              
675 1         2 for my $block (@{$self->{blocks}}) {
  1         3  
676 5 50       12 next unless $block;
677 5 50 66     16 if ($block->{type} eq 'paragraph' && 0 == @{$block->{lines}} && !$block->{line}) {
  3   66     19  
678 0         0 warn "fix";
679 0         0 next;
680             }
681 5         11 push @blocks, $block;
682             }
683 1         4 @blocks;
684             }
685              
686             sub eof
687             {
688 1     1   2 my $self = shift;
689 1         3 $self->close_block();
690 1         2 for my $tag (@{$self->{tag_stack}}) {
  1         3  
691 0         0 $self->append_text("\n");
692             }
693 1         15 $self->SUPER::eof(@_);
694             }
695             }
696              
697             sub _find_blocks_in_html
698             {
699 1     1   2 my $self = shift;
700 1   50     5 my $text = shift || "";
701 1 50       4 die if @_;
702              
703 1         22 my $parser = XHTML::MediaWiki::Parser->new
704             (start_h => [\&_html_tag, 'self, "S", tagname, text, attr'],
705             end_h => [\&_html_tag, 'self, "E", tagname, text'],
706             comment_h => [\&_html_comment, 'self, text'],
707             text_h => [\&_html_text, 'self, dtext, skipped_text, is_cdata'],
708             marked_sections => 1,
709             boolean_attribute_value => '__TEXT_MEDIAWIKIFORMAT_BOOL__',
710             );
711 1         174 $parser->{opts} = {},
712             $parser->{tags} = {
713             b => { phrase => 1 },
714             big => { phrase => 1 },
715             blockquote => { phrase => 1 },
716             br => { empty => 1 },
717             caption => {},
718             center => {},
719             cite => {},
720             code => { phrase => 1 },
721             dd => {},
722             div => {
723             special => 1,
724             },
725             dl => {},
726             dt => {},
727             em => {},
728             font => {},
729              
730             h1 => { block => 'header' },
731             h2 => { block => 'header' },
732             h3 => { block => 'header' },
733             h4 => { block => 'header' },
734             h5 => { block => 'header' },
735             h6 => { block => 'header' },
736              
737             hr => { empty => 1 },
738             i => { },
739             li => { },
740             nowiki => {
741             nowiki => 1,
742             notag => 1,
743             },
744             ol => { },
745             p => { block => 'p' },
746             paragraph => {
747             block => 'paragraph',
748             notag => 1
749             },
750             pre => {
751             block => 'pre',
752             # nowiki => 1,
753             },
754             rb => {},
755             rp => {},
756             rt => {},
757             ruby => {
758             block => 'ruby',
759             can_cdata => 1,
760             },
761             s => {},
762             samp => {},
763             small => {},
764             strike => {},
765             strong => {},
766             sub => {},
767             sup => {},
768             table => {},
769             td => {},
770             th => {},
771             tr => {},
772             tt => {},
773             u => {},
774             ul => {},
775             var => {},
776             };
777 1         5 $parser->{tag_stack} = [];
778 1         3 $parser->{blocks} = [];
779 1         2 $parser->{current_block} = undef;
780              
781 1         16 my @lines = split(/\r?\n/, $text);
782              
783 1         3 for my $line (@lines) {
784 11         12 my $close = 0;
785 11 50       27 die if chomp $line;
786 11 50       22 if ($parser->noformat) {
787             # we are in nowiki or pre block
788             } else {
789 11 0 33     22 if ($parser->in_prewiki && $line && $line !~ m/^\s+/) {
      33        
790 0         0 $parser->close_block();
791             }
792 11 50       91 if ($line =~ qr/^(={1,6})\s*(.+?)\s*\1$/) {
    50          
    50          
    50          
    50          
793 0         0 my $x = length $1;
794 0         0 $line = sprintf("%s\n", $x, $2, $x);
795 0         0 $parser->{last} = 'header';
796             } elsif ($line =~ /^$/) {
797 0 0       0 if ($parser->check_current_block) {
798 0 0       0 if ($parser->in_paragraph) {
    0          
799 0         0 $parser->close_block();
800             } elsif ($parser->in_prewiki) {
801 0         0 $parser->close_block();
802             } else {
803             }
804             } else {
805 0 0 0     0 unless ({header => 1, prewiki => 1}->{$parser->{last} || ''}) {
806 0         0 $line = "
";
807             }
808             }
809             } elsif ($line =~ m/^\s(\s*.*)$/) {
810 0         0 $line = $1;
811 0         0 $parser->close_block( new_state => 'prewiki', auto_merge => 1 );
812              
813 0         0 $parser->{last} = 'prewiki';
814             } elsif ($line =~ m/^(#+)\s*(.*)\s*$/) {
815 0         0 my $x = length $1;
816 0         0 $parser->close_block( new_state => 'ordered', indent => $x );
817 0         0 $close = 1;
818 0         0 $line = $2;
819 0         0 $parser->{last} = 'nested';
820             } elsif ($line =~ m/^(\*+)\s*(.*)\s*$/) {
821 0         0 my $x = length $1;
822 0         0 $parser->close_block( new_state => 'unordered', indent => $x );
823 0         0 $close = 1;
824 0         0 $line = $2;
825 0         0 $parser->{last} = 'nested';
826             } else {
827             }
828             }
829 11 50       34 next unless $line;
830 11         51 $parser->parse($line);
831 11         46 $parser->parse("\n");
832              
833 11         14 $parser->{empty_lines} = 0;
834              
835 11 50       26 $parser->close_block() if $close;
836             }
837 1         4 $parser->eof();
838 1         1 my @blocks;
839              
840 1         5 for my $block ($parser->get_blocks) {
841 5 50       11 next unless defined $block;
842 5         12 my $type = $block->block_type;
843 5         13 my $class = $self->get_block($type);
844              
845 5         13 my $new_block =
846             $class->new (
847             type => $type,
848             $block->args,
849             formater => $self,
850             );
851 5         15 push @blocks, $new_block;
852             }
853              
854 1         39 return @blocks;
855             }
856              
857             sub _find_blocks
858             {
859 1     1   1 my $self = shift;
860 1         2 my $text = shift;
861              
862 1         2 my @blocks;
863              
864 1         4 @blocks = $self->_find_blocks_in_html($text);
865              
866 1         4 return @blocks;
867             }
868              
869             sub _nest_blocks
870             {
871 1     1   2 my $self = shift;
872 1         2 my @blocks = @_;
873 1 50       4 return unless @blocks;
874              
875 1         3 my @processed = shift @blocks;
876 1         2 for my $block (@blocks)
877             {
878 4         29 my @x = $processed[-1]->nest( $block );
879 4         10 push @processed, @x;
880             }
881              
882 1         5 return @processed;
883             }
884              
885             sub _process_blocks
886             {
887 1     1   1 my $self = shift;
888 1         3 my @blocks = @_;
889 1         3 my @open;
890 1         2 for my $block (@blocks)
891             {
892 5         13 push @open, $self->_process_block($block);
893             }
894 1         5 return join '', @open ;
895             }
896              
897             sub _process_block
898             {
899 5     5   7 my $self = shift;
900 5         6 my ($block, $tags, $opts) = @_;
901 5         18 my $type = $block->type();
902              
903 5         8 my ($start, $end, $start_line, $end_line, $between);
904 5 50       12 if ($tags->{$type})
905             {
906 0         0 ($start, $end, $start_line, $end_line, $between) = @{$tags->{$type}};
  0         0  
907             }
908             else
909             {
910 5         10 ($start, $end, $start_line, $end_line) = ('', '', '', '');
911             }
912              
913 5         7 my @text = ();
914 5 50       7 for my $line (grep (/^\Q$type\E$/, @{$tags->{unformatted_blocks}})
  5         25  
915             ? $block->text()
916             : $block->formatted_text())
917             {
918 5 50       16 if (blessed $line)
919             {
920 0   0     0 my $prev_end = pop @text || ();
921 0         0 push @text, _process_block ($line, $tags, $opts), $prev_end;
922 0         0 next;
923             }
924              
925 5         6 my @triplets;
926 5 50 50     22 if ((ref ($start_line) || '') eq 'CODE')
927             {
928 0         0 @triplets = $start_line->($line, $block->level(),
929             $block->shift_args(), $tags, $opts);
930             }
931             else
932             {
933 5         11 @triplets = ($start_line, $line, $end_line);
934             }
935 5         16 push @text, @triplets;
936             }
937              
938 5 50       13 pop @text if $between;
939 5         26 return join '', $start, @text, $end;
940             }
941              
942             sub _format
943             {
944 1     1   2 my $self = shift;
945 1         2 my $text = shift;
946              
947 1         5 my @blocks = $self->_find_blocks($text);
948              
949 1         6 @blocks = $self->_nest_blocks(@blocks);
950 1         6 my $ret = $self->_process_blocks(@blocks);
951              
952 1         23 return $ret;
953             }
954              
955             sub _strong
956             {
957 1     1   10 "$_[1]";
958             }
959              
960             =item * emphasized()
961              
962             emphasized controls the output of "" tags.
963              
964             =cut
965              
966             sub emphasized
967             {
968 1     1 1 5 "$_[1]";
969             }
970              
971             =item * link()
972              
973             The link method is often overridden to modify the display and
974             operation of links.
975              
976             link takes 3 arguments the Link, any extra_text, and the type of the link;
977              
978             The type is true for footnotes.
979              
980             =cut
981              
982             sub link
983             {
984 3     3 1 6 my $self = shift;
985 3   50     17 my $link = shift || '';
986 3   50     45 my $extra = shift || '';
987 3         7 my $type = shift;
988 3         5 my $text = $link;
989 3 100       10 if ($type) {
990 1         3 $text = ++$self->{footnote};
991             } else {
992 2         11 $link = $self->{link_path} . $link;
993             }
994 3         22 qq|$text$extra|;
995             }
996              
997             =item * find_links()
998              
999             The C method is also often overridden in order to change the way
1000             links are detected.
1001              
1002             =cut
1003              
1004             sub find_links
1005             {
1006 7     7 1 12 my $self = shift;
1007 7         21 my $text = shift;
1008              
1009 7 50       39 return '' unless defined $text;
1010              
1011 7         26 $text =~ s/\[\[([^\]]*)\]\]([A-Za-z0-9]*)/$self->link($1, $2, 0)/ge;
  2         8  
1012 7         16 $text =~ s/\[([a-zA-Z]+:[^\]]*)\]/$self->link($1, '', 1)/ge;
  1         5  
1013              
1014 7         19 return $text;
1015             }
1016              
1017             =item * template_text()
1018              
1019             Override this method to control the text that is generated for an unknown template ({{word}}).
1020              
1021             =cut
1022              
1023             sub template_text
1024             {
1025 5     5 1 6 my $self = shift;
1026 5         11 my $text = shift;
1027 5 50       16 die if @_;
1028 5         26 'No template for: ' . $text . '';
1029             }
1030              
1031             =item * format_line()
1032              
1033             Override this method to extend or modify line level parsing.
1034              
1035             =cut
1036              
1037             sub format_line
1038             {
1039 7     7 1 1247 my $self = shift;
1040 7         13 my $text = shift;
1041              
1042 7 50       46 return '' unless defined $text;
1043              
1044 7         36 my $strong_tag = qr/'''(.+?)'''/;
1045 7         20 my $emphasized_tag = qr/''(.+?)''/;
1046              
1047 7         35 $text =~ s!$strong_tag!$self->_strong($1)!eg;
  1         5  
1048 7         23 $text =~ s!$emphasized_tag!$self->emphasized($1)!eg;
  1         6  
1049              
1050 7         21 $text = $self->find_links($text);
1051              
1052 7         24 my $template_tag = qr/{{\s*([a-zA-Z0-9][a-z0-9|]*)\s*}}/;
1053 7         35 $text =~ s!$template_tag!$self->template_text($1)!eg;
  5         12  
1054            
1055 7         40 return $text;
1056             }
1057              
1058             # BLOCK code is below here and needs to be moved.
1059              
1060             {
1061             package XHTML::MediaWiki::Block::Start;
1062              
1063 2     2   18 use base "XHTML::MediaWiki::Block";
  2         4  
  2         1474  
1064             sub formatted_text
1065             {
1066 0     0   0 "\n";
1067             }
1068             }
1069             {
1070             package XHTML::MediaWiki::Block::Header;
1071              
1072 2     2   12 use base "XHTML::MediaWiki::Block";
  2         4  
  2         22490  
1073              
1074             sub formatted_text
1075             {
1076 0     0   0 my $self = shift;
1077 0         0 my $formatter = $self->formatter;
1078 0         0 my $text = $self->SUPER::formatted_text();
1079              
1080 0         0 my $newtext = $text;
1081 0         0 $newtext =~ s/<[^>]+>//g;
1082 0         0 $newtext =~ s/\s/_/g;
1083 0         0 qq|| . $text;
1084             }
1085             }
1086              
1087             {
1088             package XHTML::MediaWiki::Block::Special;
1089 2     2   22 use base "XHTML::MediaWiki::Block";
  2         3  
  2         1555  
1090              
1091             sub formatted_text
1092             {
1093 2     2   3 my $self = shift;
1094 2         16 my $formatter = $self->formatter;
1095 2         4 my $ret_text = '';
1096 2         3 for my $line (@{$self->{lines}}) {
  2         6  
1097 2 50       6 die("internal error") unless $line;
1098              
1099 2         3 my $text .= $line->{text};
1100 2 50       5 if ($line->{state} eq 'nowiki') {
1101 0         0 $ret_text .= $text;
1102             } else {
1103 2         6 $ret_text .= $formatter->format_line($text);
1104             }
1105             }
1106 2         6 $ret_text;
1107             }
1108             }
1109             {
1110             package XHTML::MediaWiki::Block::P;
1111 2     2   12 use base "XHTML::MediaWiki::Block";
  2         5  
  2         1332  
1112              
1113             sub formatted_text
1114             {
1115 0     0   0 my $self = shift;
1116 0         0 $self->SUPER::formatted_text(@_) . "\n";
1117             }
1118             }
1119             {
1120             package XHTML::MediaWiki::Block::Paragraph;
1121 2     2   14 use base "XHTML::MediaWiki::Block";
  2         4  
  2         886  
1122              
1123 2     2   13 use Carp qw(croak);
  2         4  
  2         312  
1124              
1125             sub formatted_text
1126             {
1127 3     3   6 my $self = shift;
1128 3         4 my $formater = $self->{formater};
1129 3         4 my $ret_text = '';
1130              
1131 3         4 for my $line (@{$self->{lines}}) {
  3         6  
1132 2     2   2732 use Data::Dumper;
  2         20009  
  2         528  
1133 3 50       14 warn Dumper $self unless $line;
1134 3 50       7 die("internal error") unless $line;
1135              
1136 3         7 my $text .= $line->{text};
1137 3 50       10 if ($line->{state} eq 'nowiki') {
1138 0         0 $ret_text .= $text;
1139             } else {
1140 3         8 $ret_text .= $formater->format_line($text);
1141             }
1142             }
1143 3 50       12 if ($ret_text =~ m/^\s*$/) {
1144             # return "\n";
1145             } else {
1146 3         12 return '

' . $ret_text . "

\n";
1147             }
1148             }
1149             }
1150              
1151             {
1152             package XHTML::MediaWiki::Block::Nested;
1153 2     2   19 use base "XHTML::MediaWiki::Block";
  2         3  
  2         2592  
1154              
1155             sub new
1156             {
1157 0     0   0 my $class = shift;
1158 0         0 my $self = $class->SUPER::new(@_);
1159              
1160 0 0       0 die caller unless $self->{level};
1161 0         0 return $self;
1162             }
1163              
1164             sub formatted_text
1165             {
1166 0     0   0 my $self = shift;
1167              
1168 0         0 my $formatter = $self->formatter;
1169 0         0 my $text = $self->SUPER::formatted_text(@_);
1170              
1171 0         0 my $indent = $self->{level};
1172 0         0 my $ret = $self->start_block;
1173              
1174 0         0 $ret .= '
  • ' . $text;
  • 1175 0 0       0 if ($self->{block}) {
    1176 0         0 $ret .= $self->{block}->formatted_text();
    1177             }
    1178 0         0 $ret .= "\n";
    1179              
    1180 0         0 for my $x (@{$self->{added}}) {
      0         0  
    1181 0         0 $ret .= '
  • ' . $x->SUPER::formatted_text();
  • 1182 0 0       0 if ($x->{block}) {
    1183 0         0 $ret .= $x->{block}->formatted_text();
    1184             }
    1185 0         0 $ret .= "";
    1186 0         0 $ret .= "\n";
    1187             }
    1188 0         0 $ret .= $self->end_block;
    1189              
    1190 0         0 return $ret;
    1191             }
    1192              
    1193             sub level
    1194             {
    1195 0     0   0 my $self = shift;
    1196              
    1197 0         0 return $self->{level};
    1198             }
    1199              
    1200             sub cmp
    1201             {
    1202 0     0   0 my $self = shift;
    1203 0         0 my $cmp_block = shift;
    1204 0         0 my $ret = 0;
    1205              
    1206 0 0 0     0 if (ref($self) eq ref($cmp_block) && $self->level == $cmp_block->level) {
    1207 0         0 $ret = 1;
    1208             }
    1209 0         0 return $ret;
    1210             }
    1211              
    1212             sub nests
    1213             {
    1214 0     0   0 1;
    1215             }
    1216              
    1217             sub nest_block
    1218             {
    1219 0     0   0 my $self = shift;
    1220 0   0     0 my $current = $self->{added}->[-1] || $self;
    1221 0         0 for my $block (@_) {
    1222 0         0 my $index = $block->level - $self->level;
    1223 0 0       0 die 'internal error' if $index <= 0;
    1224 0 0       0 if ($index == 1) {
    1225 0 0       0 if (my $x = $current->{block}) {
    1226 0         0 $x->nest($block);
    1227             } else {
    1228 0         0 $current->{block} = $block;
    1229             }
    1230             } else {
    1231 0   0     0 $current->{block} ||= ref($block)->new(
    1232             formater => $current->{formater},
    1233             type => $current->type,
    1234             level => $current->level + 1,
    1235             );
    1236 0         0 $current->{block}->nest($block);
    1237             }
    1238             }
    1239             }
    1240             }
    1241              
    1242             {
    1243             package XHTML::MediaWiki::Block::Ordered;
    1244 2     2   30 use base "XHTML::MediaWiki::Block::Nested";
      2         29  
      2         1356  
    1245 0     0   0 sub start_block { "
      \n" }
    1246 0     0   0 sub end_block { "\n" }
    1247             }
    1248             {
    1249             package XHTML::MediaWiki::Block::Unordered;
    1250 2     2   14 use base "XHTML::MediaWiki::Block::Nested";
      2         85  
      2         1437  
    1251 0     0   0 sub start_block { "
      \n" }
    1252 0     0   0 sub end_block { "\n" }
    1253             }
    1254             {
    1255             package XHTML::MediaWiki::Block::Pre;
    1256 2     2   17 use base "XHTML::MediaWiki::Block";
      2         6  
      2         1399  
    1257              
    1258             sub formatted_text {
    1259 0     0   0 my $self = shift;
    1260 0         0 my $text = $self->unformatted_text;
    1261              
    1262 0         0 return $text;
    1263             }
    1264             }
    1265             {
    1266             package XHTML::MediaWiki::Block::Prewiki;
    1267 2     2   12 use base "XHTML::MediaWiki::Block";
      2         2  
      2         1346  
    1268              
    1269             sub formatted_text
    1270             {
    1271 0     0   0 my $self = shift;
    1272 0         0 my $text = $self->SUPER::formatted_text(@_);
    1273 0         0 $text =~ s/^\s*//;
    1274              
    1275 0         0 return "\n" . '
    ' . $text . "\n
    ";
    1276             }
    1277             }
    1278             {
    1279             package XHTML::MediaWiki::Block::Ruby;
    1280 2     2   12 use base "XHTML::MediaWiki::Block";
      2         4  
      2         1268  
    1281              
    1282             sub formatted_text
    1283             {
    1284 0     0   0 my $self = shift;
    1285 0         0 my $text = $self->SUPER::unformatted_text(@_);
    1286              
    1287 0         0 return "Ruby Data";
    1288             }
    1289             }
    1290             {
    1291             package XHTML::MediaWiki::Block;
    1292 2     2   14 use Params::Validate qw (validate ARRAYREF);
      2         2  
      2         2234  
    1293              
    1294             sub new
    1295             {
    1296 5     5   8 my $class = shift;
    1297 5         101 my %p = validate(@_, {
    1298             formater => 1,
    1299             type => 1,
    1300             indent => 0,
    1301             level => 0,
    1302             lines => ARRAYREF,
    1303             args => 0,
    1304             });
    1305              
    1306 5         52 bless { %p }, $class
    1307             }
    1308              
    1309             sub merge_block
    1310             {
    1311 0     0   0 my $self = shift;
    1312              
    1313 0         0 push(@{$self->{added}}, @_);
      0         0  
    1314             }
    1315              
    1316             sub cmp
    1317             {
    1318 4     4   9 0;
    1319             }
    1320              
    1321             sub merge
    1322             {
    1323 4     4   6 my $self = shift;
    1324 4         5 my @ret = @_;
    1325              
    1326 4         20 while (my $block = pop @ret) {
    1327 4 50       14 if ($self->cmp($block)) {
    1328 0         0 $self->merge_block($block);
    1329             } else {
    1330 4         7 push(@ret, $block);
    1331 4         6 last;
    1332             }
    1333             }
    1334              
    1335 4         9 @ret;
    1336             }
    1337              
    1338             sub nests
    1339             {
    1340 4     4   21 return 0;
    1341             }
    1342              
    1343             sub nest
    1344             {
    1345 4     4   6 my $self = shift;
    1346 4         7 my @next_blocks = @_;
    1347              
    1348 4         14 @next_blocks = $self->merge(@next_blocks);
    1349 4         11 while (@next_blocks) {
    1350 4         4 my $next = $next_blocks[0];
    1351 4 50 33     16 if ($self->nests && $next->nests) {
    1352 0         0 $self->nest_block(pop @next_blocks);
    1353             } else {
    1354 4         5 last;
    1355             }
    1356             }
    1357              
    1358 4         10 return @next_blocks;
    1359             }
    1360              
    1361             sub level
    1362             {
    1363 0     0   0 my $x = shift;
    1364 0         0 warn $x;
    1365 0         0 0;
    1366             }
    1367              
    1368             sub type
    1369             {
    1370 5     5   6 my $self = shift;
    1371              
    1372 5         11 $self->{type};
    1373             }
    1374              
    1375             sub formatter
    1376             {
    1377 2     2   3 shift->{formater};
    1378             }
    1379              
    1380             sub unformatted_text {
    1381 0     0     my $self = shift;
    1382 0           my $formater = $self->{formater};
    1383 0           my $text = '';
    1384              
    1385 0           for my $line (@{$self->{lines}}) {
      0            
    1386 0 0         die("internal error") unless $line;
    1387              
    1388 0           $text .= $line->{text};
    1389             }
    1390 0           return $text;
    1391             }
    1392              
    1393             sub formatted_text {
    1394 0     0     my $self = shift;
    1395 0           my $formater = $self->{formater};
    1396 0           my $text = '';
    1397              
    1398 0           for my $line (@{$self->{lines}}) {
      0            
    1399 0 0         die("internal error") unless $line;
    1400              
    1401 0 0         if ($line->{state} eq 'nowiki') {
    1402 0           $text .= $line->{text};
    1403             } else {
    1404 0           $text .= $formater->format_line($line->{text});
    1405             }
    1406             }
    1407 0           return $text;
    1408             }
    1409             }
    1410              
    1411             1;
    1412             __END__