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