Extension:JSPWiki2MediaWiki

The goal
This tool is merely a DRAFT version,a mini utility that can be used to convert JSPWiki pages to MediaWiki format. The basis for this tool is php2mediawiki by Isaac Wilcox, Copyright (C) 2005 Isaac Wilcox. php2mediawiki provided a convenient basis for this converter and the modifications added to it were introduced to support the conversion of the JSPWiki format.

Install

 * copy the source to a file named JSPWiki2MediaWiki.pl.

How to use
This tool is written in Perl and requires you to have it installed on your machine. Perl can be downloaded from here

Once you've installed Perl open a command prompt and run: perl JSP2Mediawiki.pl 

Where the  is a text file containing the jspwiki page

The file where the converted content will be stored will be named.

In order to get the JSPWiki page content, go to the JSPWiki page and click the edit button, copy and paste its content to the jspwiki_file.txt file, and run the conversion utility.

License
GNU General Public License (GPL)

Author: user:Uzi_Cohen

Code
quux".
 * 1)  - Probably a fair few other things.  My PHPWiki really doesn't tax the
 * 2)    markup.
 * 3) On with the show.  Prerequisites:
 * 4)   1. Hopefully this goes without saying, but...you must have a working,
 * 5)      online PHPWiki and a working, online MediaWiki.
 * 6)   2. You must have accounts with the MySQL (or MySQL*s*) that host the PHP
 * 7)      and Media Wikis, and have relevant privileges.
 * 8)   3. You must have *NIX knowledge, and not be a fool.
 * 9)      I assume you're using *NIX, because I know nothing of Perl on Win32.
 * 10)      Patches for Win32 portability issues are of course welcome.
 * 11)   4. You must not expect the script to do /all/ the work for you.
 * 1)   3. You must have *NIX knowledge, and not be a fool.
 * 2)      I assume you're using *NIX, because I know nothing of Perl on Win32.
 * 3)      Patches for Win32 portability issues are of course welcome.
 * 4)   4. You must not expect the script to do /all/ the work for you.
 * 1)   4. You must not expect the script to do /all/ the work for you.
 * 1)   4. You must not expect the script to do /all/ the work for you.

my $is_zaks_own_wiki = 0;
 * 1) Turns on a few extra conversions that probably only Zak's wiki uses.

my $infile_name = ""; if ($#ARGV != 0) { print "usage: please specify a file name to convert\n"; exit; } else { $infile_name = $ARGV[0]; } my $page = get_page($infile_name); print "\n\n\n -> Converting page: $page->{title}\n"; convert_markup($page);

open(NEW_PAGE, ">$infile_name".".after"); print NEW_PAGE "$page->{title}\n"; print NEW_PAGE $page->{content}; print NEW_PAGE "\n\n\n\n";

close(NEW_PAGE); exit(0);

my %deferred_substs;
 * 1) ID => "replacement text" mapping

{ sub get_page { my ($file_name) = @_; my $pgcontent = ""; my $pgtitle  = ""; my $record   = ""; my $page; open (ORIG_PAGE, "<".$file_name) || die "couldn't open input file! ($file_name)"; $pgtitle = ''; #if ( defined($pgtitle = ) ) { while ( defined($record = ) ) { $pgcontent .= $record; }   $page = { title => $pgtitle, content => $pgcontent }; #}
 * 1) $pagehash get_page;
 * 2) Retrieve and return from a file the page's title and latest content in a
 * 3) hash.
 * 1) hash.

close(ORIG_PAGE); return $page; } }

sub convert_markup { my ($page) = @_; my $old_title = $page->{title}; # http://en.wikipedia.org/wiki/Wikipedia:Naming_conventions_(technical_restrictions) # says that # + < > [ ] | { } are forbidden in MediaWiki page titles. Only # pages I have here are variants on "C++", luckily for me, so I special-case # those and allow them. Not so lucky for you...everything else causes script death. if ($page->{title} =~ /\+\+$/o) { $page->{title} =~ s/\+\+/_Plus_Plus/o; } elsif ($page->{title} =~ /\+\+/) { $page->{title} =~ s/\+\+/_Plus_Plus_/o; } elsif ($page->{title} =~ /^\//o          || $page->{title} =~ /[]\[\{\}\<\>\#\+\|]/o) { # Starting with '/' is also unsupported. die("Can't yet handle page titles with funky chars in"); } if ($page->{title} =~ / /o) { # MediaWiki doesn't expect to see embedded spaces in page titles in the # DB; the PHP layer will always turn a request for e.g. "Bumpy Caps" # into "Bumpy_Caps" before asking the DB. So retitle such pages here. $page->{title} =~ s/ /_/go; } if ($page->{title} =~ /^[a-z]/o) { # Pages starting with small letters seem to break without this. # Again I think the PHP looks for IBook, not iBook. $page->{title} =~ s/^([a-z])/uc($1)/eo; }
 * 1) void convert_markup($page);
 * 2) Convert $page (content, and maybe title) from PHPWiki to MediaWiki format as
 * 3) best we can.  Modifies $page in place.
 * 1) best we can.  Modifies $page in place.

# Break the page up into lines. This avoids having to special-case for \n in # the middle of things, and is apparently how PHPWiki looks at things. # Few PHPWiki constructs span >1 line. my @lines = split(/\n/, $page->{content});

# Sort out verbatim sections early so that following substitutions just # see a placeholder and leave it alone. block_cvt_verbatim(\@lines);

foreach my $line (@lines) { $line = cvt_nowiki($line); $line = cvt_fixedwidth1($line); $line = cvt_linebreaks($line); $line = cvt_remove_tilde($line); if ($is_zaks_own_wiki) { $line = cvt_unfolds($line); $line = cvt_backlinks($line); }   # $line = cvt_plugins($line);

# Apply in most specific --> least specific order to avoid applying an   # overly generic conversion prematurely. So, named external links # first, because they're more specific (http always in them). Etc.   #$line = cvt_wikiwords($line); $line = cvt_underline($line); $line = cvt_toc($line); $line = cvt_explicit_external_links1($line); $line = cvt_explicit_external_links2($line); $line = cvt_explicit_internal_links1($line); $line = cvt_explicit_internal_links2($line);

# InterWikiMaps (see note at top of file). Has to precede WikiWords, # otherwise LocalFile:foo and Map:BumpyTarget get ified first. $line = cvt_interwikimaps($line);

# It helps if this precedes cvt_lists, because '-' is a valid bulleted list # item marker. $line = cvt_horizontal_rules($line);

# It helps if cvt_lists precedes bold, because '*' is a bit overloaded. # bold_italic should precede bold $line = cvt_bold_italics($line); $line = cvt_bold($line);
 * 1)    $line = cvt_lists($line);
 * 2)    $line = cvt_strikethrough($line);
 * 3)   $line = cvt_superscript($line);
 * 4)   $line = cvt_subscript($line);

#$line = cvt_italics($line); # same format - leave as is

# Headings must follow fixedwidth, because it produces '=' $line = cvt_headings_jsp($line); }

for (my $i = 0; $i < $#lines; $i++) { foreach my $block_cvt_sub (\( block_cvt_new_style_tables, )) { my ($num_lines_to_remove, @new_content) = $block_cvt_sub->(\@lines, $i); if (defined($num_lines_to_remove)) { splice(@lines, $i, $num_lines_to_remove, @new_content); # Skip inserted lines, and subtract one because the for loop is about # to $i++ again. $i += $#new_content; # Arbitrary decision to only run one matching block converter on any # given section. last; }   }  }

$page->{content} = join("\n", @lines); $page->{content} = apply_deferred_substs($page->{content}); %deferred_substs = ;

if ($is_zaks_own_wiki) { # HACK: sort links to pages called CategoryFoo, and sort any InterWikiMap # "Category:Foo" links...turn them all to and add a : # version so that the text still looks the same as it did. # By this point, Category links might look like any of: # CategoryTools                    => Category:Tools #                    => Category:Tools #  => same again # Category:Tools                       => # Category:WikiWord => # FIXME: use apply_deferred_substs again to avoid all this assertion mess foreach my $pat (qr/\[\[\s*Category([^]:]+)\]\]/,                    qr/\[\[\s*Category:([^]]+)\]\]/,  		   qr/(?:Category:(\w+))/,  		   qr/(?:Category:\[\[(\w+)\]\])/) { while ($page->{content} =~ $pat) { my $id = defer_subst("Category:$1"); $page->{content} =~ s/$pat/&&&&&$id&&&&&/; }   }    $page->{content} = apply_deferred_substs($page->{content});

# Another Zak-ism (might be useful to others too, but I'm sure there's a   # neater way anyway). # Insert a template at content start to let users know that conversion is   # temporary output. Need original title for this. #   # Need to turn spaces into %20 etc, otherwise e.g.: #  "http://foo/wiki/index.php/Old Page With Spaces" # won't link properly in MediaWiki. $old_title = uri_escape($old_title, " "); $page->{content} = "\n". $page->{content}; } }

sub block_cvt_new_style_tables { my ($lines, $i) = @_;

# FIXME We don't recognize tables if the first line is indented, because the # converter breaks on them at the moment. #(see the bottom for rules for recognizing tables) if ($lines->[$i] =~ /^\|{1,2}.*/) { return convert_table($lines, $i); } else { return undef; } }

sub block_cvt_verbatim { my ($lines) = @_;
 * 1) Turn into a MediaWiki indented-by-one section.  This block
 * 2) conversion is a little different to the rest in that it protects the
 * 3) wrapped content from any other conversions, line or block.  Thus, the
 * 4) arguments and return value are not like the other block_cvt_*; it
 * 5) operates on @lines in place, and doesn't return anything.
 * 6) PHPWiki seems to want to start a line, and include trailing
 * 7) content on the same line.  It seems to want on a line on its
 * 8) own.
 * 1) own.

foreach my $i (0 .. $#{$lines}-1) { if ($lines->[$i] !~ /^ (.*)(\s*)$/) { next; }   my $cur_line = $i; my @new_content; # Gather content trailing after the opening tag, if any. if (defined($1)) { push(@new_content, " $1"); }   $cur_line++; # skip opening tag line while (defined($lines->[$cur_line]) && $lines->[$cur_line] !~ m|^ $|) { push(@new_content, " " . $lines->[$cur_line]); $cur_line++; }   # If we found an opening tag but there are no more closing tags, we're done. if (!defined($lines->[$cur_line])) { last; }   my $id = defer_subst(join("\n", @new_content) . "\n"); splice(@$lines, $i, $cur_line - $i + 1, ("&&&&&$id&&&&&")); } }

{ my $next_deferred_subst_id; sub defer_subst { my ($replacement_text) = @_; if (!defined($next_deferred_subst_id)) { $next_deferred_subst_id = 0; } $deferred_substs{$next_deferred_subst_id} = $replacement_text; return $next_deferred_subst_id++; } }
 * 1) $id defer_subst($replacement_text);
 * 2) Add a deferred substitution to the list.  When you want to make sure that a
 * 3) substitution will not be susceptible to further changes, get an ID from this
 * 4) function and insert '&&&&&id&&&&&' instead of the replacement text.  Later,
 * 5) converter will find all '&&&&&id&&&&&'s and replace them with whatever you
 * 6) saved.  Useful to prevent e.g. BumpyCaps inside a URL being marked up.
 * 1) saved.  Useful to prevent e.g. BumpyCaps inside a URL being marked up.

sub apply_deferred_substs { my ($content) = @_;
 * 1) void apply_deferred_substs($content);
 * 2) Apply all deferred substitutions to the given content (in place).
 * 1) Apply all deferred substitutions to the given content (in place).

$content =~ s/&&&&&(\d+)&&&&&/$deferred_substs{$1}/gx; return $content; }


 * 1) Markup conversion
 * 1) Markup conversion

sub cvt_horizontal_rules { my ($line) = @_;
 * 1) Horizontal rules.
 * 2) This just protects HRs from further messing.
 * 3) Regex stolen from Block_hr (possibly changing leading whitespace semantics).
 * 1) Regex stolen from Block_hr (possibly changing leading whitespace semantics).

if ($line =~ /^-{4,}\s*$/) { my $id = defer_subst($line); $line = "&&&&&$id&&&&&"; } return $line; }

sub cvt_headings_jsp { my ($line) = @_;
 * 1) Headings
 * 2)  !!!text => ==text==     (section)
 * 3)  !!text  => ===text===   (subsection)
 * 4)  !text   => ====text==== (subsubsection)
 * 5)  See transform.php:wtm_headings.  Regex stolen from there.

if ($line =~ /^(!{1,3})[^!]/) { my $markup = '=' x (5 - length($1)); $line =~ s/     # remove the !s from start of line ^!{1,3}     # and any leading whitespace on heading \s* # and capture the heading itself (.*)     #Change it with /${markup}$1${markup}/x; } return $line; }

sub cvt_headings_trac { my ($line) = @_; if ($line =~ /^(={1,3})[^=]+\1/) { my $markup = $1."="; $line =~ s/     # remove the =s from start of line ^(={1,3})     # and any leading whitespace on heading #\s* # and capture the heading itself ([^=]+)\1(.*)     #Change it with /${markup}$2${markup}$3/x; } return $line; }
 * 1) Headings
 * 2)  =text= => ==text==     (section)
 * 3)  ==text==  => ===text===   (subsection)
 * 4)  ===text===   => ====text==== (subsubsection)
 * 5)  See transform.php:wtm_headings.  Regex stolen from there.

sub cvt_plugins { my ($line) = @_;
 * 1) Unsupported single-line plugin calls.
 * 2) See transform.php:wtm_plugin.
 * 3) FIXME: handle multi-line calls (see Block_plugin).
 * 4) FIXME: do something with the plugins we can emulate.
 * 1) FIXME: handle multi-line calls (see Block_plugin).
 * 2) FIXME: do something with the plugins we can emulate.

if ($line =~ /^<\?plugin\s.*\?>\s*$/) { # Hmm. For now let's just save this chunk of stuff so it doesn't get # fiddled by other conversions. my $id = defer_subst($line); $line = "&&&&&$id&&&&&"; } return $line; }

sub cvt_backlinks { my ($line) = @_; # Only pay attention if there's a page=Category... if ($line =~ /^<\?plugin\s+BackLinks\s.*(?:page\s*=\s*Category([^\s]+)).*\?>\s*$/) { my $id = defer_subst(""); $line = "&&&&&$id&&&&&"; } return $line; }
 * 1) This one will only work if you've hacked your MediaWiki like Zak did.
 * 2) Contact me for the hack if you're interested.

sub cvt_unfolds { my ($line) = @_; # Only pay attention if there's a section=... if ($line =~ /^<\?plugin\s+UnfoldSubpages\s.*section\s*=\s*"?([^\s]+).*\?>\s*$/) {   my $id = defer_subst("");    $line = "&&&&&$id&&&&&";  }  return $line; }
 * 1) This one will only work if you've hacked your MediaWiki like Zak did.
 * 2) Contact me for the hack if you're interested.

sub cvt_interwikimaps { my ($line) = @_; my $supported_maps = join('|', qw/   Google AnotherMap   /); my $pat = qr/ # starts with    \[\{        \}\]  /x;  my $template_name = ;  my $param_list = ;  while ($line =~ m/$pat/ox) {    # Don't wanna let template invocations get converted any more.    $template_name = $1;    $param_list = $2;    $param_list =~ s/\ /\|/;    my $id = defer_subst("");    $line =~ s/$pat/&&&&&$id&&&&&/;  }  return $line; }
 * 1) InterWikiMaps
 * 2)  i.e. [{Google foo }] -->
 * 3) Very limited support - see notes at top of file.

sub cvt_wikiwords { my ($line) = @_; my $WikiWordRegex = qr/ # must follow a non-alphanumeric char, or be first thing on the line # Also don't match if there's a preceding '~', because PHPWiki # suppresses markup in that case. (?<![~[:alnum:]]) # match at least "FiFi" ((?:upper:lower:+){2,}) # and not followed by an alnum (hmm, think this is dirty...prev bit is   # greedy, so think they meant [0-9A-Z] there) (?!alnum:)/x; while ($line =~ /$WikiWordRegex/ox) { # Don't wanna let WikiWords get converted any more...I don't think...   # FIXME this defer might be unnecessary. my $id = defer_subst("$1"); $line =~ s/$WikiWordRegex/&&&&&$id&&&&&/; } return $line; } sub cvt_explicit_external_links1 { my ($line) = @_; my $pat = qr/ # Starts with a [ \[       # "coolsite" consists of non-link-ending, non-renaming chars # ($1 = coolsite) non greedy ([^]]*?)   # maybe some whitespace \s* # then a | \|   # maybe some whitespace \s* # then an http link (FIXME could be ftp, etc) containing non-link-ending # chars ($2 = http...) ((?:http|ftp|mailto)[^] ]+) # terminated by a ] \]  /x;
 * 1) WikiWords
 * 2) See $WikiNameRegexp in PHPWiki (occurs several times, not sure which if any
 * 3) is "the" regex).  Regex is hacked a /little/ here to document and capture.
 * 1) is "the" regex).  Regex is hacked a /little/ here to document and capture.
 * 1) Explicit named  or  not named external links
 * 2)  [coolsite |http://foo/ ] => coolsite

while ($line =~ /$pat/o) { my $id = 0; if ($2 eq "") { # Explicit anonymous external links #  => http://foo/ # These are modified to make images appear inline ([] in MW doesn't do this), # to make MW render them as the link address (these links appear as "[1]->"     # otherwise) and to keep them safe from further messing by other conversions. # May need to treat images specially instead? #$id = defer_subst("[$1]");

$id = defer_subst("$1"); }   else { $id = defer_subst("[$2 $1]"); }   $line =~ s/$pat/&&&&&$id&&&&&/x; } return $line; }

sub cvt_explicit_external_links2 { my ($line) = @_; my $pat = qr/ # Starts with a [ \[       # maybe some whitespace \s* # then an http link (FIXME could be ftp, etc) containing non-link-ending # chars ($1 = http...) ((?:http|ftp|mailto)[^] ]+) # terminated by a ] \]  /x;
 * 1) Explicit named  or  not named external links
 * 2)   =>

while ($line =~ /$pat/o) { my $id = 0; # Explicit anonymous external links #  => http://foo/ # These are modified to make images appear inline ([] in MW doesn't do this), # to make MW render them as the link address (these links appear as "[1]->"   # otherwise) and to keep them safe from further messing by other conversions. # May need to treat images specially instead? #$id = defer_subst("[$1]");

$id = defer_subst("$1"); $line =~ s/$pat/&&&&&$id&&&&&/x; } return $line; }

sub cvt_nowiki { my ($line) = @_; my $pat = qr/ # Starts with a {{{ {{{     # text that should not be processed by the wiki # ($1 = text) (.*?)   # terminated by a ] }}}  /x;
 * 1) exclude text from wiki
 * 2)   => text

while ($line =~ /$pat/o) { my $id = 0; $id = defer_subst(" $1 "); $line =~ s/$pat/&&&&&$id&&&&&/x; } return $line; }

sub cvt_explicit_internal_links1 { my ($line) = @_; my $pat = qr/ # Starts with a [ \[     # "coolpage" consists of non-link-ending, non-renaming chars # ($1 = coolpage) ([^]|]*)   \|    # maybe some whitespace \s* # then an internal link containing non-link-ending # chars ($2 = internal link) ([^]]*)   # terminated by a ] \]  /x;
 * 1) Explicit named  internal links
 * 2)  [link text | link] =>  link text
 * 3)  [link text |] => link text    <--- underline

while ($line =~ /$pat/o) { my $id = 0; if ($2 eq "") { $id = defer_subst(" $1 "); }   else { my $link = convert_to_camel($2); $id = defer_subst(" $1"); }   $line =~ s/$pat/&&&&&$id&&&&&/x; } return $line; }

sub cvt_explicit_internal_links2 { my ($line) = @_; my $pat = qr/ # Starts with a [ \[     # "coolpage" consists of non-link-ending, non-renaming chars # ($1 = coolpage) ([^]|]*)   # maybe some whitespace \s* # terminated by a ] \]  /x;
 * 1) Explicit not named internal links
 * 2)  [link] => link

while ($line =~ /$pat/o) { my $id = 0; my $link = convert_to_camel($1); $id = defer_subst(" $1"); $line =~ s/$pat/&&&&&$id&&&&&/x; } return $line; }

sub cvt_lists { my ($line) = @_; # Get a cup of tea. my $pat = qr/ # Bullets must be first non-whitespace on line. # Capture the indentation so we can determine level later. ^(\ *)   # About to encounter some list item marker, so start capturing so we can # tell later whether it was  or . (   # Any one of our 5 choices of bulleted list marker.    # Now, the first 4 we'll ignore the other uses of, and always just see as    # bullets.      (1\.)|(a\.)|(i\.)    # But '*' is annoying if you have a standalone line of bold text, which I    # do, so the hairy negative lookahead from Block_list is copied here.    # This basically considers * a bullet unless it looks like these:    #   *foo, bar baz*    #   *foo bar*    #   *foobar*    # cos that's probably meant to be bold.  But consider e.g. these to be    # bullets:    #   *foo bar*baz    #   * foo bar*    #   *foo bar *baz      | \*    # Not followed by:       (?! ### One char of something (i.e. not space) \S ### Zero or more chars that aren't *s [^*]*   ### The last of which must be a something (not space) (?<=\S) ### Then another * \*   ### Followed by either space, or end of the line, i.e. not immediately ### followed by text. (?!\S) # End of "Not followed by" )   # End of choice of bullets (captured).    ) # Possibly some spaces \ *   # And followed by some content. (?=\S) /x;
 * 1) Bulleted lists
 * 2) A little more relaxed than Block_list, just because I hate that regex ;)
 * 1) A little more relaxed than Block_list, just because I hate that regex ;)

if ($line =~ /$pat/o) { # Work out nesting level. Two spaces make a new level. my $nest_level = (length($1) / 2) + 1; # If bullet used was UL, use '*', else use '#'. my $bullet; if ($2 eq '1.') { $bullet = '#'; }    elsif ($2 eq 'a.') { $bullet = '#'; }   elsif ($2 eq 'i.') { $bullet = '#'; }   else { $bullet = '*'; }   my $wm_list_markup = $bullet x $nest_level; my $id = defer_subst("$wm_list_markup"); $line =~ s/$pat/&&&&&$id&&&&&/; } return $line; }

sub cvt_bold_italics { my ($line) = @_; my $pat = qr/ __ (.+?) __ /x;
 * 1) Bold Italics
 * 2)  __fo__ => foo

while ($line =~ /$pat/o) { $line =~ s/$pat/$1/g; } return $line; }

sub cvt_underline { my ($line) = @_; my $pat = qr/ \[ (.+?) \|\] /x;
 * 1) underline.
 * 2)  [foo|] => foo

while ($line =~ /$pat/o) { $line =~ s/$pat/ $1<\/u>/g; } return $line; }

sub cvt_strikethrough { my ($line) = @_; my $pat = qr/ (.+?)  /x;
 * 1) strikethrough.
 * 2)  foo => foo

while ($line =~ /$pat/o) { $line =~ s/$pat/ $1<\/s>/g; } return $line; }

sub cvt_subscript { my ($line) = @_; my $pat = qr/ ,, (.+?) ,, /x;
 * 1) subscript.
 * ,,foo,, => {{sub|foo}}

while ($line =~ /$pat/o) { $line =~ s/$pat/ $1<\/sub>/g; } return $line; }

sub cvt_toc { my ($line) = @_; my $pat = qr/ \[{TableOfContents}\] /x;
 * 1) toc.
 * 2)  [{TableOfContents}]=>

while ($line =~ /$pat/o) { my $id = 0; $id = defer_subst(""); $line =~ s/$pat/&&&&&$id&&&&&/x; } return $line; }

sub cvt_superscript { my ($line) = @_; my $pat = qr/ \^ ([^^]+) \^ /x;
 * 1) superscript.
 * 2)  ^foo^ => {{sup|foo}}

while ($line =~ /$pat/o) { $line =~ s/$pat/ $1<\/sup>/g; } return $line; } sub cvt_bold { my ($line) = @_; my $pat = qr/ __ (.+?) __ /x;
 * 1) Bold
 * 2)  __foo__ => foo

while ($line =~ /$pat/o) { $line =~ s/$pat/$1/g; } return $line; }

sub cvt_italics { my ($line) = @_; my $pat = qr/ _ ([^_]+) _ /x;
 * 1) Italics
 * 2)  _foo_ => foo

while ($line =~ /$pat/o) { $line =~ s/$pat/$1/g; } return $line; }

sub cvt_remove_tilde { my ($line) = @_; my $pat = qr/ \~( \w+ ) /x;
 * 1) Fixed width
 * 2)  !foo  => foo

while ($line =~ /$pat/o) { $line =~ s/$pat/$1/g; } return $line; }

sub cvt_fixedwidth1 { my ($line) = @_; my $pre_str = '{{'; my $post_str = '}}'; my $pat = qr/ ${pre_str}(.+?)${post_str} /x;
 * 1) Fixed width
 * 2)  {{foo}} =>

while ($line =~ /$pat/o) { $line =~ s/$pat/ sub cvt_fixedwidth2 { my ($line) = @_; my $pat = qr/ \`([^`]+)\` /x;

while ($line =~ /$pat/o) { $line =~ s/$pat/