blob: 27aa5595986e8ca293c924dce23824f78e85942b [file] [log] [blame]
#+##############################################################################
#
# latex2html.pm: interface to LaTeX2HTML
#
# Copyright (C) 1999, 2000, 2003, 2005, 2006, 2009, 2011, 2013
# Free Software Foundation, Inc.
#
# 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 3 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.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# This code was taken from the main texi2html file in 2006.
# Certainly originally written by Olaf Bachmann.
# Adapted from texi2html T2h_l2h.pm in 2011.
#
#-##############################################################################
require 5.0;
use strict;
use Cwd;
use File::Copy;
use File::Spec;
my $global_cmds = get_conf('GLOBAL_COMMANDS');
if (!defined($global_cmds)) {
set_from_init_file('GLOBAL_COMMANDS', []);
$global_cmds = get_conf('GLOBAL_COMMANDS');
}
push @$global_cmds, ('math', 'tex');
texinfo_register_handler('structure', \&l2h_process);
texinfo_register_handler('finish', \&l2h_finish);
texinfo_register_command_formatting('math', \&l2h_do_tex);
texinfo_register_command_formatting('tex', \&l2h_do_tex);
# name/location of latex2html program
set_from_init_file('L2H_L2H', 'latex2html');
# If this is set the actual call to latex2html is skipped. The previously
# generated content is reused, instead.
# If set to 0, the cache is not used.
# If undef the cache is used for as many tex fragments as possible
# and for the remaining the command is run.
set_from_init_file('L2H_SKIP', undef);
# If this is set l2h uses the specified directory for temporary files. The path
# leading to this directory may not contain a dot (i.e., a ".");
# otherwise, l2h will fail.
set_from_init_file('L2H_TMP', '');
# If set, l2h uses the file as latex2html init file
set_from_init_file('L2H_FILE', undef);
# if this is set the intermediate files generated by texi2html in relation with
# latex2html are cleaned (they all have the prefix <document name>_l2h_).
set_from_init_file('L2H_CLEAN', 1);
# latex2html conversions consist of 2 stages:
# 1) l2h_process
# to latex: Put "latex" code into a latex file
# (l2h_to_latex, l2h_finish_to_latex)
# to html: Use latex2html to generate corresponding html code and images
# (l2h_to_html)
# from html: Extract generated code and images from latex2html run
# (l2h_init_from_html)
# 2) l2h_do_tex called each time a @tex or @math command is encountered
# in the output tree.
# init l2h defaults for files and names
my ($l2h_name, $l2h_latex_file, $l2h_cache_file, $l2h_html_file, $l2h_prefix);
# holds the status of latex2html operations. If 0 it means that there was
# an error
my $status = 0;
my $debug;
my $verbose;
my $docu_rdir;
my $docu_volume;
my $docu_directories;
my $docu_name;
my %commands_counters;
# init_from_html
my $extract_error_count;
my $invalid_counter_count;
# change_image_file_names
my %l2h_img; # associate src file to destination file
# such that files are not copied twice
my $image_count;
# do_tex
my $html_output_count = 0; # html text outputed in html result file
##########################
#
# First stage: Generation of Latex file
# Initialize with: init
# Add content with: l2h_to_latex ($text) --> HTML placeholder comment
# Finish with: finish_to_latex
#
my $l2h_latex_preamble = <<EOT;
% This document was automatically generated by the l2h extenstion of texi2html
% DO NOT EDIT !!!
\\documentclass{article}
\\usepackage{html}
\\begin{document}
EOT
my $l2h_latex_closing = <<EOT;
\\end{document}
EOT
my %l2h_to_latex = (); # associate a latex text with the index in the
# html result array.
my @l2h_to_latex = (); # array used to associate the index with
# the original latex text.
my $latex_count = 0; # number of latex texts really stored
my $latex_converted_count = 0; # number of latex texts passed through latex2html
my $to_latex_count = 0; # total number of latex texts processed
my $cached_count = 0; # number of cached latex texts
my %l2h_cache = (); # the cache hash. Associate latex text with
# html from the previous run
my @l2h_from_html; # array of resulting html
my %global_count = (); # associate a command name and the
# corresponding counter to the index in the
# html result array
# set $status to 1, if l2h could be initalized properly, to 0 otherwise
sub l2h_process($)
{
my $self = shift;
%l2h_to_latex = (); # associate a latex text with the index in the
# html result array.
@l2h_to_latex = (); # array used to associate the index with
# the original latex text.
$latex_count = 0; # number of latex texts really stored
$latex_converted_count = 0; # number of latex texts passed through latex2html
$to_latex_count = 0; # total number of latex texts processed
$cached_count = 0; # number of cached latex texts
%l2h_cache = (); # the cache hash. Associate latex text with
# html from the previous run
@l2h_from_html = (); # array of resulting html
%global_count = (); # associate a command name and the
# corresponding counter to the index in the
# html result array
%commands_counters = ();
$extract_error_count = 0;
$invalid_counter_count = 0;
%l2h_img = (); # associate src file to destination file
# such that files are not copied twice
$image_count = 1;
$html_output_count = 0; # html text outputed in html result file
$status = 0;
return if (defined($self->get_conf('OUTFILE'))
and $Texinfo::Common::null_device_file{$self->get_conf('OUTFILE')});
$docu_name = $self->{'document_name'};
$docu_rdir = $self->{'destination_directory'};
$docu_rdir = '' if (!defined($docu_rdir));
my $no_file;
($docu_volume, $docu_directories, $no_file)
= File::Spec->splitpath($docu_rdir, 1);
$l2h_name = "${docu_name}_l2h";
$l2h_latex_file = File::Spec->catpath($docu_volume, $docu_directories,
"${l2h_name}.tex");
$l2h_cache_file = File::Spec->catpath($docu_volume, $docu_directories,
"${docu_name}-l2h_cache.pm");
# destination dir -- generated images are put there, should be the same
# as dir of enclosing html document --
$l2h_html_file = File::Spec->catpath($docu_volume, $docu_directories,
"${l2h_name}.html");
$l2h_prefix = "${l2h_name}_";
$debug = $self->get_conf('DEBUG');
$verbose = $self->get_conf('VERBOSE');
unless ($self->get_conf('L2H_SKIP')) {
unless (open(L2H_LATEX, ">$l2h_latex_file")) {
$self->document_error(sprintf($self->__(
"l2h: could not open latex file %s for writing: %s"),
$l2h_latex_file, $!));
$status = 0;
return;
}
warn "# l2h: use ${l2h_latex_file} as latex file\n" if ($verbose);
print L2H_LATEX $l2h_latex_preamble;
}
# open the database that holds cached text
l2h_init_cache($self) if (!defined($self->get_conf('L2H_SKIP'))
or $self->get_conf('L2H_SKIP'));
foreach my $command ('tex', 'math') {
if ($self->{'extra'}->{$command}) {
my $counter = 0;
foreach my $root (@{$self->{'extra'}->{$command}}) {
$counter++;
my $tree;
if ($command eq 'math') {
$tree = $root->{'args'}->[0];
} else {
$tree = {'contents' => [@{$root->{'contents'}}]};
if ($tree->{'contents'}->[0]
and $tree->{'contents'}->[0]->{'type'}
and $tree->{'contents'}->[0]->{'type'} eq 'empty_line_after_command') {
shift @{$tree->{'contents'}};
}
if ($tree->{'contents'}->[-1]->{'cmdname'}
and $tree->{'contents'}->[-1]->{'cmdname'} eq 'end') {
pop @{$tree->{'contents'}};
}
}
my $text = Texinfo::Convert::Texinfo::convert($tree);
#$text .= "\n" if ($command eq 'tex');
l2h_to_latex($self, $command, $text, $counter);
$commands_counters{$root} = $counter;
}
}
}
$status = l2h_finish_to_latex($self);
if ($status) {
$status = l2h_to_html($self);
}
if ($status) {
$status = l2h_init_from_html($self);
}
# FIXME use $status? That is abort when something goes wrong on the
# latex2html front?
return 1;
}
# print text (2nd arg) into latex file (if not already there nor in cache)
# which can be later on replaced by the latex2html generated text.
#
sub l2h_to_latex($$$$)
{
my $self = shift;
my $command = shift;
my $text = shift;
my $counter = shift;
if ($command eq 'tex') {
$text .= ' ';
} elsif ($command eq 'math') {
$text = "\$".$text."\$";
}
$to_latex_count++;
$text =~ s/(\s*)$//;
# try whether we have text already on things to do
my $count = $l2h_to_latex{$text};
unless ($count) {
$latex_count++;
$count = $latex_count;
# try whether we can get it from cache
my $cached_text = l2h_from_cache($text);
if (defined($cached_text)) {
$cached_count++;
# put the cached result in the html result array
$l2h_from_html[$count] = $cached_text;
} else {
$latex_converted_count++;
unless ($self->get_conf('L2H_SKIP')) {
print L2H_LATEX "\\begin{rawhtml}\n\n";
print L2H_LATEX "<!-- l2h_begin $l2h_name $count -->\n";
print L2H_LATEX "\\end{rawhtml}\n";
print L2H_LATEX "$text\n";
print L2H_LATEX "\\begin{rawhtml}\n";
print L2H_LATEX "<!-- l2h_end $l2h_name $count -->\n\n";
print L2H_LATEX "\\end{rawhtml}\n";
}
}
$l2h_to_latex[$count] = $text;
$l2h_to_latex{$text} = $count;
}
$global_count{"${command}_$counter"} = $count;
return 1;
}
# print closing into latex file and close it
sub l2h_finish_to_latex($)
{
my $self = shift;
my $reused = $to_latex_count - $latex_converted_count - $cached_count;
unless ($self->get_conf('L2H_SKIP')) {
print L2H_LATEX $l2h_latex_closing;
close (L2H_LATEX);
}
warn "# l2h: finished to latex ($cached_count cached, $reused reused, $latex_converted_count to process)\n" if ($verbose);
unless ($latex_count) {
# no @tex nor @math
l2h_finish($self);
return 0;
}
return 1;
}
###################################
# Use latex2html to generate corresponding html code and images
#
# to_html([$l2h_latex_file, [$l2h_html_dir]]):
# Call latex2html on $l2h_latex_file
# Put images (prefixed with $l2h_name."_") and html file(s) in $l2h_html_dir
# Return 1, on success
# 0, otherwise
#
sub l2h_to_html($)
{
my $self = shift;
my ($call, $dotbug);
# when there are no tex constructs to convert (happens in case everything
# comes from the cache), there is no latex2html run
if ($self->get_conf('L2H_SKIP') or ($latex_converted_count == 0)) {
warn "# l2h: skipping latex2html run\n" if ($verbose);
return 1;
}
# Check for dot in directory where dvips will work
if ($self->get_conf('L2H_TMP')) {
if ($self->get_conf('L2H_TMP') =~ /\./) {
$self->document_warn($self->__("l2h: L2H_TMP directory contains a dot"));
$dotbug = 1;
}
} else {
if (cwd() =~ /\./) {
$self->document_warn($self->__("l2h: current directory contains a dot"));
$dotbug = 1;
}
}
return 0 if ($dotbug);
$call = $self->get_conf('L2H_L2H');
# use init file, if specified
my $init_file = $self->get_conf('L2H_FILE');
$call = $call . " -init_file " . $init_file
if (defined($init_file) and $init_file ne ''
and -f $init_file and -r $init_file);
# set output dir
$call .= (($docu_rdir ne '') ? " -dir $docu_rdir" : " -no_subdir");
# use l2h_tmp, if specified
$call .= " -tmp ".$self->get_conf('L2H_TMP')
if (defined($self->get_conf('L2H_TMP'))
and $self->get_conf('L2H_TMP') ne '');
# use a given html version if specified
$call .= " -html_version ".$self->get_conf('L2H_HTML_VERSION')
if (defined($self->get_conf('L2H_HTML_VERSION'))
and $self->get_conf('L2H_HTML_VERSION') ne '');
# options we want to be sure of
$call .= " -address 0 -info 0 -split 0 -no_navigation -no_auto_link";
$call .= " -prefix $l2h_prefix $l2h_latex_file";
warn "# l2h: executing '$call'\n" if ($verbose);
if (system($call)) {
$self->document_error(sprintf($self->__("l2h: command did not succeed: %s"),
$call));
return 0;
} else {
warn "# l2h: latex2html finished successfully\n" if ($verbose);
return 1;
}
}
##########################
# Third stage: Extract generated contents from latex2html run
# Initialize with: init_from_html
# open $l2h_html_file for reading
# reads in contents into array indexed by numbers
# return 1, on success -- 0, otherwise
# Finish with: finish
# closes $l2h_html_dir/$l2h_name.".$docu_ext"
# the images generated by latex2html have names like ${docu_name}_l2h_img?.png
# they are copied to ${docu_name}_?.png, and html is changed accordingly.
# FIXME is it really necessary to bother doing that? Looks like an unneeded
# complication to me (pertusus, 2009), and it could go bad if there is some
# SRC="(.*?)" in the text (though the regexp could be made more specific).
# %l2h_img; # associate src file to destination file
# such that files are not copied twice
sub l2h_change_image_file_names($$)
{
my $self = shift;
my $content = shift;
my @images = ($content =~ /SRC="(.*?)"/g);
my ($src, $dest);
for $src (@images) {
$dest = $l2h_img{$src};
unless ($dest) {
my $ext = '';
if ($src =~ /.*\.(.*)$/ and (!defined($self->get_conf('EXTENSION'))
or $1 ne $self->get_conf('EXTENSION'))) {
$ext = ".$1";
} else {
# A warning when the image extension is the same than the
# document extension. copying the file could result in
# overwriting an output file (almost surely if the default
# texi2html file names are used).
$self->document_warn(sprintf($self->__(
"l2h: image has invalid extension: %s"), $src));
next;
}
while (-e File::Spec->catpath($docu_volume, $docu_directories,
"${docu_name}_${image_count}$ext")) {
$image_count++;
}
my $file_src
= File::Spec->catpath($docu_volume, $docu_directories, $src);
$dest = "${docu_name}_${image_count}$ext";
my $file_dest
= File::Spec->catpath($docu_volume, $docu_directories, $dest);
if ($debug) {
copy($file_src, $file_dest);
} else {
if (!rename($file_src, $file_dest)) {
$self->document_warn(sprintf($self->__("l2h: rename %s as %s failed: %s"),
$file_src, $file_dest, $!));
}
}
$l2h_img{$src} = $dest;
}
$content =~ s/SRC="$src"/SRC="$dest"/g;
}
return $content;
}
sub l2h_init_from_html($)
{
my $self = shift;
# when there are no tex constructs to convert (happens in case everything
# comes from the cache), the html file that was generated by previous
# latex2html runs isn't reused.
if ($latex_converted_count == 0) {
return 1;
}
if (! open(L2H_HTML, "<$l2h_html_file")) {
$self->document_warn(sprintf($self->__("l2h: could not open %s: %s"),
$l2h_html_file, $!));
return 0;
}
warn "# l2h: use $l2h_html_file as html file\n" if ($verbose);
my $html_converted_count = 0; # number of html resulting texts
# retrieved in the file
my ($count, $h_line);
while ($h_line = <L2H_HTML>) {
if ($h_line =~ /!-- l2h_begin $l2h_name ([0-9]+) --/) {
$count = $1;
my $h_content = '';
my $h_end_found = 0;
while ($h_line = <L2H_HTML>) {
if ($h_line =~ /!-- l2h_end $l2h_name $count --/) {
$h_end_found = 1;
chomp $h_content;
chomp $h_content;
$html_converted_count++;
# transform image file names and copy image files
$h_content = l2h_change_image_file_names($self, $h_content);
# store result in the html result array
$l2h_from_html[$count] = $h_content;
# also add the result in cache hash
$l2h_cache{$l2h_to_latex[$count]} = $h_content;
last;
}
$h_content = $h_content.$h_line;
}
unless ($h_end_found) {
# couldn't found the closing comment. Should be a bug.
$self->document_warn(sprintf(__("latex2html.pm: end of \@%s item %d not found"),
$l2h_name, $count));
close(L2H_HTML);
return 0;
}
}
}
# Not the same number of converted elements and retrieved elements
if ($latex_converted_count != $html_converted_count) {
$self->document_warn(sprintf($self->__(
"latex2html.pm: processing produced %d items in HTML; expected %d, the number of items found in the document"),
$html_converted_count, $latex_converted_count));
}
warn "# l2h: Got $html_converted_count of $latex_count html contents\n"
if ($verbose);
close(L2H_HTML);
return 1;
}
# $html_output_count = 0; # html text outputed in html result file
# called each time a construct handled by latex2html is encountered, should
# output the corresponding html
sub l2h_do_tex($$)
{
my $self = shift;
my $cmdname = shift;;
my $command = shift;
my $content = shift;
my $counter = $commands_counters{$command};
return '' unless ($status);
my $count = $global_count{"${cmdname}_$counter"};
################################## begin debug section (incorrect counts)
if (!defined($count)) {
# counter is undefined
$invalid_counter_count++;
$self->document_warn(
sprintf($self->__("l2h: could not determine the fragment %d for \@%s",
$counter, $cmdname)));
return ("<!-- l2h: ". __LINE__ . " undef count for ${cmdname}_$counter -->")
if ($debug);
return '';
} elsif(($count <= 0) or ($count > $latex_count)) {
# counter out of range
$invalid_counter_count++;
$self->_bug_message("l2h: request of $count out of range [0,$latex_count]");
return ("<!-- l2h: ". __LINE__ . " out of range count $count -->")
if ($debug);
return '';
}
################################## end debug section (incorrect counts)
# this seems to be a valid counter
my $result = '';
$result = "<!-- l2h_begin $l2h_name $count -->" if ($debug);
if (defined($l2h_from_html[$count])) {
$html_output_count++;
$result .= $l2h_from_html[$count];
$result .= "\n" if ($cmdname eq 'tex');
} else {
# if the result is not in @l2h_from_html, there is an error somewhere.
$extract_error_count++;
$self->document_warn(sprintf($self->__(
"l2h: could not extract the fragment %d for \@%s with output counter %d from HTML"),
$counter, $cmdname, $count));
# try simple (ordinary) substitution (without l2h)
$result .= "<!-- l2h: ". __LINE__ . " use default -->" if ($debug);
$result .= &{$self->default_commands_conversion($cmdname)}($self,
$cmdname, $command, $content);
}
$result .= "<!-- l2h_end $l2h_name $count -->" if ($debug);
return $result;
}
# store results in the cache and remove temporary files.
sub l2h_finish($)
{
my $self = shift;
return 1 unless($status);
if ($verbose) {
if ($extract_error_count + $invalid_counter_count) {
warn "# l2h: finished from html ($extract_error_count extract and $invalid_counter_count invalid counter errors)\n";
} else {
warn "# l2h: finished from html (no error)\n";
}
if ($html_output_count != $latex_converted_count) {
# this may happen if @-commands are collected at some places
# but @-command at those places are not expanded later. For
# example @math on @multitable lines.
warn "# l2h: $html_output_count html outputed for $latex_converted_count converted\n";
}
}
l2h_store_cache($self);
if ($self->get_conf('L2H_CLEAN')) {
warn "# l2h: removing temporary files generated by l2h extension\n"
if ($verbose);
my $quoted_l2h_name = quotemeta($l2h_name);
my $dir = $docu_rdir;
$dir = File::Spec->curdir() if ($dir eq '');
if (opendir (DIR, $dir)) {
foreach my $file (grep { /^$quoted_l2h_name/ } readdir(DIR)) {
# FIXME error condition not checked
unlink File::Spec->catpath($docu_volume, $docu_directories, $file);
}
}
}
warn "# l2h: Finished\n" if $verbose;
return 1;
}
##############################
# stuff for l2h caching
#
# FIXME it is clear that l2h stuff takes very long compared with texi2any
# which is already quite long. However this also adds some complexity
# I tried doing this with a dbm data base, but it did not store all
# keys/values. Hence, I did as latex2html does it
sub l2h_init_cache($)
{
my $self = shift;
if (-r $l2h_cache_file) {
my $rdo = do "$l2h_cache_file";
$self->document_error(sprintf($self->__("l2h: could not load %s: %s"),
$l2h_cache_file, $@))
unless ($rdo);
}
}
# store all the text obtained through latex2html
sub l2h_store_cache($)
{
my $self = shift;
return unless $latex_count;
my ($key, $value);
unless (open(FH, ">$l2h_cache_file")) {
$self->document_error(sprintf($self->__("l2h: could not open %s for writing: %s"),
$l2h_cache_file, $!));
return;
}
foreach my $key(sort(keys(%l2h_cache))) {
#while (($key, $value) = each %l2h_cache) {
my $value = $l2h_cache{$key};
# escape stuff
$key =~ s|/|\\/|g;
$key =~ s|\\\\/|\\/|g;
# weird, a \ at the end of the key results in an error
# maybe this also broke the dbm database stuff
$key =~ s|\\$|\\\\|;
$value =~ s/\|/\\\|/go;
$value =~ s/\\\\\|/\\\|/go;
$value =~ s|\\\\|\\\\\\\\|g;
print FH "\n\$l2h_cache_key = q/$key/;\n";
print FH "\$l2h_cache{\$l2h_cache_key} = q|$value|;\n";
}
print FH "1;";
close (FH);
}
# return cached html, if it exists for text, and if all pictures
# are there, as well
sub l2h_from_cache($)
{
my $text = shift;
my $cached = $l2h_cache{$text};
if (defined($cached)) {
while ($cached =~ m/SRC="(.*?)"/g) {
unless (-e File::Spec->catpath($docu_volume, $docu_directories, $1)) {
return undef;
}
}
return $cached;
}
return undef;
}
1;