Adding information about the media2iki github project.
[ikiwiki] / doc / tips / convert_mediawiki_to_ikiwiki / discussion.mdwn
1 The u32 page is excellent, but I wonder if documenting the procedure here
2 would be worthwhile. Who knows, the remote site might disappear. But also
3 there are some variations on the approach that might be useful:
4
5  * using a python script and the dom library to extract the page names from
6    Special:Allpages (such as
7    <http://www.staff.ncl.ac.uk/jon.dowland/unix/docs/get_pagenames.py>)
8  * Or, querying the mysql back-end to get the names
9  * using WWW::MediaWiki for importing/exporting pages from the wiki, instead
10    of Special::Export
11
12 Also, some detail on converting mediawiki transclusion to ikiwiki inlines...
13
14 -- [[users/Jon]]
15
16 > "Who knows, the remote site might disappear.". Right now, it appears to
17 > have done just that. -- [[users/Jon]]
18
19 I have manage to recover most of the site using the Internet Archive. What
20 I was unable to retrieve I have rewritten. You can find a copy of the code
21 at <http://github.com/mithro/media2iki>
22
23
24 The iki-fast-load ruby script from the u32 page is given below:
25
26         #!/usr/bin/env ruby
27
28         # This script is called on the final sorted, de-spammed revision
29         # XML file.
30         #
31         # It doesn't currently check for no-op revisions...  I believe
32         # that git-fast-load will dutifully load them even though nothing
33         # happened.  I don't care to solve this by adding a file cache
34         # to this script.  You can run iki-diff-next.rb to highlight any
35         # empty revisions that need to be removed.
36         #
37         # This turns each node into an equivalent file.
38         #    It does not convert spaces to underscores in file names.
39         #       This would break wikilinks.
40         #       I suppose you could fix this with mod_speling or mod_rewrite.
41         #
42         # It replaces nodes in the Image: namespace with the files themselves.
43
44
45         require 'rubygems'
46         require 'node-callback'
47         require 'time'
48         require 'ostruct'
49
50
51         # pipe is the stream to receive the git-fast-import commands
52         # putfrom is true if this branch has existing commits on it, false if not.
53         def format_git_commit(pipe, f)
54            # Need to escape backslashes and double-quotes for git?
55            # No, git breaks when I do this. 
56            # For the filename "path with \\", git sez: bad default revision 'HEAD'
57            # filename = '"' + filename.gsub('\\', '\\\\\\\\').gsub('"', '\\"') + '"'
58
59            # In the calls below, length must be the size in bytes!!
60            # TODO: I haven't figured out how this works in the land of UTF8 and Ruby 1.9.
61            pipe.puts "commit #{f.branch}"
62            pipe.puts "committer #{f.username} <#{f.email}> #{f.timestamp.rfc2822}"
63            pipe.puts "data #{f.message.length}\n#{f.message}\n"
64            pipe.puts "from #{f.branch}^0" if f.putfrom
65            pipe.puts "M 644 inline #{f.filename}"
66            pipe.puts "data #{f.content.length}\n#{f.content}\n"
67            pipe.puts
68         end
69
70 > Would be nice to know where you could get "node-callbacks"... this thing is useless without it. --[[users/simonraven]]
71
72
73 Mediawiki.pm - A plugin which supports mediawiki format.
74
75         #!/usr/bin/perl
76         # By Scott Bronson.  Licensed under the GPLv2+ License.
77         # Extends Ikiwiki to be able to handle Mediawiki markup.
78         #
79         # To use the Mediawiki Plugin:
80         # - Install Text::MediawikiFormat
81         # - Turn of prefix_directives in your setup file.
82         #     (TODO: we probably don't need to do this anymore?)
83         #        prefix_directives => 1,
84         # - Add this plugin on Ikiwiki's path (perl -V, look for @INC)
85         #       cp mediawiki.pm something/IkiWiki/Plugin
86         # - And enable it in your setup file
87         #        add_plugins => [qw{mediawiki}],
88         # - Finally, turn off the link plugin in setup (this is important)
89         #        disable_plugins => [qw{link}],
90         # - Rebuild everything (actually, this should be automatic right?)
91         # - Now all files with a .mediawiki extension should be rendered properly.
92         
93         
94         package IkiWiki::Plugin::mediawiki;
95         
96         use warnings;
97         use strict;
98         use IkiWiki 2.00;
99         use URI;
100         
101         
102         # This is a gross hack...  We disable the link plugin so that our
103         # linkify routine is always called.  Then we call the link plugin
104         # directly for all non-mediawiki pages.  Ouch...  Hopefully Ikiwiki
105         # will be updated soon to support multiple link plugins.
106         require IkiWiki::Plugin::link;
107         
108         # Even if T:MwF is not installed, we can still handle all the linking.
109         # The user will just see Mediawiki markup rather than formatted markup.
110         eval q{use Text::MediawikiFormat ()};
111         my $markup_disabled = $@;
112         
113         # Work around a UTF8 bug in Text::MediawikiFormat
114         # http://rt.cpan.org/Public/Bug/Display.html?id=26880
115         unless($markup_disabled) {
116            no strict 'refs';
117            no warnings;
118            *{'Text::MediawikiFormat::uri_escape'} = \&URI::Escape::uri_escape_utf8;
119         }
120         
121         my %metaheaders;    # keeps track of redirects for pagetemplate.
122         my %tags;      # keeps track of tags for pagetemplate.
123         
124         
125         sub import { #{{{
126            hook(type => "checkconfig", id => "mediawiki", call => \&checkconfig);
127            hook(type => "scan", id => "mediawiki", call => \&scan);
128            hook(type => "linkify", id => "mediawiki", call => \&linkify);
129            hook(type => "htmlize", id => "mediawiki", call => \&htmlize);
130            hook(type => "pagetemplate", id => "mediawiki", call => \&pagetemplate);
131         } # }}}
132         
133         
134         sub checkconfig
135         {
136            return IkiWiki::Plugin::link::checkconfig(@_);
137         }
138         
139         
140         my $link_regexp = qr{
141             \[\[(?=[^!])        # beginning of link
142             ([^\n\r\]#|<>]+)      # 1: page to link to
143             (?:
144                 \#              # '#', beginning of anchor
145                 ([^|\]]+)       # 2: anchor text
146             )?                  # optional
147         
148             (?:
149                 \|              # followed by '|'
150                 ([^\]\|]*)      # 3: link text
151             )?                  # optional
152             \]\]                # end of link
153                 ([a-zA-Z]*)   # optional trailing alphas
154         }x;
155         
156         
157         # Convert spaces in the passed-in string into underscores.
158         # If passed in undef, returns undef without throwing errors.
159         sub underscorize
160         {
161            my $var = shift;
162            $var =~ tr{ }{_} if $var;
163            return $var;
164         }
165         
166         
167         # Underscorize, strip leading and trailing space, and scrunch
168         # multiple runs of spaces into one underscore.
169         sub scrunch
170         {
171            my $var = shift;
172            if($var) {
173               $var =~ s/^\s+|\s+$//g;      # strip leading and trailing space
174               $var =~ s/\s+/ /g;      # squash multiple spaces to one
175            }
176            return $var;
177         }
178         
179         
180         # Translates Mediawiki paths into Ikiwiki paths.
181         # It needs to be pretty careful because Mediawiki and Ikiwiki handle
182         # relative vs. absolute exactly opposite from each other.
183         sub translate_path
184         {
185            my $page = shift;
186            my $path = scrunch(shift);
187         
188            # always start from root unless we're doing relative shenanigans.
189            $page = "/" unless $path =~ /^(?:\/|\.\.)/;
190         
191            my @result = ();
192            for(split(/\//, "$page/$path")) {
193               if($_ eq '..') {
194                  pop @result;
195               } else {
196                  push @result, $_ if $_ ne "";
197               }
198            }
199         
200            # temporary hack working around http://ikiwiki.info/bugs/Can__39__t_create_root_page/index.html?updated
201            # put this back the way it was once this bug is fixed upstream.
202            # This is actually a major problem because now Mediawiki pages can't link from /Git/git-svn to /git-svn.  And upstream appears to be uninterested in fixing this bug.  :(
203            # return "/" . join("/", @result);
204            return join("/", @result);
205         }
206         
207         
208         # Figures out the human-readable text for a wikilink
209         sub linktext
210         {
211            my($page, $inlink, $anchor, $title, $trailing) = @_;
212            my $link = translate_path($page,$inlink);
213         
214            # translate_path always produces an absolute link.
215            # get rid of the leading slash before we display this link.
216            $link =~ s#^/##;
217         
218            my $out = "";
219            if($title) {
220                $out = IkiWiki::pagetitle($title);
221            } else {
222               $link = $inlink if $inlink =~ /^\s*\//;
223                $out = $anchor ? "$link#$anchor" : $link;
224               if(defined $title && $title eq "") {
225                  # a bare pipe appeared in the link...
226                  # user wants to strip namespace and trailing parens.
227                  $out =~ s/^[A-Za-z0-9_-]*://;
228                  $out =~ s/\s*\(.*\)\s*$//;
229               }
230               # A trailing slash suppresses the leading slash
231               $out =~ s#^/(.*)/$#$1#;
232            }
233            $out .= $trailing if defined $trailing;
234            return $out;
235         }
236         
237         
238         sub tagpage ($)
239         {
240            my $tag=shift;
241         
242            if (exists $config{tagbase} && defined $config{tagbase}) {
243               $tag=$config{tagbase}."/".$tag;
244            }
245         
246            return $tag;
247         }
248         
249         
250         # Pass a URL and optional text associated with it.  This call turns
251         # it into fully-formatted HTML the same way Mediawiki would.
252         # Counter is used to number untitled links sequentially on the page.
253         # It should be set to 1 when you start parsing a new page.  This call
254         # increments it automatically.
255         sub generate_external_link
256         {
257            my $url = shift;
258            my $text = shift;
259            my $counter = shift;
260         
261            # Mediawiki trims off trailing commas.
262            # And apparently it does entity substitution first.
263            # Since we can't, we'll fake it.
264         
265            # trim any leading and trailing whitespace
266            $url =~ s/^\s+|\s+$//g;
267         
268            # url properly terminates on > but must special-case &gt;
269            my $trailer = "";
270            $url =~ s{(\&(?:gt|lt)\;.*)$}{ $trailer = $1, ''; }eg;
271         
272            # Trim some potential trailing chars, put them outside the link.
273            my $tmptrail = "";
274            $url =~ s{([,)]+)$}{ $tmptrail .= $1, ''; }eg;
275            $trailer = $tmptrail . $trailer;
276         
277            my $title = $url;
278            if(defined $text) {
279               if($text eq "") {
280                  $text = "[$$counter]";
281                  $$counter += 1;
282               }
283               $text =~ s/^\s+|\s+$//g;
284               $text =~ s/^\|//;
285            } else {
286               $text = $url;
287            }
288         
289            return "<a href='$url' title='$title'>$text</a>$trailer";
290         }
291         
292         
293         # Called to handle bookmarks like \[[#heading]] or <span class="createlink"><a href="http://u32.net/cgi-bin/ikiwiki.cgi?page=%20text%20&amp;from=Mediawiki_Plugin%2Fmediawiki&amp;do=create" rel="nofollow">?</a>#a</span>
294         sub generate_fragment_link
295         {
296            my $url = shift;
297            my $text = shift;
298         
299            my $inurl = $url;
300            my $intext = $text;
301            $url = scrunch($url);
302         
303            if(defined($text) && $text ne "") {
304               $text = scrunch($text);
305            } else {
306               $text = $url;
307            }
308         
309            $url = underscorize($url);
310         
311            # For some reason Mediawiki puts blank titles on all its fragment links.
312            # I don't see why we would duplicate that behavior here.
313            return "<a href='$url'>$text</a>";
314         }
315         
316         
317         sub generate_internal_link
318         {
319            my($page, $inlink, $anchor, $title, $trailing, $proc) = @_;
320         
321            # Ikiwiki's link link plugin wrecks this line when displaying on the site.
322            # Until the code highlighter plugin can turn off link finding,
323            # always escape double brackets in double quotes: \[[
324            if($inlink eq '..') {
325               # Mediawiki doesn't touch links like \[[..#hi|ho]].
326               return "\[[" . $inlink . ($anchor?"#$anchor":"") .
327                  ($title?"|$title":"") . "]]" . $trailing;
328            }
329         
330            my($linkpage, $linktext);
331            if($inlink =~ /^ (:?) \s* Category (\s* \: \s*) ([^\]]*) $/x) {
332               # Handle category links
333               my $sep = $2;
334               $inlink = $3;
335               $linkpage = IkiWiki::linkpage(translate_path($page, $inlink));
336               if($1) {
337                  # Produce a link but don't add this page to the given category.
338                  $linkpage = tagpage($linkpage);
339                  $linktext = ($title ? '' : "Category$sep") .
340                     linktext($page, $inlink, $anchor, $title, $trailing);
341                  $tags{$page}{$linkpage} = 1;
342               } else {
343                  # Add this page to the given category but don't produce a link.
344                  $tags{$page}{$linkpage} = 1;
345                  &$proc(tagpage($linkpage), $linktext, $anchor);
346                  return "";
347               }
348            } else {
349               # It's just a regular link
350               $linkpage = IkiWiki::linkpage(translate_path($page, $inlink));
351               $linktext = linktext($page, $inlink, $anchor, $title, $trailing);
352            }
353         
354            return &$proc($linkpage, $linktext, $anchor);
355         }
356         
357         
358         sub check_redirect
359         {
360            my %params=@_;
361         
362            my $page=$params{page};
363            my $destpage=$params{destpage};
364            my $content=$params{content};
365         
366            return "" if $page ne $destpage;
367         
368            if($content !~ /^ \s* \#REDIRECT \s* \[\[ ( [^\]]+ ) \]\]/x) {
369               # this page isn't a redirect, render it normally.
370               return undef;
371            }
372         
373            # The rest of this function is copied from the redir clause
374            # in meta::preprocess and actually handles the redirect.
375         
376            my $value = $1;
377            $value =~ s/^\s+|\s+$//g;
378         
379            my $safe=0;
380            if ($value !~ /^\w+:\/\//) {
381               # it's a local link
382               my ($redir_page, $redir_anchor) = split /\#/, $value;
383         
384               add_depends($page, $redir_page);
385               my $link=bestlink($page, underscorize(translate_path($page,$redir_page)));
386               if (! length $link) {
387                  return "<b>Redirect Error:</b> <nowiki>\[[$redir_page]] not found.</nowiki>";
388               }
389         
390               $value=urlto($link, $page);
391               $value.='#'.$redir_anchor if defined $redir_anchor;
392               $safe=1;
393         
394               # redir cycle detection
395               $pagestate{$page}{mediawiki}{redir}=$link;
396               my $at=$page;
397               my %seen;
398               while (exists $pagestate{$at}{mediawiki}{redir}) {
399                  if ($seen{$at}) {
400                     return "<b>Redirect Error:</b> cycle found on <nowiki>\[[$at]]</nowiki>";
401                  }
402                  $seen{$at}=1;
403                  $at=$pagestate{$at}{mediawiki}{redir};
404               }
405            } else {
406               # it's an external link
407               $value = encode_entities($value);
408            }
409         
410            my $redir="<meta http-equiv=\"refresh\" content=\"0; URL=$value\" />";
411            $redir=scrub($redir) if !$safe;
412            push @{$metaheaders{$page}}, $redir;
413         
414            return "Redirecting to $value ...";
415         }
416         
417         
418         # Feed this routine a string containing <nowiki>...</nowiki> sections,
419         # this routine calls your callback for every section not within nowikis,
420         # collecting its return values and returning the rewritten string.
421         sub skip_nowiki
422         {
423            my $content = shift;
424            my $proc = shift;
425         
426            my $result = "";
427            my $state = 0;
428         
429            for(split(/(<nowiki[^>]*>.*?<\/nowiki\s*>)/s, $content)) {
430               $result .= ($state ? $_ : &$proc($_));
431               $state = !$state;
432            }
433         
434            return $result;
435         }
436         
437         
438         # Converts all links in the page, wiki and otherwise.
439         sub linkify (@)
440         {
441            my %params=@_;
442         
443            my $page=$params{page};
444            my $destpage=$params{destpage};
445            my $content=$params{content};
446         
447            my $file=$pagesources{$page};
448            my $type=pagetype($file);
449            my $counter = 1;
450         
451            if($type ne 'mediawiki') {
452               return IkiWiki::Plugin::link::linkify(@_);
453            }
454         
455            my $redir = check_redirect(%params);
456            return $redir if defined $redir;
457         
458            # this code was copied from MediawikiFormat.pm.
459            # Heavily changed because MF.pm screws up escaping when it does
460            # this awful hack: $uricCheat =~ tr/://d;
461            my $schemas = [qw(http https ftp mailto gopher)];
462            my $re = join "|", map {qr/\Q$_\E/} @$schemas;
463            my $schemes = qr/(?:$re)/;
464            # And this is copied from URI:
465            my $reserved   = q(;/?@&=+$,);   # NOTE: no colon or [] !
466            my $uric       = quotemeta($reserved) . $URI::unreserved . "%#";
467         
468            my $result = skip_nowiki($content, sub {
469               $_ = shift;
470         
471               # Escape any anchors
472               #s/<(a[\s>\/])/&lt;$1/ig;
473               # Disabled because this appears to screw up the aggregate plugin.
474               # I guess we'll rely on Iki to post-sanitize this sort of stuff.
475         
476               # Replace external links, http://blah or [http://blah]
477               s{\b($schemes:[$uric][:$uric]+)|\[($schemes:[$uric][:$uric]+)([^\]]*?)\]}{
478                  generate_external_link($1||$2, $3, \$counter)
479               }eg;
480         
481               # Handle links that only contain fragments.
482               s{ \[\[ \s* (\#[^|\]'"<>&;]+) (?:\| ([^\]'"<>&;]*))? \]\] }{
483                  generate_fragment_link($1, $2)
484               }xeg;
485         
486               # Match all internal links
487               s{$link_regexp}{
488                  generate_internal_link($page, $1, $2, $3, $4, sub {
489                     my($linkpage, $linktext, $anchor) = @_;
490                     return htmllink($page, $destpage, $linkpage,
491                        linktext => $linktext,
492                        anchor => underscorize(scrunch($anchor)));
493                  });
494               }eg;
495            
496               return $_;
497            });
498         
499            return $result;
500         }
501         
502         
503         # Find all WikiLinks in the page.
504         sub scan (@)
505         {
506            my %params = @_;
507            my $page=$params{page};
508            my $content=$params{content};
509         
510            my $file=$pagesources{$page};
511            my $type=pagetype($file);
512         
513            if($type ne 'mediawiki') {
514               return IkiWiki::Plugin::link::scan(@_);
515            }
516         
517            skip_nowiki($content, sub {
518               $_ = shift;
519               while(/$link_regexp/g) {
520                  generate_internal_link($page, $1, '', '', '', sub {
521                     my($linkpage, $linktext, $anchor) = @_;
522                     push @{$links{$page}}, $linkpage;
523                     return undef;
524                  });
525               }
526               return '';
527            });
528         }
529         
530         
531         # Convert the page to HTML.
532         sub htmlize (@)
533         {
534            my %params=@_;
535            my $page = $params{page};
536            my $content = $params{content};
537         
538         
539            return $content if $markup_disabled;
540         
541            # Do a little preprocessing to babysit Text::MediawikiFormat
542            # If a line begins with tabs, T:MwF won't convert it into preformatted blocks.
543            $content =~ s/^\t/    /mg;
544         
545            my $ret = Text::MediawikiFormat::format($content, {
546         
547                allowed_tags    => [#HTML
548                         # MediawikiFormat default
549                         qw(b big blockquote br caption center cite code dd
550                            div dl dt em font h1 h2 h3 h4 h5 h6 hr i li ol p
551                            pre rb rp rt ruby s samp small strike strong sub
552                            sup table td th tr tt u ul var),
553                          # Mediawiki Specific
554                          qw(nowiki),
555                          # Our additions
556                          qw(del ins),   # These should have been added all along.
557                          qw(span),   # Mediawiki allows span but that's rather scary...?
558                          qw(a),      # this is unfortunate; should handle links after rendering the page.
559                        ],
560         
561                allowed_attrs   => [
562                         qw(title align lang dir width height bgcolor),
563                         qw(clear), # BR
564                         qw(noshade), # HR
565                         qw(cite), # BLOCKQUOTE, Q
566                         qw(size face color), # FONT
567                         # For various lists, mostly deprecated but safe
568                         qw(type start value compact),
569                         # Tables
570                         qw(summary width border frame rules cellspacing
571                            cellpadding valign char charoff colgroup col
572                            span abbr axis headers scope rowspan colspan),
573                         qw(id class name style), # For CSS
574                         # Our additions
575                         qw(href),
576                        ],
577         
578               }, {
579               extended => 0,
580               absolute_links => 0,
581               implicit_links => 0
582               });
583         
584            return $ret;
585         }
586         
587         
588         # This is only needed to support the check_redirect call.
589         sub pagetemplate (@)
590         {
591            my %params = @_;
592            my $page = $params{page};
593            my $destpage = $params{destpage};
594            my $template = $params{template};
595         
596            # handle metaheaders for redirects
597            if (exists $metaheaders{$page} && $template->query(name => "meta")) {
598            # avoid duplicate meta lines
599               my %seen;
600               $template->param(meta => join("\n", grep { (! $seen{$_}) && ($seen{$_}=1) } @{$metaheaders{$page}}));
601            }
602         
603            $template->param(tags => [
604               map {
605                  link => htmllink($page, $destpage, tagpage($_), rel => "tag")
606               }, sort keys %{$tags{$page}}
607            ]) if exists $tags{$page} && %{$tags{$page}} && $template->query(name => "tags");
608         
609            # It's an rss/atom template. Add any categories.
610            if ($template->query(name => "categories")) {
611               if (exists $tags{$page} && %{$tags{$page}}) {
612                  $template->param(categories => [map { category => $_ },
613                     sort keys %{$tags{$page}}]);
614               }
615            }
616         }
617         
618         1
619
620 Hello. Got ikiwiki running and I'm planning to convert my personal
621 Mediawiki wiki to ikiwiki so I can take offline copies around. If anyone
622 has an old copy of the instructions, or any advice on where to start I'd be
623 glad to hear it. Otherwise I'm just going to chronicle my journey on the
624 page.--[[users/Chadius]]
625
626 > Today I saw that someone is working to import wikipedia into git.
627 > <http://www.gossamer-threads.com/lists/wiki/foundation/181163>
628 > Since wikipedia uses mediawiki, perhaps his importer will work
629 > on mediawiki in general. It seems to produce output that could be
630 > used by the [[plugins/contrib/mediawiki]] plugin, if the filenames
631 > were fixed to use the right extension.  --[[Joey]]