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