#!/usr/bin/perl
=head1 NAME

	docbook2odf - DocBook to OpenDocument XSL Transformation utils
	Copyright (C) 2006 Roman Fordinal
	http://open.comsultia.com/docbook2odf/

=head1 LICENSE

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

=cut

use strict;
#use utf8;
#use encoding 'utf-8';
#use open ':utf8', ':std';

# depends on
use Cwd;
use File::Copy;
use File::Path;
use Getopt::Long;

# variable depends
my $USE_IMAGE_MAGICK = 0;
if (eval "require Image::Magick")
{
	$USE_IMAGE_MAGICK = 1;
}

my $USE_SABLOTRON = 0;
my $USE_LIBXSLT = 0;
if (eval "require XML::Sablotron;")
{
	$USE_SABLOTRON = 1;
}
elsif (eval "require XML::LibXSLT;")
{
	$USE_LIBXSLT = 1;
}

my $USE_ZIP = 0;
if (eval "require Archive::Zip qw( :ERROR_CODES :CONSTANTS )")
{
	$USE_ZIP = 1;
}

################################################################
# INITIALIZATION
################################################################


# initial variables
our $PATH=Cwd::abs_path();
our $PATH_INSTALL="/usr/share/docbook2odf/xsl"; # not final
our $PATH_XSL = do
{
	(-e $PATH.'/../xsl/docbook.xsl') ? $PATH.'/../xsl' :
	$PATH_INSTALL
};
my ($input, $output, $output_dir);
my ($help, $quiet, $verbose, $debug);
my ($params, $force);

our $program_Date='$Date: 2007-05-19 12:55:08 +0200 (So, 19 máj 2007) $';
our $program_Rev='$Rev: 235 $';
our $program_Author='$Author: fordinal $';
our $program_Id='$Id: docbook2odf 235 2007-05-19 10:55:08Z fordinal $';

$program_Rev=~/(\d+)/;
my $program_version="0.".$1;
my $program_name="docbook2odf ".$program_version;
my $program_description="a non-interactive docbook to opendocument convertor";
my $program_usage="docbookfile [-o opendocumentfile]";# [--params]";

my $result = GetOptions
	(
		"o|output-file=s"  => \$output,
		"output-dir=s"   => \$output_dir,
		"params=s"       => \$params,
		"xsl-file=s"     => \$PATH_XSL,
		"debug"          => \$debug,
		"quiet"          => \$quiet,
		"verbose"        => \$verbose,
		"help"           => \$help,
		"f|force"        => \$force,
	);
my $input = $ARGV[0];

if ($help)
{
	print "$program_name, $program_description\n";
	print "Usage: docbook2odf $program_usage\n";
	print "\n";
	print <<"HELP";
Arguments:
  -o|--output-file    specify output opendocument filename.
  --output-dir        specify output directory.
  --params            list of params ( var=value,var2=value2 ).
  --xsl-file          use this xsl stylesheet instead.
  --debug             show debug messages.
  -q|--quiet             quiet (no output).
  -v|--verbose           verbose (extra output).
  -h|--help              print this help.
  -f|--force          overwrite existing output filename.
HELP
	exit;
}

if (!$input)
{
	print "$program_name, $program_description\n";
	print "Usage: docbook2odf $program_usage\n";
	print "Try `docbook2odf --help` for more information\n";
	exit;
}



##################################################################################
# START
##################################################################################

my $output_file=$output;
if (!$output)
{
	$output_file=$input;
	# if I run this script from commandline
	# the output filename is in current workdir
	# otherwise in directory of input filename (docbook)
	if ($ENV{'TERM'} && !$output_dir)
	{
		$output_file=~s|^.*/||;
		$output_file=$PATH.'/'.$output_file;
	}
	elsif ($output_dir)
	{
		$output_file=~s|^.*/||;
	}
	else
	{
		# output directory is in input file directory
	}
	$output_file=~s/\.(docbook|db|xml)$//;
	$output_file.=".od";
}
if ($output_dir)
{
	$output_dir=~s|/$||;
}
elsif ($output_file=~s|^(.*/)||)
{
	$output_dir=$1;
	$output_dir=~s|/$||;
}
else
{
	$output_dir=$PATH;
}

my $input_file=$input;
my $input_dir;
if ($input_file=~s|^(.*/)||)
{
	$input_dir=$1;
	$input_dir=~s|/$||;
}
else
{
	$input_dir='.';
}


# program information
if ($verbose)
{
	print "$program_name, $program_description\n";
}

# input / output files
if ($verbose)
{
	print "\n";
	print "input file:   \"$input\"\n";
	print "output file:  \"$output_dir/$output_file?\"\n";
	print "stylesheets:  \"$PATH_XSL\"\n";
}



##################################################################################
# TEMPORARY DIRECTORY
##################################################################################

# create a temporary directory
#my $TEMP=$output_dir.'/'.$output_file.'.temp';
my $TEMP='/tmp/docbook2odf-'.$$.'-'.$output_file.'.tmp';
print "Creating TEMP directory ($TEMP)\n" if $debug;
rmtree $TEMP if -e $TEMP; # delete TEMP directory if exists
mkpath $TEMP;
mkpath $TEMP.'/Pictures';
mkpath $TEMP.'/META-INF';
mkpath $TEMP.'/process';



##################################################################################
# TRANSFORMATION
##################################################################################
print "XSL transformation\n" if $debug;

# DOCBOOK -> ODF (one big xml)

# parse params;
my @params_arr;
foreach my $param(split(',',$params))
{
	foreach (split('=',$param))
	{
		push @params_arr,$_;
	}
}

my $XML_DOC = $input;
my $XSL = $PATH_XSL.'/docbook.xsl';
open (HND, '>'.$TEMP.'/process/full.xml');
print HND xml_process($XSL, $XML_DOC,@params_arr);

# MIMETYPE
open (HND, '>'.$TEMP.'/mimetype');
print HND 'application/vnd.oasis.opendocument.text';
close HND;



##################################################################################
# SPLIT
##################################################################################

$XML_DOC = $TEMP.'/process/full.xml';
$XSL = $PATH_XSL.'/odf.xsl';

# MANIFEST
open (HND, '>'.$TEMP.'/META-INF/manifest.xml');
print HND xml_process($XSL, $XML_DOC, 'part'=>'manifest');

# META
open (HND, '>'.$TEMP.'/meta.xml');
print HND xml_process($XSL, $XML_DOC, 'part'=>'meta');

# STYLES
open (HND, '>'.$TEMP.'/styles.xml');
binmode(HND);
print HND xml_process($XSL, $XML_DOC, 'part'=>'styles');

# CONTENT
my $content = xml_process($XSL, $XML_DOC, 'part'=>'content');
#utf8::encode($content);
if ($debug)
{
	open (HND, '>'.$TEMP.'/process/content.xml');
	binmode(HND);
	print HND $content;
}

print "\n" if $debug;



##################################################################################
# POSTPROCESSING
##################################################################################

do # post processing of content
{
	print "content postprocess\n" if $debug;
	# copy pictures into TEMP directory
	
	my @uris;
	my $i=1;
	while ($content=~s|<([\w:]+)([^<]*?)(xlink:href)="(.*?)"|<$1$2xlink:href=<!TMPHREF-$i!>|)
	{
		my $tag=$1;
		my $oth=$2;
		my $href=$3;
		my $uri=$4;
		
		print "-postprocessing $href\[$i]='$uri' in tag '$tag'\n" if $debug;
		
		if ($tag ne "draw:image")
		{
			$uris[$i]=$4;
			$i++;
			next;
		}
		
		my $ext=$uri;$ext=~s|^.*\.||;
		if ($uri=~/^\//)
		{
			# uri processing
		}
		else
		{
			# uri processing
			$uri=$input_dir."/".$uri;
		}
		
		my $filename=sprintf("%07d",$i);
		$uris[$i]='Pictures/'.$filename.".".$ext;
		my $dest=$TEMP.'/Pictures/'.$filename.'.'.$ext;
		print "-copy '$uri'->'$dest'\n" if $debug;
		copy($uri,$dest);
		$i++;
	}
	$content=~s|<!TMPHREF-(\d+)!>|"$uris[$1]"|g;
	
	while($content=~s|function:([\w:\-]+):\((.*?)\)|<!TMP!>|)
	{
		my $function=$1;
		my $data=$2;
		print "function='$function' data='$data'\n" if $debug;
		if ($USE_IMAGE_MAGICK)
		{
			if ($function eq "getimage-width")
			{
				my $p = new Image::Magick;
				$data=$input_dir."/".$data unless $data=~/^\//;
				$p->Read($data);
				my $width=($p->Get('columns')*0.02644)."cm";
				print "output='$width'\n" if $debug;
				$content=~s|<!TMP!>|$width|;
				next;
			}
			if ($function eq "getimage-height")
			{
				my $p = new Image::Magick;
				$data=$input_dir."/".$data unless $data=~/^\//;
				$p->Read($data);
				my $height=($p->Get('height')*0.02644)."cm";
				print "output='$height'\n" if $debug;
				$content=~s|<!TMP!>|$height|;
				next;
			}
		}
		elsif ($function eq "getimage-width" || $function eq "getimage-height")
		{
			$data=$input_dir."/".$data unless $data=~/^\//;
			my ($width, $height) = img_dimmensions($data);
			print "output='$width'\n" if $debug;
			print "output='$height'\n" if $debug;
			($function=~/width/) and $content=~s|<!TMP!>|$width|;
			($function=~/height/) and $content=~s|<!TMP!>|$height|;
			next;
		}
		#751mm=284px*2.644 196mm=74px
		$content=~s|<!TMP!>||;
	}

	# convert alternative nbsp character to ODF spaces
	$content=~s|([\xC2\x82]+)|'<text:s text:c="'.length($1).'"/>'|eg;
};
print "\n" if $debug;

open (HND, '>'.$TEMP.'/content.xml');
binmode(HND);
print HND $content;



##################################################################################
# ZIPPING
##################################################################################

# when --output-file is not defined
# then I run autodetection of document type
$output_file.=do
{
	($content=~/<office:text/) ? 't' :
	($content=~/<office:presentation/) ? 'p' :
	($content=~/<office:spread/) ? 's' :
	'm'
} unless $output;

if (-e $output_dir.'/'.$output_file && !$force)
{
	rmtree $TEMP;
	die "file $output_dir/$output_file exists\n";
}

if (!$debug)
{
	rmtree $TEMP.'/process';
}

# zipping directory
print "zipping directory '$TEMP' (PWD='$PATH')\n" if $debug;
my $zip;
if ($USE_ZIP)
{
	print "using Archive::Zip\n" if $debug;
	$zip = Archive::Zip->new();
	$zip->addTree($TEMP);
	$zip->writeToFileNamed($output_dir.'/'.$output_file);
}
else
{
	print "using zip command\n" if $debug;
	chdir($TEMP);
	my $out=`zip -rq "$output_dir/$output_file" *`;
}

print "\n" if $debug;

print "Saved $output_file\n" unless $quiet;



##################################################################################
# CLEANING
##################################################################################

if (!$debug)
{
	# delete temporary directory
	print "delete temporary directory='$TEMP' (PWD='$PATH')\n" if $debug;
	chdir '..';
	rmtree $TEMP;
}





##################################################################################
# FUNCTIONS
##################################################################################


sub img_dimmensions
{
	my $imgfile = shift;
	my $tmpdir = "/tmp/docbook2odf-$$-".int(100*rand());
	my $ext = '';
	mkdir($tmpdir);
	($imgfile =~ /\.(\w+)$/) and $ext = $1;
	
	# Copy to make sure the file name is reasonable.
	copy($imgfile,"$tmpdir/img-file.$ext");
	$imgfile = "img-file.$ext";
	
	# Convert to PNG.
	chdir($tmpdir);
	if ($ext eq "gif")
	{
		`gif2png -O $imgfile`;
	}
	elsif ($ext ne "png")
	{
		`anytopnm $imgfile 2> /dev/null| pnmtopng > img-file.png`;
	}
	$imgfile = "img-file.png";
	
	# Get the image dimmensions.
	my $data = `file $imgfile`;
	
	rmtree($tmpdir);
	
	($data =~ /PNG image data, (\d+) x (\d+)/) and return (($1*0.02644)."cm", ($2*0.02644)."cm");
	return ("3cm","3cm");
}



sub xml_process
{
	my $XSL = shift;
	my $XML_DOC = shift;
	
	print "param = @_\n" if $debug;
	
	if ($USE_SABLOTRON)
	{
		print "xslt by sablotron\n" if $debug;
		my $sab = new XML::Sablotron();
		my $situa = new XML::Sablotron::Situation();
		while (@_)
		{
			my $name = shift;
			my $val  = shift;
			$sab->addParam($situa, $name, $val);
		}
		$sab->process($situa, $XSL, $XML_DOC, 'arg:/output');
		return $sab->getResultArg('arg:/output');
	}
 	elsif ($USE_LIBXSLT)
 	{
 		print "xslt by libxslt\n" if $debug;
		my $xslt = XML::LibXSLT->new();
		my $stylesheet = $xslt->parse_stylesheet_file($XSL);
		my @params;
 		while (@_)
 		{
 			my $name = shift;
 			my $val  = shift;
			push @params, $name;
			push @params, $val;
 		}
		my $results = $stylesheet->transform_file($XML_DOC,
			XML::LibXSLT::xpath_to_string(@params));
		return $stylesheet->output_string($results);
 	}
	else
	{
		print "xslt by xsltproc\n" if $debug;
		my $PARAM = '';
		while (@_)
		{
			my $name = shift;
			my $val  = shift;
			$PARAM .= " --stringparam $name $val ";
		}
		return `xsltproc $PARAM $XSL $XML_DOC`;
	}
}


1;