#!/usr/bin/perl -w

#
# Wiki-To-LaTeX script version 0.2.1, 2010/02/26, (c) Tuetschek 2008-2010
#
# Usage: w2l.pl file1 [file2 ...]
#

#
# TODO
#

# - prepinace: tema clanku v nadpise
# - prepinace: defaultni wikipedie
# - odkazy: spravne kodovani diakritiky (urlencode (?))
# - obrazky: odhad velikosti
# - cross-odkazy [[#...]] vytvaret reference na kazdy nadpis a pak
#   je odkazovat (?)


#
# CONSTANTS
#

# standard header for LaTeX output
$header = <<EOF
\\documentclass[a4paper,10pt,notitlepage]{article}
\\frenchspacing
\\usepackage{a4}
\\usepackage[czech]{babel}
\\usepackage[cp1250]{inputenc}
\\usepackage{amsmath,amssymb,amsthm,hyperref,wrapfig,graphicx}
\\oddsidemargin -1cm
\\evensidemargin -1cm
\\textwidth 18cm
\\textheight 25cm
\\topmargin -2.3cm
\\begin{document}
EOF
;

# standard LaTeX footer
$footer = "\\end{document}" ;

# sirka obrazku (vnejsi a vnitrni)
$graphicsInnerWidth = "68mm";
$graphicsOuterWidth = "70mm";


# special chars to be re-written outside of math mode
%specCharsNormal = (
    '&ndash;' => '--',
    '&lt;' => '\textless ',
    '&gt;' => '\textgreater ',
    '&amp;' => '\&',
    '<' => '\textless ',
    '>' => '\textgreater ',
    '%' => '\% ',
    '\|' => '\textbar ',
    '\$' => '\$',
    '_' => '\_',
    '#' => '\#',
    '~' => '\~{}',
    '\^' => '\^',
);

# special chars to be re-written inside math mode
%specCharsMath = (
    '&ndash;' => '--',
    '&lt;' => '<',
    '&gt;' => '>',
    '&amp;' => '\&',
    '%' => '\% ',
    '\$' => '\$',
    '#' => '\#',
    '~' => '\sim',
);

# prefixes for wikis that may be referred to
%wikiprefixes = (
    'wen\:' => 'http://en.wikipedia.org/wiki/',
    'wcs\:' => 'http://cs.wikipedia.org/wiki/',
    '' => 'http://wiki.matfyz.cz/wiki/', # default
);


#
# FUNCTIONS
#


# sets-up the depth of a given list by printing \\begin{} and \\end{}
# setListDepth( $listName, $oldDepth, $newDepth )
sub setListDepth {

    my $listName = shift;
    my $oldDepth = shift;
    my $newDepth = shift;

    if ( $newDepth < $oldDepth ){
	
	while( $oldDepth > $newDepth ){
	    print "\\end{", $listName, "}\n";
	    $oldDepth--;
	}
    }
    elsif ($newDepth > $oldDepth){

	while($newDepth > $oldDepth){
	    print "\\begin{", $listName, "}\n";

	    if ($newDepth > $oldDepth + 1){
		print "\\item\n";
	    }
	    $newDepth--;
	}
    }
}


# handles all lists (gets current list depths and returns new ones as well
# as the resulting string, prints all \\begin{}'s and \\end{}'s needed
# ($str, $listDepth, $enumDepth) = handleLists($str, $listDepth, $enumDepth)
sub handleLists {
    
    my ($str, $listDepth, $enumDepth) = @_;

    # close all lists that do not continue on this line
    if ( $str !~ m/^\s*[\*\#]/ ){

	setListDepth( "itemize", $listDepth, 0 );
	setListDepth( "enumerate", $enumDepth, 0 );
	$enumDepth = 0;
	$listDepth = 0;
    }
    # open/continue an unnumbered list
    elsif ( $str =~ m/^\s*\*/g ){
	
	my $newDepth = pos($str);
	
	# find out the current list depth
	pos($str) = 0;
	$str =~ m/^\s*\**/g;	
	$newDepth = pos($str) - $newDepth + 1;

	# set up list depths by printing \\begin{} and \\end{}
	setListDepth( "enumerate", $enumDepth, 0 );
	# separate directly connected lists of different types, since LaTeX does
	# not allow them to follow one another directly
	print "\\ \n" if ($enumDepth); 
	setListDepth( "itemize", $listDepth, $newDepth );

	# set up variables and the resulting string
	$listDepth = $newDepth;
	$enumDepth = 0;
	$str = "\\item " . substr($str, pos($str));
    }
    # open/continue a numbered list (analogous to previous)
    elsif( $str =~ m/^\s*\#/g ){

	my $newDepth = pos($str); 
	
	pos($str) = 0;
	$str =~ m/^\s*\#*/g;	
	$newDepth = pos($str) - $newDepth + 1;

	setListDepth( "itemize", $listDepth, 0 );
	print "\\ \n" if ($listDepth);
	setListDepth( "enumerate", $enumDepth, $newDepth );

	$enumDepth = $newDepth; 
	$listDepth = 0;
	$str = "\\item " . substr($str, pos($str));
    }

    # return results
    return ($str, $listDepth, $enumDepth);
}


# converts all captions and style settings to LaTeX
# $str = handleCaptionsAndStyles($str)
sub handleCaptionsAndStyles {

    my $str = shift;

    # captions (several levels)
    $str =~ s/^====(.*)====$/\\subsubsection{$1}/;
    $str =~ s/^===(.*)===$/\\subsection{$1}/;
    $str =~ s/^==(.*)==$/\\section{$1}/;

    # bold, italics, teletype
    $str =~ s/'''(([^']+'?)*)('''|$)/\\textbf{$1}/g;
    $str =~ s/''(([^']+'?)*)(''|$)/\\textit{$1}/g;
    $str =~ s/<b>(.*?)<\/b>/\\textbf{$1}/g;
    $str =~ s/<i>(.*?)<\/i>/\\textit{$1}/g;
    $str =~ s/<tt>(.*?)<\/tt>/\\texttt{$1}/g;

    return $str;    
}


# converts all links to wiki, outer links and image links
# $str = handleLinks($str)
sub handleLinks {

    my $str = shift;

    # images (need to handle them first)
    # (image file names are broken and subsequently corrected in handleMathAndSpecChars)
    $str =~ s/\[\[Image:([^\]\|]*)\|([^\]\|]*\|)?([^\]]*)\]\]/\\begin{wrapfigure}{R}{$graphicsOuterWidth}\\begin{center}\\includegraphics[width=$graphicsInnerWidth]{$1}\\end{center}\\caption{$3}\\end{wrapfigure}/g;

    # all other wikipedias links
    foreach my $key (sort { length($b) <=> length($a) } (keys %wikiprefixes)){

	$str =~ s/\[\[$key([^\]\|]*)\]\]/\\href{$wikiprefixes{$key}$1}{$key$1}/g;
	$str =~ s/\[\[$key([^\]\|]*)\|([^\]]*)\]\]/\\href{$wikiprefixes{$key}$1}{$2}/g;
    }

    # links to outer world
    $str =~ s/\[(http:\/\/|ftp:\/\/|https:\/\/)(\S*) ([^\]]*)\]/\\href{$1$2}{$3}/g;
    $str =~ s/\[(http:\/\/|ftp:\/\/|https:\/\/)(\S*)\]/\\url{$1$2}/g;

    return $str;
}


# maps all the characters from charMapping for the given string to LaTeX versions
# $str = mapCharacters($str,\%charMapping)
sub mapCharacters {

    my $str = shift;
    my %mapping = %{shift()};

    foreach my $specChar (keys %mapping){

	$str =~ s/$specChar/$mapping{$specChar}/g;
    }

    return $str;
}


# converts all math begins and ends, maps all special characters to their 
# LaTeX versions. this presumes that the wiki code is correct and does not 
# begin in math mode
# $str = handleMathAndSpecChars($str)
sub handleMathAndSpecChars {

    my $str = shift;
    my @maths = split(/<\/?math>/, $str);
    my $mathMode = 0;

    $str = "";
    
    foreach my $snippet (@maths){

	$snippet = ($mathMode ? mapCharacters($snippet,\%specCharsMath) 
		: mapCharacters($snippet, \%specCharsNormal));

	$str .= ($str eq "" ? $snippet : "\$" . $snippet);
	$mathMode = !$mathMode; # toggle math mode
    }

    # correct image links ("\_" -> "_")
    while($str =~ m/\\includegraphics/g){
	
	my $fileBeg, my $fileEnd;
	my $fileLink;

	$str =~ m/{/g;
	$fileBeg = pos($str);
	$str =~ m/}/g;
	$fileEnd = pos($str) - 1;
	$fileLink = substr($str, $fileBeg, $fileEnd - $fileBeg);
	$fileLink =~ s/\\_/_/g;

	$str = substr($str, 0, $fileBeg) . $fileLink . substr($str, $fileEnd);
	pos($str) = $fileBeg + length($fileLink) + 1;
    }
    
    return $str;
}


# main function: processes one file, prints the LaTeX output to STDOUT
# processFile(\<FILE>)
sub processFile {

    my $file = shift;
    my $listDepth = 0; # not-numbered list depth
    my $enumDepth = 0; # numbered list depth
    

    while(<$file>){


	# sets-up list depths (itemize / enumerate)
	($_, $listDepth,$enumDepth) = handleLists($_, $listDepth, $enumDepth);
	

	# convert captions, bold and italics
	$_ = handleCaptionsAndStyles($_);

	# convert image placings and links
	$_ = handleLinks($_);


	# convert math begin/end and all special characters in and
	# outside of math mode	
	$_ = handleMathAndSpecChars($_);

	print;
    }

    # close any lists (possibly) ending on the last line
    handleLists("", $listDepth, $enumDepth) if ($listDepth || $enumDepth);
}


#
# MAIN
#


# vytiskne hlavicku latexoveho souboru
print $header;

# argumenty: soubory se vstupem! 
if ( @ARGV ){
    while( @ARGV ){
	
	open( INPUT, $ARGV[0] );
	processFile( \*INPUT );
	shift( @ARGV );
    }
}
else {
    processFile( \*STDIN );
}

# vytiskne paticku latexoveho souboru
print $footer;
