MoinMoin to DokuWiki Сравнение версий

Различия

Здесь показаны различия между двумя версиями данной страницы.

Ссылка на это сравнение

Предыдущая версия справа и слева Предыдущая версия
Следующая версия
Предыдущая версия
wiki:moinmoin2dokuwiki [2009/07/22 02:11]
wiki:moinmoin2dokuwiki [2009/12/25 23:53] (текущий)
Строка 15: Строка 15:
 # ----------------------------------------------------------------------------------------------- # # ----------------------------------------------------------------------------------------------- #
  
-# Hash for smileys replacement ​(actual for ubuntu.com wiki)+# Hash for smileys replacement
 %smileys = ( %smileys = (
  #​moin doku  #​moin doku
Строка 60: Строка 60:
 } }
  
-Link  +Links  
-sub link_replacement ​+sub linkReplacement ​
- die "​Oops!\n" ​if @_ != 5;+ die "​Oops!\n" ​unless ​@_ == 5;
  my ($namespace,​$br,​$target,​$text,​$params) = @_;  my ($namespace,​$br,​$target,​$text,​$params) = @_;
  # Attachments links  # Attachments links
Строка 81: Строка 81:
 } }
  
-Block +Blocks 
-sub block_replacement ​{+sub blockReplacement ​{
  for (my $i = 0; $i < @_ && $_[$i] =~ /^\$*$/; $i++ ) { shift @_ } # delete empty lines  for (my $i = 0; $i < @_ && $_[$i] =~ /^\$*$/; $i++ ) { shift @_ } # delete empty lines
  for (my $i = -1; -$i <= @_ && $_[$i] =~ /^\$*$/; $i-- ) { pop @_ } # delete empty lines  for (my $i = -1; -$i <= @_ && $_[$i] =~ /^\$*$/; $i-- ) { pop @_ } # delete empty lines
Строка 96: Строка 96:
 } }
  
-Table +Tables 
-sub table_replacement ​{+sub tableReplacement ​{
  @table = @_;  @table = @_;
  for (@table) {  for (@table) {
Строка 107: Строка 107:
  "​|| ​ $2  " . '​|'​ x (length($1) - 2)  "​|| ​ $2  " . '​|'​ x (length($1) - 2)
  /ge;  /ge;
-Spanning+Span
  # TODO  # TODO
  # Aligment  # Aligment
Строка 150: Строка 150:
  } elsif ($is_table) {  } elsif ($is_table) {
  $is_table = 0;  $is_table = 0;
- @table = table_replacement(@table);+ @table = tableReplacement(@table);
  print OUTFILE @table;  print OUTFILE @table;
  @table = ();  @table = ();
Строка 164: Строка 164:
  next MOINSCAN unless ($+{text} && $+{text} !~ /^\s+$/);  next MOINSCAN unless ($+{text} && $+{text} !~ /^\s+$/);
  $_ = "​$+{text}\n";​  $_ = "​$+{text}\n";​
- } elsif (/​(?<​data>​.+?​)}{$block_sep_len}\s*(?<​text>​.*)\n/​ && $is_block) {+ } elsif (/​(?<​data>​.+?​)?}{$block_sep_len}\s*(?<​text>​.*)\n/​ && $is_block) {
  $is_block = 0;  $is_block = 0;
  push @block, "​$+{data}\n"​ if $+{data};  push @block, "​$+{data}\n"​ if $+{data};
- @block = block_replacement(@block);+ @block = blockReplacement(@block);
  print OUTFILE @block;  print OUTFILE @block;
  @block = ();  @block = ();
Строка 204: Строка 204:
  (??{ &​inv($+{br}) x 2 }) # closing brackets  (??{ &​inv($+{br}) x 2 }) # closing brackets
  /  /
- &link_replacement($namespace,​$+{br},​$+{target},​$+{text},​$+{params})+ &linkReplacement($namespace,​$+{br},​$+{target},​$+{text},​$+{params})
  /gxe;   /gxe;
  # CamelCase links  # CamelCase links
Строка 228: Строка 228:
  s{~\+(.+?​)\+~}<​$1>​g;​ #​ larger text  s{~\+(.+?​)\+~}<​$1>​g;​ #​ larger text
  # Lists and intends  # Lists and intends
- if (/​^(?<​intend>​\s+)\*\s*(?<​value>​\S.*)\n/​) { # dotted list+ if (/​^(?<​intend>​\s+)\*\s+(?<​value>​\S.*)\n/​) { # dotted list
  if ($is_list) { print OUTFILE "​\n"​ }  if ($is_list) { print OUTFILE "​\n"​ }
  print OUTFILE " "​x(2*length($+{intend})),"​* ";  print OUTFILE " "​x(2*length($+{intend})),"​* ";
Строка 234: Строка 234:
  $intend = length $+{intend};  $intend = length $+{intend};
  $is_list = 1;  $is_list = 1;
- } elsif (/​^(?<​intend>​\s+)[1aAiI]\.(#​\d+)?​\s*(?<​value>​\S.*)\n/​) { # numeric list+ } elsif (/​^(?<​intend>​\s+)[1aAiI]\.(#​\d+)?​\s+(?<​value>​\S.*)\n/​) { # numeric list
  if ($is_list) { print OUTFILE "​\n"​ }  if ($is_list) { print OUTFILE "​\n"​ }
  print OUTFILE " "​x(2*length($+{intend})),"​- ";  print OUTFILE " "​x(2*length($+{intend})),"​- ";
Строка 288: Строка 288:
  }  }
  if ($is_table) {  if ($is_table) {
- @table = table_replacement(@table);+ @table = tableReplacement(@table);
  print OUTFILE @table;  print OUTFILE @table;
  }  }
  if ($is_block) {  if ($is_block) {
- @block = block_replacement(@block);+ @block = blockReplacement(@block);
  print OUTFILE @block;  print OUTFILE @block;
  }  }
Строка 315: Строка 315:
   * http://​moinmo.in/​HelpOnEditing - проверить,​ всё ли учли   * http://​moinmo.in/​HelpOnEditing - проверить,​ всё ли учли
 </​note>​ </​note>​
 +
 +Абсолютно то же самое, только переписанное на Python, вдруг кому пригодится. Те же комментарии,​ таблицы обрабатываются не до конца и нужно не забыть заменить <​!/​code>​ на %%</​code>​%%:​
 +
 +<code python>
 +#​!/​usr/​bin/​env python
 +# coding=utf8
 +
 +# Author: Vadim Nevorotin (malamut@ubuntu.ru)
 +# License: GPLv3
 +
 +# Rus:
 +# Скрипт конвертации разметки MoinMoin в разметку DokuWiki
 +# Использование:​ moin2doku.py moin_file doku_file [namespace]
 +# namespace - это пространство имён для всех ссылок на вложения (картинки,​ например)
 +
 +# Скрипт не до конца обрабатывает таблицы,​ не учитывается склейка ячеек и выравнивание
 +# Так же не добавлена конвертация новой возможности moin: {{{#!wiki caution }}} и прочих выделений (http://​moinmo.in/​HelpOnAdmonitions)
 +# Всё остальное с http://​moinmo.in/​HelpOnEditing на 11.2009 конвертируется в полном объёме
 +
 +# Eng:
 +
 +# Usage: ./​moin2doku.py moin_file doku_file [namespace]
 +# namespace - for all links to images ({{namespace:​image.png}})
 +
 +import sys
 +import re
 +from os.path import isfile
 +
 +# Hash for smileys replacement
 +smileys = {
 + #​moin doku
 + '​X-('​ :​ ':​-X',​
 + ':​D'​ :​ ':​-D',​
 + '<:​('​ :​ ':​-?',​
 + ':​o'​ :​ ':​-O',​
 + ':​('​ :​ ':​-(',​
 + ':​)'​ :​ ':​-)',​
 + '​B)'​ :​ '​8-)',​
 + ':​))'​ :​ ':​-P',​
 + ';​)'​ :​ ';​-)',​
 + '/​!\\'​ :​ ':​!:',​
 + '<​!>'​ :​ ':​!:',​
 + '​(!)'​ :​ ':​!:',​
 + ':​-?'​ :​ ':​-P',​
 + ':​\\'​ :​ ':​-\\',​
 + '>:>'​ :​ '​^_^',​
 + '​|)'​ :​ ':​-|',​
 + ':​-('​ :​ ':​-(',​
 + ':​-)'​ :​ ':​-)',​
 + '​B-)'​ :​ '​8-)',​
 + ':​-))'​ :​ ':​-P',​
 + ';​-)'​ :​ ';​-)',​
 + '​|-)'​ :​ ':​-|',​
 + '​(./​)'​ :​ '​LOL',​
 + '​{OK}'​ :​ ':​!:',​
 + '​{X}'​ :​ ':​!:',​
 + '​{i}'​ :​ ':​!:',​
 + '​{1}'​ :​ '<​1>',​
 + '​{2}'​ :​ '<​2>',​
 + '​{3}'​ :​ '<​3>',​
 + '​{*}'​ :​ '<​ubu>',​
 + '​{o}'​ :​ '<​circ>',​
 +}
 +
 +# Convert functions
 +
 +def inv(br):
 + if br == '​{':​ return '​}'​
 + if br == '​[':​ return '​]'​
 + return br
 +
 +def linkReplacement(namespace,​br,​target,​text):​
 + if not target:
 + print >> sys.stderr, "Fatal error"
 + sys.exit(1)
 + m_att = re.match('​(?:​(?:​attachment|drawing):​)(?​P<​att>​.+)$',​target)
 + m_inter = re.match('​(.+?​):​(?​!//​)(.+)$',​target)
 + # Attachments links
 + if m_att:
 + if m_att.group('​att'​):​
 + att = re.sub('​(.+)/','',​m_att.group('​att'​)) #​ leave only filename, without namespaces ​
 + else:
 + att = m_att.groups('​att'​)
 + if text:
 + return '​{{'​+namespace+':'​+att+'​|'​+text+'​}}'​
 + else:
 + return '​{{'​+namespace+':'​+att+'​}}'​
 + # InterWiki links
 + if m_inter:
 + if text:
 + return '​[['​+m_inter.group(1)+'>'​+m_inter.group(2)+'​|'​+text+'​]]'​
 + else:
 + return '​[['​+m_inter.group(1)+'>'​+m_inter.group(2)+'​]]'​
 + # All other links simply returned in doku format
 + if not re.search('://',​ target): # if target not an internet link replace all / to : (for namespaces)
 + target = re.sub('/',':',​target)
 + if text:
 + return br + br + target + '​|'​ + text + inv(br) * 2
 + else:
 + return br + br + target + inv(br) * 2
 +
 +def tableReplacement(table):​
 + for i in range(len(table)):​
 + line = table[i]
 + line = re.sub('​\|\|(?:​\s*(<​.+?>​))?​\s*(.+?​)\s*(?​=\|\|)',​ lambda m: '​||'​ + m.group(1) + m.group(2) if m.group(1) else '​||'​ + m.group(2), line)
 + line = re.sub('​((?:​\|\|){2,​})(.+?​)(?​=\|\|)',​ lambda m: '​|| ​ '​+m.group(2)+' ​ ' + '​|'​ * (len(m.group(1)) - 2), line)
 + line = re.sub('​\|\|(\s*)(?:<​.+?>​\s*?​)+(.*?​)(?​=\|\|)','​||\\1\\2',​ line)
 + line = re.sub('​\|\|','​|',​ line)
 + table[i] = line
 + size = len(table)
 + i = 0
 + while i < size:
 + if re.match('​\|+$',​table[i]):​
 + table.pop(i)
 + size=size-1
 + else:
 + i=i+1
 + return table
 +
 +def blockReplacement(block):​
 + if block and not block[0].strip():​
 + block.pop(0)
 + if not block[-1].strip():​
 + block.pop()
 + if not block:
 + block.append('<​code>​\n'​)
 + elif re.match('​\s*#​!python',​block[0]):​
 + block.pop(0)
 + block.insert(0,'<​code python>​\n'​)
 + elif re.match('​\s*#​!cplusplus',​block[0]):​
 + block.pop(0)
 + block.insert(0,'<​code cpp>​\n'​)
 + elif re.match('​\s*#​!java',​block[0]):​
 + block.pop(0)
 + block.insert(0,'<​code java>​\n'​)
 + elif re.match('​\s*#​!pascal',​block[0]):​
 + block.pop(0)
 + block.insert(0,'<​code pascal>​\n'​)
 + elif re.match('​\s*#​!.+',​block[0]):​
 + block.pop(0)
 + block.insert(0,'<​code>​\n'​)
 + else:
 + block.insert(0,'<​code>​\n'​)
 + block.append('<​!/​code>'​)
 + return block
 +
 +def ConvertMoinToDoku(moin_file,​doku_file,​namespace):​
 + try:
 + infile = open(moin_file,"​r"​)
 + outfile = open(doku_file,"​w"​)
 + except:
 + print >> sys.stderr, "Open file error!"​
 + sys.exit(1)
 +
 + content=infile.readlines()
 + infile.close()
 +
 + # Conversion
 + st = '​~@!'​ #​ For headings conversion
 + camel = '​(?<​![\\[!:​])\b[A-Z]+[a-z]+[A-Z][A-Za-z]*\b'​ #​ CamelCase regexp
 + conversion = (
 + # Processing instructions
 + ('​^##​(.*)\n',''​),​
 + ('​^#​(pragma|format|redirect|refresh|language)(.*)\n',''​),​
 + ('​^#​deprecated(.*)\n','<​note warning>​This page is deprecated<​note>​\n'​),​
 + # Other elements
 + ('​(<<​BR>>​)|(\[\[BR]])','​\\\\ '),
 + ('​^\s*-{4,​}\s*$','​----\n'​),​
 + ('​^(.*)/​\*(.+?​)\*/​(.*)\n','​\\1\n>​\\2\n\\3\n'​),​ #​ inline comments
 + # Macros and another foolish - simply remove
 + ('<<​.+?>>',''​),​
 + # Headings
 + ('​^\s*=====\s*(.+?​)\s*=====\s*$',​st*2 + ' \\1 ' + st*2 + '​\n'​),​
 + ('​^\s*====\s*(.+?​)\s*====\s*$',​st*3 + ' \\1 ' + st*3 + '​\n'​),​
 + ('​^\s*===\s*(.+?​)\s*===\s*$',​st*4 + ' \\1 ' + st*4 + '​\n'​),​
 + ('​^\s*==\s*(.+?​)\s*==\s*$',​st*5 + ' \\1 ' + st*5 + '​\n'​),​
 + ('​^\s*=\s*(.+?​)\s*=\s*$',​st*6 + ' \\1 ' + st*6 + '​\n'​),​
 + (st,'​='​),​
 + # CamelCase links
 + ('​('​+camel+'​)/​('​+camel+'​)','​[[\\1:​\\2]]'​),​
 + ('​\.\./​('​+camel+'​)','​[[\\1]]'​),​
 + ('/​('​+camel+'​)','​[['​+namespace+':​\\1]]'​),​
 + ('​('​+camel+'​)','​[[\\1]]'​),​
 + # Avoid automating linking - simply remove
 + ("''''''",''​),​
 + ('​``',''​),​
 + ('​!([A-Z]\w+)','​\\1'​),​
 + # Text formatting
 + ("'''''​(.+?​)'''''",'​**//​\\1//​**'​),​
 + ("'''​(.+?​)'''",'​**\\1**'​),​
 + ("''​(.+?​)''",'//​\\1//'​),​
 + ('​`(.+?​)`',"''​\\1''"​),​
 + (',,​(.+?​),,','<​sub>​\\1</​sub>'​),​
 + ('​\^(.+?​)\^','<​sup>​\\1</​sup>'​),​
 + ('​--\((.+?​)\)--','<​del>​\\1</​del>'​),​
 + # Unsupported text formating - simply remove
 + ('​~-(.+?​)-~','​\\1'​),​
 + ('​~\+(.+?​)\+~','​\\1'​)
 + )
 +
 + # Values for conversion
 + intend = 0
 + is_table = 0
 + is_block = 0
 + block_sep_len = 3
 + is_list = 0
 + table = []
 + block = []
 +
 + for i in range(len(content)):​
 + line = content[i]
 + # First of all remove all end whitespaces except \n
 + line = re.sub('​\s*$','​\n',​line)
 + # Tables
 + m = re.search('​\s*(?​P<​line>​\|\|.+\|\|\n)',​line)
 + if m and not is_block:
 + is_table = 1
 + is_list = 0
 + line = m.group('​line'​)
 + elif is_table:
 + is_table = 0
 + table = tableReplacement(table)
 + outfile.writelines(table)
 + table = []
 + # Code blocks parser
 + if not is_block:
 + line = re.sub('​\{{3}(.+?​)}{3}','​%%\\1%%',​line)
 + m_open = re.search('​(?​P<​text>​.*?​)\s*(?​P<​sep>​\{{3,​})(?​P<​mod>​.*\n)',​line)
 + m_close = re.search('​(?​P<​data>​.+?​)?​}{'​+str(block_sep_len)+'​}\s*(?​P<​text>​.*)\n',​line)
 + if m_open and not is_block and not is_table:
 + block_sep_len = len(m_open.group('​sep'​))
 + is_block = 1
 + if is_list:
 + outfile.write('​\n'​)
 + is_list = 0
 + block = [m_open.group('​mod'​)]
 + if not ( m_open.group('​text'​) and not re.match('​\s+$',​m_open.group('​text'​)) ):
 + continue
 + line = m_open.group('​text'​) + '​\n'​
 + elif m_close and is_block:
 + is_block = 0
 + if m_close.group('​data'​):​
 + block.append(m_close.group('​data'​) + '​\n'​)
 + block = blockReplacement(block)
 + outfile.writelines(block)
 + block = []
 + if not m_close.group('​text'​):​
 + continue
 + line = m_close.group('​text'​) + '​\n'​
 + elif is_block:
 + block.append(line)
 + continue
 + # Links
 + # Unfortunately,​ Python is not a Perl, so we can't convert links using only one regexp...
 + link = ''​
 + s = ''​
 + br = ''​
 + is_link = 0
 + last = len(line)-1
 + for i in range(len(line)):​
 + if not is_link and i != last and line[i] == '​['​ and line[i+1] == '​[':​
 + is_link = 1
 + link = br = '​['​
 + elif not is_link and i != last and line[i] == '​{'​ and line[i+1] == '​{':​
 + is_link = 1
 + link = br = '​{'​
 + elif is_link and line[i] == inv(br) and line[i-1] == inv(br):
 + is_link = 0
 + link = link + line[i]
 + m = re.match('​(?​P<​target>​[^\|]+)(?:​\|(?​P<​text>​[^\|]+))?',​link[2:​-2])
 + link = linkReplacement(namespace,​link[0],​m.group('​target'​),​m.group('​text'​))
 + s = s + link
 + elif is_link:
 + link = link + line[i]
 + else:
 + s = s + line[i]
 + line = s
 + # Now convert syntax
 + for r in conversion:
 + reg=re.compile(r[0],​re.I)
 + line=reg.sub(r[1],​line)
 + # Lists and intends
 + m_dotted = re.match('​(?​P<​intend>​\s+)\*\s+(?​P<​value>​\S.*)\n',​line)
 + m_numeric = re.match('​(?​P<​intend>​\s+)[1aAiI]\.(#​\d+)?​\s+(?​P<​value>​\S.*)\n',​line)
 + m_defs = re.match('​(?​P<​intend>​\s+)(?​P<​key>​\S[^\[\{]+)::​\s+(?​P<​value>​\S.*)\n',​line)
 + m_def = re.match('​(?​P<​intend>​\s+)(?​P<​key>​\S[^\[\{]+)::​\s*',​line)
 + m_desc = re.match('​(?​P<​intend>​\s+)::​\s*(?​P<​value>​\S.*)\n',​line)
 + m_white = re.match('​(?​P<​intend>​\s+)(\.\s+)?​\S',​line)
 + if m_dotted:
 + if is_list:
 + outfile.write('​\n'​)
 + outfile.write("​ "​*(2*len(m_dotted.group('​intend'​)))+"​* ")
 + line = m_dotted.group('​value'​)
 + intend = len(m_dotted.group('​intend'​))
 + is_list = 1
 + elif m_numeric:
 + if is_list:
 + outfile.write('​\n'​)
 + outfile.write("​ "​*(2*len(m_numeric.group('​intend'​)))+"​- ")
 + line = m_numeric.group('​value'​)
 + intend = len(m_numeric.group('​intend'​))
 + is_list = 1
 + elif m_defs:
 + if is_list:
 + outfile.write('​\n'​)
 + outfile.write('​**'​+m_defs.group('​key'​)+'​**\n'​)
 + outfile.write(' ​ * ')
 + line = m_defs.group('​value'​)
 + intend = len(m_defs.group('​intend'​))
 + is_list = 1
 + elif m_def:
 + if is_list:
 + outfile.write('​\n'​)
 + outfile.write('​**'​+m_def.group('​key'​)+'​**\n'​)
 + line = ''​
 + is_list = 0
 + elif m_desc:
 + if is_list:
 + outfile.write('​\n'​)
 + outfile.write(' ​ * ')
 + line = m_desc.group('​value'​)
 + intend = len(m_desc.group('​intend'​))
 + is_list = 1
 + elif m_white:
 + curr_intend = len(m_white.groups('​intend'​))
 + if curr_intend != intend and is_list:
 + outfile.write('​\n\n'​)
 + is_list = 0
 + elif curr_intend != intend and not is_list:
 + outfile.write('​\n'​)
 + elif curr_intend == intend and is_list:
 + outfile.write('​ ')
 + line = line.rstrip()
 + intend = curr_intend
 + line = re.sub('​^\s*(\.\s*)?​(?​=\S)','',​line)
 + else:
 + if is_list and not re.match('​\n',​line):​
 + outfile.write('​\n\n'​)
 + elif intend:
 + outfile.write('​\n'​)
 + intend = 0
 + is_list = 0
 + # Smileys $)
 + for smile in smileys.keys():​
 + line = re.sub('​(\s|\A)'​+re.escape(smile)+'​(\s)','​ '​+smileys[smile]+'​\\2',​line)
 + # Finally...
 + if is_block and is_list:
 + line += '​\n'​
 + if not is_table:
 + outfile.write(line)
 + else:
 + table.append(line)
 + # If we haven'​t close some things
 + if is_table:
 + table = tableReplacement(table)
 + outfile.writelines(table)
 + if is_block:
 + block = blockReplacement(block)
 + outfile.writelines(block)
 + outfile.close()
 +
 +# Main script...
 +
 +def PrintHelp():​
 + print """​Usage:​ moin2doku moin_file doku_file [namespace]
 +Convert MoinMoin page to Doku, using namespace for pictures"""​
 + sys.exit(0)
 +
 +def PrintParameterError():​
 + print >> sys.stderr, "​Incorrect parameters! Use --help to read more information."​
 + sys.exit(1)
 +
 +def CheckParameters(moin_file,​doku_file):​
 + if not isfile(moin_file):​
 + print >> sys.stderr, "Moin file doesn'​t exists!"​
 + sys.exit(1)
 + if not isfile(doku_file):​
 + print >> sys.stderr, "Doku file doesn'​t exists!"​
 + sys.exit(1)
 +
 +if __name__ == '​__main__':​
 + if len(sys.argv) > 1:
 + if sys.argv[1] in ('​-h',​ '​--help'​):​
 + PrintHelp()
 + elif len(sys.argv) > 2:
 + moin_file = sys.argv[1]
 + doku_file = sys.argv[2]
 + if len(sys.argv) > 3:
 + namespace = sys.argv[3]
 + else:
 + namespace = '​.'​
 + else:
 + PrintParameterError()
 + else:
 + PrintParameterError()
 +
 + CheckParameters(moin_file,​doku_file)
 +
 + ConvertMoinToDoku(moin_file,​doku_file,​namespace)
 +</​code>​
 +
 +===== Улучшенная версия =====
 +
 +Я ж ленивый,​ поэтому я написал '​обёртку'​ к представленной выше функции конвертации так, что бы её было удобно вызывать с различными аргументами и, кроме всего прочего,​ этот вариант умеет скачивать страницы и вложения из интернета. Функция конвертации здесь та же, просто она сопровождается удобным способом её вызывать.
 +
 +Описание см. в POD в коде. Не забудьте заменить <​!/​code>​ на </​code>​.
 +
 +<code perl>
 +#​!/​usr/​bin/​perl -w
 +
 +use 5.010;​ #​ Для всяких выкрутасов,​ очень удобно
 +
 +use File::​Path;​ #​ Для управления каталогами
 +use File::​Spec::​Functions;​ #​ Для склейки путей
 +use Getopt::​Long;​ #​ Аргументики,​ разбираем аргументики
 +use Pod::​Usage;​ #​ Для документации (всё равно пашет через пень-колоду)
 +
 +=begin comment
 +
 +Сначала идёт основная функция конвертации одного файла в другой,​ потом всякая ерунда,​ связанная с
 +выкачкой вложений из интернета и разбором аргументов. Основную функцию как мог перевёл на аглицкий.
 +
 +Аргументы описываются ниже в основной программе,​ всё это написано just for fun, но если найдёте ошибку - 
 +сообщите мне, или, как вариант,​ исправьте прям в этом скрипте самостоятельно и не забудьте поместить его
 +обратно на help.ubuntu.ru
 +
 +=end comment
 +
 +=cut
 +
 +# ----------------------------------------------------------------------------------------------- #
 +#             ​Convert text file with MoinMoin Wiki syntax to DokuWiki syntax ​                     #
 +#                          by Malamut (malamut@ubuntu.ru) (2009) ​                                 #
 +# ----------------------------------------------------------------------------------------------- #
 +
 +# Hash for smileys replacement
 +%smileys = (
 + #​moin doku
 + '​X-('​ =>​ ':​-X',​
 + ':​D'​ =>​ ':​-D',​
 + '<:​('​ =>​ ':​-?',​
 + ':​o'​ =>​ ':​-O',​
 + ':​('​ =>​ ':​-(',​
 + ':​)'​ =>​ ':​-)',​
 + '​B)'​ =>​ '​8-)',​
 + ':​))'​ =>​ ':​-P',​
 + ';​)'​ =>​ ';​-)',​
 + '/​!\\'​ =>​ ':​!:',​
 + '<​!>'​ =>​ ':​!:',​
 + '​(!)'​ =>​ ':​!:',​
 + ':​-?'​ =>​ ':​-P',​
 + ':​\\'​ =>​ ':​-\\',​
 + '>:>'​ =>​ '​^_^',​
 + '​|)'​ =>​ ':​-|',​
 + ':​-('​ =>​ ':​-(',​
 + ':​-)'​ =>​ ':​-)',​
 + '​B-)'​ =>​ '​8-)',​
 + ':​-))'​ =>​ ':​-P',​
 + ';​-)'​ =>​ ';​-)',​
 + '​|-)'​ =>​ ':​-|',​
 + '​(./​)'​ =>​ '​LOL',​
 + '​{OK}'​ =>​ ':​!:',​
 + '​{X}'​ =>​ ':​!:',​
 + '​{i}'​ =>​ ':​!:',​
 + '​{1}'​ =>​ '<​1>',​
 + '​{2}'​ =>​ '<​2>',​
 + '​{3}'​ =>​ '<​3>',​
 + '​{*}'​ =>​ '<​ubu>',​
 + '​{o}'​ =>​ '<​circ>',​
 +);
 +
 +# For links regex
 +sub inv {
 + given ($_[0]) {
 + when ("​{"​) { return "​}"​ }
 + when ("​["​) { return "​]"​ }
 + }
 + return $_[0];
 +}
 +
 +# Links 
 +sub linkReplacement {
 + die "​Oops!\n"​ unless @_ == 5;
 + my ($namespace,​$br,​$target,​$text,​$params) = @_;
 + # Attachments links
 + if ($target =~ /​^(?:​(?:​attachment|drawing):​)(?<​att>​.+)$/​) {
 + (my $att = $+{att}) =~ s{(.+)/​}{};​ #​ leave only filename, without namespaces ​
 + if ($text) { return "​{{$namespace:​$att|$text}}"​ }
 + else { return "​{{$namespace:​$att}}"​ }
 + }
 + # InterWiki links
 + if ($target =~ m#​^(.+?​):​(?​!//​)(.+)$#​) { # all with : except internet links (containing ://)
 + if ($text) { return "​[[$1>​$2|$text]]"​ }
 + else { return "​[[$1>​$2]]"​ }
 + }
 + # All other links simply returned in doku format
 + $target =~ s#/#:#g if $target !~ m#://#; # if target not an internet link replace all / to : (for namespaces)
 + if ($text) { return "​$br$br$target|$text"​ . inv($br) x 2 }
 + else { return "​$br$br$target"​ . inv($br) x 2 }
 +}
 +
 +# Blocks
 +sub blockReplacement {
 + for (my $i = 0; $i < @_ && $_[$i] =~ /^\$*$/; $i++ ) { shift @_ } # delete empty lines
 + for (my $i = -1; -$i <= @_ && $_[$i] =~ /^\$*$/; $i-- ) { pop @_ } # delete empty lines
 + if (!@_) { unshift @_, "<​code>​\n"​ }
 + elsif ($_[0] =~ /​^\s*#​!python/​) { shift @_; unshift @_, "<​code python>​\n"​ }
 + elsif ($_[0] =~ /​^\s*#​!cplusplus/​) { shift @_; unshift @_, "<​code cpp>​\n"​ }
 + elsif ($_[0] =~ /​^\s*#​!java/​) { shift @_; unshift @_, "<​code java>​\n"​ }
 + elsif ($_[0] =~ /​^\s*#​!pascal/​) { shift @_; unshift @_, "<​code pascal>​\n"​ }
 + elsif ($_[0] =~ /​^\s*#​.+/​) { shift @_; unshift @_, "<​code>​\n"​ }
 + else { unshift @_, "<​code>​\n"​ }
 + push @_, "<​!/​code>​\n";​
 + return @_;
 +}
 +
 +# Tables
 +sub tableReplacement {
 + @table = @_;
 + for (@table) {
 + s/​\|\|(?:​\s*(<​.+?>​))?​\s*(.+?​)\s*(?​=\|\|)/​ #​ delete all whitespaces
 + if ($1) { "​||$1$2"​ }
 + else { "​||$2"​ }
 + /ge;
 + s/​((?:​\|\|){2,​})(.+?​)(?​=\|\|)/​ #​ move few || to another side of cell and center
 + "​|| ​ $2  " . '​|'​ x (length($1) - 2)
 + /ge;
 + # Span
 + # TODO
 + # Aligment
 + # TODO
 + #​s/​\|\|.*?​(?:​(?:<​\(>​)|(?:<​style="​text-align:​ left">​))(?​=\|\|)//​g;​ #​ left
 + #​s/<:>//​g;​ #​ center
 + #​s/<​\)>//​g;​ #​ right
 + # Ok, thats all
 + s/​\|\|(\s*)(?:<​.+?>​\s*?​)+(.*?​)(?​=\|\|)/​||$1$2/​g;​ #​ remove all tags, we really don't need it
 + s/​\|\|/​|/​g;​ #​ finally replace || to |
 + }
 + # Remove all empty strings
 + for (0..$#​table) {
 + if ($table[$_] =~ /^\|+$/) { splice @table, $_, 1 }
 + }
 + return @table;
 +}
 +
 +# Convert moin file (first argument) to doku file (second argument), namespace for attachments is the third argument (default - (.))
 +sub ConvertMoinToDoku {
 + die "​ConvertMoinToDoku:​ I need 2 or 3 arguments!\n"​ if (@_ != 2 and @_ != 3);
 + my $moin = shift @_;
 + my $doku = shift @_;
 + my $namespace = shift @_ // '​.';​ #/#​3rd argument or (.)
 + open INFILE, "<",​ $moin or die "​Can'​t open '​$moin'​ ($!)!\n";​
 + open OUTFILE, ">",​ $doku or die "​Can'​t open '​$doku'​ ($!)!\n";​
 + my $intend = 0;
 + my $is_table = 0;
 + my $is_block = 0;
 + my $block_sep_len = 3;​ #​ length of block separator ({{{), in can be >= 3
 + my $is_list = 0;
 + my @table = ();
 + my @block = ();
 +MOINSCAN: while (<​INFILE>​) {
 + # First of all remove all end whitespaces except \n
 + s/​\s*$/​\n/;​
 + # Tables
 + if (/​\s*(?<​line>​\|\|.+\|\|\n)/​ && !$is_block) {
 + $is_table = 1;
 + $is_list = 0;
 + $_ = $+{line};
 + } elsif ($is_table) {
 + $is_table = 0;
 + @table = tableReplacement(@table);​
 + print OUTFILE @table;
 + @table = ();
 + }
 + # Code blocks parser
 + s/​\{{3}(.+?​)}{3}/​%%$1%%/​g unless $is_block;​ #​ first remove all ignored blocks
 + if (/​(?<​text>​.*?​)\s*(?<​sep>​\{{3,​})(?<​mod>​.*\n)/​ && !$is_block && !$is_table) {
 + $block_sep_len = length $+{sep};
 + $is_block = 1;
 + if ($is_list) { print OUTFILE "​\n"​ }
 + $is_list = 0;
 + @block = ($+{mod});
 + next MOINSCAN unless ($+{text} && $+{text} !~ /^\s+$/);
 + $_ = "​$+{text}\n";​
 + } elsif (/​(?<​data>​.+?​)?​}{$block_sep_len}\s*(?<​text>​.*)\n/​ && $is_block) {
 + $is_block = 0;
 + push @block, "​$+{data}\n"​ if $+{data};
 + @block = blockReplacement(@block);​
 + print OUTFILE @block;
 + @block = ();
 + next MOINSCAN unless $+{text};
 + $_ = "​$+{text}\n";​
 + } elsif ($is_block) {
 + push @block, $_;
 + next MOINSCAN;
 + }
 + # Processing instructions
 + s/##​(.*)\n//;​ #​ comments
 + s/#​(pragma|format|redirect|refresh|language)(.*)\n//​i;​ #​ remove all
 + s/#​deprecated(.*)\n/<​note warning>​This page is deprecated<​note>​\n/​i;​ #​ deprecated
 + # Other elements
 + s/​(<<​BR>>​)|(\[\[BR]])/​\\\\ /​g;​ #​ break
 + s/​^\s*-{4,​}\s*$/​----\n/​g;​ #​ horizontal line
 + s#​^(.*)/​\*(.+?​)\*/​(.*)\n#​$1\n>​$2\n$3\n#;​ #​ inline comments
 + # Macros and another foolish - simply remove
 + s/<<​.+?>>//​g;​ #​ macros
 + # Headings
 + my $s = "​~!@>​{";​
 + s/​^\s*=====\s*(.+?​)\s*=====\s*$/​$s$s $1 $s$s\n/​g;​ #​ level 5
 + s/​^\s*====\s*(.+?​)\s*====\s*$/​$s$s$s $1 $s$s$s\n/​g;​ #​ level 4
 + s/​^\s*===\s*(.+?​)\s*===\s*$/​$s$s$s$s $1 $s$s$s$s\n/​g;​ #​ level 3
 + s/​^\s*==\s*(.+?​)\s*==\s*$/​$s$s$s$s$s $1 $s$s$s$s$s\n/​g;​ #​ level 2
 + s/​^\s*=\s*(.+?​)\s*=\s*$/​$s$s$s$s$s$s $1 $s$s$s$s$s$s\n/​g;​ #​ level 1
 + s/​\Q$s\E/​=/​g;​
 + # Links
 + s/
 + (?<​br>​[\[\{])\g{br} #​ opening brackets
 + (?<​target>​[^\|]+?​) #​ target
 + (?:​\|(?<​text>​ #​ text
 + (?​(?​=\{\{).+?​}}|[^\|]+?​) #​ test if text is an image link
 + ))?
 + (?:​\|(?<​params>​[^\|]+?​))?​ #​ parameters
 + (??{ &​inv($+{br}) x 2 }) # closing brackets
 + /
 + &​linkReplacement($namespace,​$+{br},​$+{target},​$+{text},​$+{params})
 + /gxe;
 + # CamelCase links
 + my $camel = '​(?<​![\\[!:​])\b[A-Z]+[a-z]+[A-Z][A-Za-z]*\b';​ #​ CamelCase link regex
 + s#​($camel)/​($camel)#​[[$1:​$2]]#​g;​ #​ CamelCase to namespace
 + s#​\.\./​($camel)#​[[$1]]#​g;​ #​ (very)strange CamelCase
 + s#/​($camel)#​[[$namespace:​$1]]#​g;​ #​ CamelCase to subpage
 + s#​($camel)#​[[$1]]#​g;​ #​ simlpe CamelCase
 + # Avoid automating linking - simply remove
 + s{''''''​}<>​g;​
 + s{``}<>​g;​
 + s{!([A-Z]\w+)}<​$1>​g;​
 + # Text formatting
 + s{'''''​(.+?​)'''''​}<​**//​$1//​**>​g;​ #​ bold and italic
 + s{'''​(.+?​)'''​}<​**$1**>​g;​ #​ bold
 + s{''​(.+?​)''​}<//​$1//>​g;​ #​ italic
 + s{`(.+?​)`}<''​$1''>​g;​ #​ monospaced
 + s{,,​(.+?​),,​}{<​sub>​$1</​sub>​}g;​ #​ sub index
 + s{\^(.+?​)\^}{<​sup>​$1</​sup>​}g;​ #​ sup index
 + s{--\((.+?​)\)--}{<​del>​$1</​del>​}g;​ #​ strike through text
 + # Unsupported text formating - simply remove
 + s{~-(.+?​)-~}<​$1>​g;​ #​ smaller text
 + s{~\+(.+?​)\+~}<​$1>​g;​ #​ larger text
 + # Lists and intends
 + if (/​^(?<​intend>​\s+)\*\s+(?<​value>​\S.*)\n/​) { # dotted list
 + if ($is_list) { print OUTFILE "​\n"​ }
 + print OUTFILE " "​x(2*length($+{intend})),"​* ";
 + $_ = $+{value};
 + $intend = length $+{intend};
 + $is_list = 1;
 + } elsif (/​^(?<​intend>​\s+)[1aAiI]\.(#​\d+)?​\s+(?<​value>​\S.*)\n/​) { # numeric list
 + if ($is_list) { print OUTFILE "​\n"​ }
 + print OUTFILE " "​x(2*length($+{intend})),"​- ";
 + $_ = $+{value};
 + $intend = length $+{intend};
 + $is_list = 1;
 + } elsif (/​^(?<​intend>​\s+)(?<​key>​\S[^\[\{]+)::​\s+(?<​value>​\S.*)\n/​) { # definition list
 + if ($is_list) { print OUTFILE "​\n"​ }
 + print OUTFILE "​**$+{key}**\n";​
 + print OUTFILE " ​ * ";
 + $_ = $+{value};
 + $intend = length $+{intend};
 + $is_list = 1;
 + } elsif (/​^(?<​intend>​\s+)(?<​key>​\S[^\[\{]+)::​\s*/​) { # definition
 + if ($is_list) { print OUTFILE "​\n"​ }
 + print OUTFILE "​**$+{key}**\n";​
 + $_="";​
 + $is_list = 0;
 + } elsif (/​^(?<​intend>​\s+)::​\s*(?<​value>​\S.*)\n/​) { # description
 + if ($is_list) { print OUTFILE "​\n"​ }
 + print OUTFILE " ​ * ";
 + $_ = $+{value};
 + $intend = length $+{intend};
 + $is_list = 1;
 + } elsif (/​^(?<​intend>​\s+)(\.\s+)?​\S/​) { # simple leading whitespaces
 + my $curr_intend = length $+{intend};
 + if ($curr_intend != $intend and $is_list) {
 + print OUTFILE "​\n\n";​
 + $is_list = 0;
 + }
 + elsif ($curr_intend != $intend and !$is_list) { print OUTFILE "​\n"​ }
 + elsif ($curr_intend == $intend and $is_list) {
 + print OUTFILE " ";
 + s/​\n$//;​
 + }
 + $intend = $curr_intend;​
 +
 + s/​^\s*(\.\s*)?​(?​=\S)//;​
 + } else { #​ string witout leading whitespaces
 + if ($is_list && ! /^\n/) { print OUTFILE "​\n\n"​ } 
 + elsif ($intend) { print OUTFILE "​\n"​ }
 + $intend = 0;
 + $is_list = 0;
 + }
 + # Smileys ;)
 + foreach $smile (keys %smileys) {
 + s/​(\s|\A)\Q$smile\E(\s)/​ $smileys{$smile}$2/​g;​
 + }
 + # Ok, print all results of abracadabra to file if we need it
 + if ($is_block && $is_list) { $_ .= "​\n"​ }
 + if (!$is_table) { print OUTFILE $_ }
 + else { push @table, $_ }
 + }
 + if ($is_table) {
 + @table = tableReplacement(@table);​
 + print OUTFILE @table;
 + }
 + if ($is_block) {
 + @block = blockReplacement(@block);​
 + print OUTFILE @block;
 + }
 + close INFILE or die "​error\n";​
 + close OUTFILE or die "​error\n";​
 +}
 +
 +# ----------------------------- ​ End of Convert Function ​ --------------------------------------- #
 +
 +# ------------------------------------- ​ Функции ​ ----------------------------------------------- #
 +
 +# Создаёт директорию если её ещё нет (должна быть такая функция,​ но я не знаю :( ). Параметр - имя создаваемой директории
 +sub CreateDir {
 + die "​CreateDir:​ нужен один аргумент!\n"​ unless @_ == 1;
 + if (! -e -d $_[0]) {
 + mkdir $_[0] or die "Не могу создать '​$_[0]'​! ($!)\n";​
 + }
 +}
 +
 +# Получает нужный файл из интернета с вики и пишет его в нужное местоъ
 +# Первый параметр - адрес для скачивания
 +# Второй параметр - файл, в который нужно сохранить результат
 +sub GetUrlFile {
 + die "​GetUrlFile:​ Должно быть ровно два аргумента\n"​ unless @_ == 2;
 + my ($url, $filename) = @_;
 + my $command = "wget -nv --user-agent '​Opera'​ -O '​$filename'​ '​$url'";​
 + if  (system $command ) {
 + warn "​Невозможно получить или сохранить $url\n";​
 + return undef;
 + }
 +}
 +
 +# Получает все вложения из указанного файла по указанному адресу
 +# Первый параметр - имя файла с moin разметкой
 +# Второй параметр - базовый интернет-адрес вики
 +# Третий параметр - адрес страницы на вики вместе с неймспейсом если нужно
 +# Четвёртый параметр - папка куда складывать все скачанные файлы
 +sub GetAttachments {
 + die "​GetAttachments:​ Должно быть ровно четыре аргумента\n"​ unless @_ == 4;
 + my ($moin,​$base_url,​$page_url,​$atts_dir) = @_;
 + open INFILE, "<",​ $moin or die "Не могу открыть файл '​$moin'​!\n";​
 + while (<​INFILE>​) {
 + if (my @atts = /​([\{\[])\1(?:​(?:​attachment|drawing):​)(.*?​)(??​{ &​inv($1) x 2 })/g) {
 + for (my $i = 1; $i < @atts; $i+=2) {
 + my ($namespace,​ $file) = $atts[$i] =~ m#​^(?:​(.+)/​)?​([^/​]+?​)$#;​
 + my $url = $namespace
 + ? "​$base_url/​$namespace?​action=AttachFile&​do=get&​target=$file"​
 + : "​$base_url/​$page_url?​action=AttachFile&​do=get&​target=$file";​
 + CreateDir($atts_dir);​
 + GetUrlFile($url,​catfile($atts_dir,​$file));​
 + }
 + }
 + }
 + close INFILE;
 +}
 +
 +# ---------------------------- ​ Программа ​ ------------------------------------------------------- #
 +
 +=head1 NAME
 +
 +moin2doku.pl - преобразует разметку из MoinMoin в DokuWiki
 +
 +=head1 SYNOPSIS
 +
 +moin2doku.pl [options] files|urls
 +
 +=cut
 +
 +# Всякие (не)нужные переменные...
 +my $help = 0; # показать помощь
 +my $urls = 0; # воспринимать аргументы как url, а не как файлы (соответствует ключу -u)
 +my $verbose = 0; # подробный вывод
 +
 +my $working_dir = "";​ #​ директория для сохранения всех результатов
 +my $use_pagename_dir = 0; # использовать ли в качестве $working_dir имя страницы для каждой обработанной страницы
 +my $attachments_dir = "";​ #​ директория для всяких картинок и прочего из {{attachment}}
 +
 +my $links = 1; # преобразовывать ли внутренние ссылки
 +
 +my $wiki_adress = "​localhost";​ #​ адрес вики
 +my $wiki_namespace = "";​ #​ неймспейс на вики откуда брать страницы
 +
 +my $moin_file = "";​ #​ имя файла с моин разметкой для закачки при -u, если не указано - используется ИмяСтраницы.moin
 +my $doku_file = "";​ #​ имя файла с доку разметкой,​ если не указано - используется ИмяСтраницы.txt или ИмяСтраницы.doku
 +
 +# Задаёт нужные параметры для русских статей с wiki.ubuntu.com
 +sub ru_official_wiki {
 + $wiki_adress = '​https://​wiki.ubuntu.com';​
 + $wiki_namespace = '​RussianDocumentation';​
 +}
 +
 +Getopt::​Long::​Configure ("​bundling"​);​ #​ Конфигурирование getopt дабы воспринимать склейку коротких аргументов
 +GetOptions(
 + "​verbose|v+"​ => \$verbose,​ #​ TODO: подробный вывод
 + "​delete-internal-links|l"​ => \$links,​ #​ TODO: удалять внутренние ссылки,​ а не преобразовывать
 + "​urls|u"​ => \$urls,​ #​ воспринимать параметры как интернет-ссылки
 + "​moin-file|m=s"​ => \$moin_file,​ #​ имя файла с moin разметкой,​ по умолчанию - ИмяСтраницы.moin
 + "​doku-file|d=s"​ => \$doku_file,​ #​ имя файла с doku разметкой,​ по умолчанию - ИмяСтраницы.txt или ИмяСтраницы.doku если занято
 + "​use-pagename-dir|n"​ => \$use_pagename_dir,​ #​ использовать для каждой страницы её имя в качестве working-directory
 + "​wiki-adress|w=s"​ => \$wiki_adress,​ #​ адрес вики, с которой качаем
 + "​wiki-namespace|N=s"​ => \$wiki_namespace,​ #​ неймспейс на вики, в котором искать страницы
 + "​working-directory|D=s"​ => \$working_dir,​ #​ директория для сохранения всех результатов
 + "​attachments-directory|A=s"​ => \$attachments_dir,​ #​ поддиректория для картинок и по совместительству неймспейс для вложений,​ по умолчанию - имя страницы
 + "​ru-official-wiki|r"​ => \&​ru_official_wiki,​ #​ ага, всем лень, мне тоже
 + "​help|h"​ => \$help);​ #​ ну понятно
 +
 +=head1 OPTIONS
 +
 +=over 8
 +
 +=item B<-A имя, --attachments-directory=имя>​
 +
 +Имя директории для складывания всех скачанных вложений при B<​-u>,​ а так же имя пространства имён, указываемого при конвертировании всех вложений.
 +
 +=item B<-d файл, --doku-file=файл>​
 +
 +Файл для сохранения сконвертированного в DokuWiki разметку результата. По умолчанию используется ИмяСтраницы.txt или, если такой файл уже существует,​ ИмяСтраницы.doku
 +
 +=item B<-D директория,​ --working-directory=директория>​
 +
 +Директория в которую будут сложены все результаты. По умолчанию всё складывается в текущую.
 +
 +=item B<-l, --delete-internal-links>​
 +
 +Удалять все внутренние ссылки,​ а не преобразовывать их (не работает)
 +
 +=item B<-m файл, --moin-file=файл>​
 +
 +Имя файла, в котором будет сохранён скачанный исходный код страницы в moin разметке. По умолчанию для каждой статьи используется ИмяСтатьи.moin
 +
 +=item B<-n, --use-pagename-dir>​
 +
 +Использовать для каждой страницы её имя в качестве директории для сохранения всех результатов.
 +
 +=item B<-N имя, --wiki-namespace=имя>​
 +
 +Путь до пространства имён, в котором будут искаться указанные статьи,​ от корня вики.
 +
 +=item B<-u, --urls>
 +
 +Воспринимать все агрументы как имена статей на вики по адресу,​ заданному ключами B<-N> и B<-w>
 +
 +=item B<-v, --verbose>​
 +
 +Подробный вывод (не работает)
 +
 +=item B<-w адрес, --wiki-adress>​
 +
 +Интернет-адрес вики, с которой брать страницы при указании ключа B<-u>
 +
 +=back
 +
 +=cut
 +
 +if ($help) {
 + pod2usage(-verbose => 1);
 +}
 +unless (@ARGV) {
 + pod2usage(-verbose => 1);
 +}
 +
 +if (!$use_pagename_dir && ($moin_file || $doku_file) && @ARGV > 1) {
 + warn "​При указании жёстких имён для moin или doku файлов указанный файл будет использован для всех аргументов ".
 + "и будет в итоге содержать текст только для последней обработанной страницы\n";​
 +}
 +
 +if ($urls) {
 + if ($working_dir && !$use_pagename_dir) {
 + CreateDir($working_dir);​
 + chdir $working_dir or die "​Невозможно сменить директорию на '​$working_dir'​! ($!)\n";​
 + }
 + for my $page (@ARGV) {
 + if ($page =~ m<[/ ]>) { die "​Имя страницы не может содержать символы '/',​ ' ' ($_)\n"​ }
 + $wiki_adress =~ s#/$##; # Удаляем завершающий / если он имеется
 + $wiki_namespace =~ s#/$##; # Удаляем завершающий / если он имеется
 + if ($use_pagename_dir) {
 + CreateDir($page);​
 + }
 + # Получаем исходники страницы
 + my $source_url = $wiki_namespace ? "​$wiki_adress/​$wiki_namespace/​$page?​action=raw"​ : "​$wiki_adress/​$page?​action=raw";​
 + my $moin_source = $moin_file ? $moin_file : "​$page.moin";​
 + $moin_source = catfile($page,​$moin_source) if $use_pagename_dir;​
 + GetUrlFile($source_url,​$moin_source);​
 + # Получаем вложения
 + my $atts_dir = $attachments_dir ? $attachments_dir : $page;
 + $atts_dir = catdir($page,​$atts_dir) if $use_pagename_dir;​
 + GetAttachments($moin_source,​$wiki_adress,​ $wiki_namespace ? "​$wiki_namespace/​$page"​ : $page, $atts_dir);
 + # Теперь конвертируем
 + my $doku_result = $doku_file ? $doku_file : "​$page.txt";​
 + $doku_result = catfile($page,​$doku_result) if $use_pagename_dir;​
 + unless ($attachments_dir) { ConvertMoinToDoku($moin_source,​$doku_result,​$page) }
 + else { ConvertMoinToDoku($moin_source,​$doku_result,​$attachments_dir) }
 + }
 +} else {
 + for my $file (@ARGV) {
 + # Берём имя страницы отбрасывая расширение файла
 + my ($page) = $file =~ m#​^(?:​.*/​)([^/​]+?​)(?:​\.[^\./​]+)?​$#;​
 + # Выбираем имя выходного файла
 + my $doku_result = $doku_file ? $doku_file : "​$page.txt";​
 + if ($use_pagename_dir) {
 + CreateDir($page);​
 + $doku_result = catfile($page,​$doku_result);​
 + } elsif ($working_dir) {
 + CreateDir($working_dir);​
 + $doku_result = catfile($working_dir,​$doku_result);​
 + }
 + if (-e $doku_result) { $doku_result =~ s/​\.txt$/​.doku/​ }
 + # Конвертируем
 + unless ($attachments_dir) { ConvertMoinToDoku($file,​$doku_result,​$page) }
 + else { ConvertMoinToDoku($file,​$doku_result,​$attachments_dir) }
 + }
 +}
 +
 +__END__
 +
 +=head1 DESCRIPTION
 +
 +описания пожалуй не будет - лень писать. Лучше всего использовать так:
 +moin2doku -run статья для url и
 +moin2doku -n файл для обычных файлов
 +
 +=head1 AUTHOR
 +
 +Скрипт написал Malamut just for fun :) Со всеми вопросами и пожеланиями обращайтесь:​ malamut@ubuntu.ru
 +
 +=head1 BUGS
 +
 +Некорректно работает параметр -h поскольку весь этот текст написан на русском,​ вообще что-то Pod::Usage как-то странно работает с русским текстом.
 +На данный момент некорректно обрабатываются таблицы - не учитывается выравнивание и склейка через colspan и rowspan.
 +Кроме того, не работают параметры B<-v> и B<​-l>​.
 +
 +=cut
 +</​code>​
 +
 {{tag>​DokuWiki Perl Программирование}} {{tag>​DokuWiki Perl Программирование}}