Это старая версия документа.


Официальная Wiki Ubuntu сидит на движке MoinMoin, мы же используем DokuWiki. Озадачившись вопросами конвертации синтаксиса нашёл лишь это, что только приблизительно позволяет преобразовать основные конструкции. Поэтому, ради спортивного интереса, набросал скриптик на перле (замените только в нём <!/code> на </code> в одном месте, иначе местный парсер ломается):

#!/usr/bin/perl -w
 
# Usage: ./convert.pl moin_file doku_file [namespace]
# namespace - for all links to images ({{namespace:image.png}})
 
use 5.010;			# We need it
 
# ----------------------------------------------------------------------------------------------- #
#             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  --------------------------------------- #
 
ConvertMoinToDoku @ARGV;

Используется это дело так:

./convert.pl moin_file doku_file [namespace]

namespace - это пространство имён, которое надо пихать в ссылки на картинки, если его не указать будет использоваться '.'.

К сожалению, оно не совсем корректно работает с таблицами (не учитывает склейку и выравнивание, первое является достаточно критичным), возможно потом допишу. Если вы вдруг будете это использовать - напишите мне о найденных ошибках или хотя бы оставьте тут комментарий.

TODO:

Улучшенная версия

Я ж ленивый, поэтому я написал 'обёртку' к представленной выше функции конвертации так, что бы её было удобно вызывать с различными аргументами и, кроме всего прочего, этот вариант умеет скачивать страницы и вложения из интернета. Функция конвертации здесь та же, просто она сопровождается удобным способом её вызывать.

Описание см. в POD в коде. Не забудьте заменить <!/code> на </code>.

#!/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