File Coverage

blib/lib/Wiki/Toolkit/Formatter/Mediawiki.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Wiki::Toolkit::Formatter::Mediawiki;
2              
3 1     1   33013 use strict;
  1         3  
  1         71  
4 1     1   6 use warnings;
  1         2  
  1         67  
5              
6             =head1 NAME
7              
8             Wiki::Toolkit::Formatter::Mediawiki - A Mediawiki-style formatter for
9             Wiki::Toolkit.
10              
11             =head1 VERSION
12              
13             Version 0.04
14              
15             =cut
16              
17             our $VERSION = '0.04';
18              
19             =head1 SYNOPSIS
20              
21             This package implements a formatter for the Wiki::Toolkit module which attempts
22             to duplicate the behavior of the Mediawiki application (a set of PHP scripts
23             used by Wikipedia and friends).
24              
25             use Wiki::Toolkit
26             use Wiki::Toolkit::Store::Mediawiki;
27             use Wiki::Toolkit::Formatter::Mediawiki;
28              
29             my $store = Wiki::Toolkit::Store::Mediawiki->new ( ... );
30             # See below for parameter details.
31             my $formatter = Wiki::Toolkit::Formatter::Mediawiki->new (%config,
32             store => $store);
33             my $wiki = Wiki::Toolkit->new (store => $store, formatter => $formatter);
34              
35             =cut
36              
37              
38              
39 1     1   7 use Carp qw(croak);
  1         6  
  1         68  
40 1     1   461 use Text::MediawikiFormat as => 'wikiformat';
  0            
  0            
41             use URI::Escape qw(uri_escape_utf8);
42             use Wiki::Toolkit::Formatter::Mediawiki::Link;
43              
44              
45             =head1 METHODS
46              
47             =head2 new
48              
49             my $store = Wiki::Toolkit::Store::Mediawiki->new ( ... );
50             my $formatter = Wiki::Toolkit::Formatter::Mediawiki->new
51             (allowed_tags => [# HTML
52             qw(b big blockquote br caption center cite code dd
53             div dl dt em font h1 h2 h3 h4 h5 h6 hr i li ol p
54             pre rb rp rt ruby s small strike strong sub sup
55             table td th tr tt u ul var),
56             # MediaWiki Specific
57             qw(nowiki),],
58             allowed_attrs => [qw(title align lang dir width height bgcolor),
59             qw(clear), # BR
60             qw(noshade), # HR
61             qw(cite), # BLOCKQUOTE, Q
62             qw(size face color), # FONT
63             # For various lists, mostly deprecated but
64             # safe
65             qw(type start value compact),
66             # Tables
67             qw(summary width border frame rules
68             cellspacing cellpadding valign char
69             charoff colgroup col span abbr axis
70             headers scope rowspan colspan),
71             qw(id class name style), # For CSS
72             ],
73             node_prefix => '',
74             store => $store);
75              
76             Parameters will default to the values above, with the exception of C,
77             which is a required argument without a default. C B have to
78             be of type C.
79              
80             =cut
81              
82             sub new
83             {
84             my ($class, %args) = @_;
85             croak "`store' is a required argument" unless $args{store};
86              
87             my $self = {};
88             bless $self, $class;
89             $self->_init (%args) or return undef;
90             return $self;
91             }
92              
93             sub _init
94             {
95             my ($self, %args) = @_;
96              
97             # Store the parameters or their defaults.
98             my %defs = (store => undef,
99             node_prefix => '',
100             extended_link_delimiters => qr!\[(?:\[[^][]*\]|[^][]*)\]
101             |\{\{\{[^{}]*\}\}\}
102             |\{\{[^{}]*\}\}
103             |\bRFC\s\d+!x
104             );
105              
106             foreach my $k (keys %defs)
107             {
108             $self->{"_".$k} = exists $args{$k} ? $args{$k} : $defs{$k};
109             }
110              
111             return $self;
112             }
113              
114              
115              
116             =head2 format
117              
118             my $html = $formatter->format ($content);
119              
120             Escapes any tags which weren't specified as allowed on creation, then
121             interpolates any macros, then calls Text::WikiFormat::format (with the
122             specialized Mediawiki config) to translate the raw Wiki
123             language supplied into HTML.
124              
125             =cut
126              
127             our $uric = $URI::uric;
128             our $uricCheat = $uric;
129              
130             # We need to avoid picking up 'HTTP::Request::Common' so we have a
131             # subset of uric without a colon.
132             $uricCheat =~ tr/://d;
133              
134             # Identifying characters often accidentally picked up trailing a URI.
135             our $uriCruft = q/]),.!'";}/;
136              
137              
138              
139             # Return the text of a page, after recursively replacing any variables
140             # and templates in the included page.
141             sub _include
142             {
143             my ($template_name, $opts, $tags) = @_;
144             $tags->{_template_stack} ||= [];
145              
146             return "Template recursion detected: "
147             . join (" -> ", @{$tags->{_template_stack}})
148             . " -> $template_name (repeat)."
149             if grep /^\Q$template_name\E$/, @{$tags->{_template_stack}};
150              
151             #Recurse if no loop detected.
152             push @{$tags->{_template_stack}}, $template_name;
153              
154             my $text = $tags->{_store}->retrieve_node ("Template:$template_name");
155             return "{{$template_name}}" unless defined $text;
156              
157             $text = wikiformat ($text, $tags, $opts);
158             pop @{$tags->{_template_stack}};
159             return $text;
160             }
161              
162              
163              
164             # Turn [[Wiki Link|Title]], [URI Title], scheme:url, or StudlyCaps into links.
165             sub _make_html_link
166             {
167             my ($tag, $opts, $tags) = @_;
168              
169             my ($class, $trailing) = ('', '');
170             my ($href, $title);
171             if ($tag =~ /^\[\[([^|#]*)(?:(#)([^|]*))?(?:(\|)(.*))?\]\]$/)
172             {
173             # Wiki link
174             if ($1)
175             {
176             if ($tags->{_store}
177             && ($href = $tags->{_store}->get_interwiki_url ($1)))
178             {
179             $class = " class='external'";
180             }
181             else
182             {
183             $href = $opts->{prefix} . uri_escape_utf8 $1 if $1;
184             $class = " class='link_wanted'"
185             unless !$tags->{_store}
186             || $tags->{_store}->node_exists ($1);
187             }
188             }
189             $href .= $2 . uri_escape_utf8 $3 if $2;
190              
191             if ($4)
192             {
193             # Title specified explicitly.
194             if (length $5)
195             {
196             $title = $5;
197             }
198             else
199             {
200             # An empty title asks Mediawiki to strip any parens off the end
201             # of the node name.
202             $1 =~ /^([^(]*)(?:\s*\()?/;
203             $title = $1;
204             }
205             }
206             else
207             {
208             # Title defaults to the node name.
209             $title = $1;
210             }
211             }
212             elsif ($tag =~ /^\[(\S*)(?:(\s+)(.*))?\]$/)
213             {
214             # URI
215             $href = $1;
216             if ($2)
217             {
218             $title = $3;
219             }
220             else
221             {
222             $title = ++$opts->{_uri_refs};
223             }
224             $href =~ s/'/%27/g;
225             }
226             elsif ($tag =~ qr/^RFC\s(\d+)$/)
227             {
228             # Could process ISBN too.
229             return $tag
230             unless $tags->{_store}
231             && ($href = $tags->{_store}->get_interwiki_url ("RFC:$1"));
232              
233             $class = " class='external'";
234             $title = $tag;
235             }
236             elsif ($tag =~ /^\{\{(.*)\}\}$/)
237             {
238             # Template
239              
240             # Ignore it if it doesn't exist.
241             return $tag unless $tags->{_store}->node_exists ("Template:$1");
242              
243             return _include $1, $opts, $tags;
244             }
245             else
246             {
247             # Shouldn't be able to get here without either $opts->{absolute_links}
248             # or $opts->{implicit_links};
249             $tags->{_schema_regex}
250             ||= Text::MediawikiFormat::_make_schema_regex @{$tags->{schemas}};
251             my $s = $tags->{_schema_regex};
252              
253             if ($tag =~ /^(?:<)?$s:[$uricCheat][$uric]*(?:>)?$/)
254             {
255             # absolute link
256             $href = $&;
257             $trailing = $& if $href =~ s/[$uriCruft]$//;
258             $href =~ s/^(?:<)?(.+?)(?:>)?$/$1/;
259             $title = $href;
260             }
261             else
262             {
263             # StudlyCaps
264             $href = $opts->{prefix} . uri_escape_utf8 $tag;
265             $class = " class='link_wanted'"
266             unless $tags->{_store} && $tags->{_store}->node_exists ($tag);
267             $title = $tag;
268             }
269             }
270              
271             return "$title$trailing";
272             }
273              
274              
275              
276             sub _magic_words
277             {
278             }
279              
280              
281              
282             sub _format_redirect
283             {
284             my ($self, $target) = @_;
285              
286             # my $href = _make_html_link ("[[$target]]", $tags, $opts);
287             my $href = $target;
288              
289             my $img;
290             # if ($tags->{_redirect_image})
291             # {
292             # $img = "#REDIRECT$img$href"
301             }
302              
303              
304              
305             sub format
306             {
307             my ($self, $raw) = @_;
308              
309             # Special exception handling for redirects - Text::MediawikiFormat doesn't
310             # know how to handle something that may only appear on the first line yet,
311             # though it's going to need to if I intend to handle Mediawiki's full
312             # template syntax.
313             return $self->_format_redirect ($1)
314             if $raw =~ /^#redirect\s+\[\[([^]]+)\]\]/si;
315              
316             # Set up the %tags array.
317             my %tags;
318             $tags{allowed_tags} = $self->{_allowed_tags}
319             if $self->{_allowed_attrs};
320              
321             # These always get set up.
322             $tags{link} = \&_make_html_link;
323             $tags{extended_link_delimiters} = $self->{_extended_link_delimiters};
324             $tags{_store} = $self->{_store};
325              
326             # Set up the %opts array.
327             my %opts;
328             $opts{prefix} = $self->{_node_prefix}
329             if $self->{_node_prefix};
330              
331             return wikiformat ($raw, \%tags, \%opts);
332             }
333              
334              
335             # Turn [[Wiki Link|Title]], [URI Title], scheme:url, into an array of links.
336             sub _stash_html_link
337             {
338             my ($tag, $opts, $tags) = @_;
339             my $type;
340             my $name;
341              
342             if ($tag =~ /^\[\[([^|#]*)(?:(#)([^|]*))?(?:(\|)(.*))?\]\]$/)
343             {
344             # Wiki link
345             if ($1)
346             {
347             unless ($tags->{_store}
348             && ($tags->{_store}->get_interwiki_url ($1)))
349             {
350             $name = $1;
351             $type = 'page';
352             }
353             }
354             }
355             elsif ($tag =~ /^\[(\S*)(?:(\s+)(.*))?\]$/)
356             {
357             # URI
358             $name = $1;
359             $type = 'external';
360             }
361             elsif ($tag =~ qr/^RFC\s(\d+)$/)
362             {
363             # Could process ISBN too.
364             $name = "RFC:$1";
365             $type = 'rfc';
366             }
367             elsif ($tag =~ /^\{\{\{(.*)\}\}\}$/){#no triples please.
368             }
369             elsif ($tag =~ /^\{\{(.*)\}\}$/)
370             {
371             # Template
372             $name = "Template:$1";
373             $type = 'template';
374             }
375             else
376             {
377             # Shouldn't be able to get here without either $opts->{absolute_links}
378             # or $opts->{implicit_links};
379             $tags->{_schema_regex}
380             ||= Text::MediawikiFormat::_make_schema_regex @{$tags->{schemas}};
381             my $s = $tags->{_schema_regex};
382              
383             if ($tag =~ /^(?:<)?$s:[$uricCheat][$uric]*(?:>)?$/)
384             {
385             # absolute link
386             $name = $&;
387             $name =~ s/^(?:<)?(.+?)(?:>)?$/$1/;
388             $type = 'external';
389             }
390             else
391             {
392             # StudlyCaps
393             $name = $tag;
394             $type = 'page';
395             }
396             }
397             my $link = new Wiki::Toolkit::Formatter::Mediawiki::Link $name, $type;
398             push @{$tags->{_found_links}}, $link
399             unless (!$link || grep /^\Q$link\E$/, @{$tags->{_found_links}});
400             }
401              
402              
403             =head2 find_internal_links
404              
405             my @links_to = $formatter->find_internal_links ($content);
406              
407             Returns a list of all nodes that the supplied content links to.
408              
409             =cut
410              
411             sub find_internal_links {
412             my ($self, $raw) = @_;
413             my @found_links = ();
414              
415             # Set up the %tags array.
416             my %tags;
417             $tags{allowed_tags} = $self->{_allowed_tags}
418             if $self->{_allowed_attrs};
419              
420             # These always get set up.
421             $tags{link} = \&_stash_html_link;
422             $tags{extended_link_delimiters} = $self->{_extended_link_delimiters};
423             $tags{_store} = $self->{_store};
424             $tags{_found_links} = \@found_links;
425              
426             # Set up the %opts array.
427             my %opts;
428             $opts{prefix} = $self->{_node_prefix}
429             if $self->{_node_prefix};
430              
431             wikiformat ($raw, \%tags, \%opts);
432              
433             return @found_links;
434             }
435              
436              
437              
438             =head1 SEE ALSO
439              
440             =over 4
441              
442             =item L
443              
444             =item L
445              
446             =item L
447              
448             =item L
449              
450             =back
451              
452             =head1 AUTHOR
453              
454             Derek R. Price, C<< >>
455              
456             =head1 BUGS
457              
458             Please report any bugs or feature requests to
459             C, or through the web interface at
460             L.
461             I will be notified, and then you'll automatically be notified of progress on
462             your bug as I make changes.
463              
464             =head1 SUPPORT
465              
466             You can find documentation for this module with the perldoc command.
467              
468             perldoc Wiki::Toolkit::Formatter::Mediawiki
469              
470             You can also look for information at:
471              
472             =over 4
473              
474             =item * AnnoCPAN: Annotated CPAN documentation
475              
476             L
477              
478             =item * CPAN Ratings
479              
480             L
481              
482             =item * RT: CPAN's request tracker
483              
484             L
485              
486             =item * Search CPAN
487              
488             L
489              
490             =back
491              
492             =head1 ACKNOWLEDGEMENTS
493              
494             My thanks go to Kake Pugh, for providing the well written L and
495             L modules, which got me started on this.
496              
497             =head1 COPYRIGHT & LICENSE
498              
499             Copyright 2006 Derek R. Price, all rights reserved.
500              
501             This program is free software; you can redistribute it and/or modify it
502             under the same terms as Perl itself.
503              
504             =cut
505              
506             1; # End of Wiki::Toolkit::Formatter::Mediawiki