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