#!/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$';
our $program_Rev='$Rev$';
our $program_Author='$Author$';
our $program_Id='$Id$';
$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|doc|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=|)
{
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||"$uris[$1]"|g;
while($content=~s|function:([\w:\-]+):\((.*?)\)||)
{
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||$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||$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||$width|;
($function=~/height/) and $content=~s||$height|;
next;
}
#751mm=284px*2.644 196mm=74px
$content=~s|||;
}
# convert alternative nbsp character to ODF spaces
$content=~s|(\xC2\x82)|''|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=~/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") || print "can't copy file\n";
$imgfile = "$tmpdir/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 > $tmpdir/img-file.png`;
}
$imgfile = "$tmpdir/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 ";
}
$XML_DOC=~s| |\\ |g;
# my $cmd="xsltproc --xinclude $PARAM $XSL $XML_DOC";
my $cmd="xsltproc --xinclude $PARAM $XSL $XML_DOC";
print "$cmd\n" if $debug;
return `$cmd`;
}
}
1;