1#+##############################################################################
2#
3# latex2html.pm: interface to LaTeX2HTML
4#
5#    Copyright (C) 1999, 2000, 2003, 2005, 2006, 2009, 2011, 2013
6#                  Free Software Foundation, Inc.
7#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation; either version 3 of the License,
11# or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with this program.  If not, see <http://www.gnu.org/licenses/>.
20#
21# This code was taken from the main texi2html file in 2006.
22# Certainly originally written by Olaf Bachmann.
23# Adapted from texi2html T2h_l2h.pm in 2011.
24#
25#-##############################################################################
26
27require 5.0;
28use strict;
29
30use Cwd;
31use File::Copy;
32use File::Spec;
33
34# For __(
35use Texinfo::Common;
36
37texinfo_register_handler('structure', \&l2h_process);
38texinfo_register_handler('finish', \&l2h_finish);
39
40texinfo_register_command_formatting('math', \&l2h_do_tex);
41texinfo_register_command_formatting('tex', \&l2h_do_tex);
42texinfo_register_command_formatting('displaymath', \&l2h_do_tex);
43
44# name/location of latex2html program
45set_from_init_file('L2H_L2H', 'latex2html');
46
47# If this is set the actual call to latex2html is skipped. The previously
48# generated content is reused, instead.
49# If set to 0, the cache is not used.
50# If undef the cache is used for as many tex fragments as possible
51# and for the remaining the command is run.
52set_from_init_file('L2H_SKIP', undef);
53
54# If this is set l2h uses the specified directory for temporary files. The path
55# leading to this directory may not contain a dot (i.e., a ".");
56# otherwise, l2h will fail.
57set_from_init_file('L2H_TMP', '');
58
59# If set, l2h uses the file as latex2html init file
60set_from_init_file('L2H_FILE', undef);
61
62# if this is set the intermediate files generated by texi2html in relation with
63# latex2html are cleaned (they all have the prefix <document name>_l2h_).
64set_from_init_file('L2H_CLEAN', 1);
65
66
67# latex2html conversions consist of 2 stages:
68# 1) l2h_process
69#    to latex: Put "latex" code into a latex file
70#                    (l2h_to_latex, l2h_finish_to_latex)
71#    to html: Use latex2html to generate corresponding html code and images
72#                    (l2h_to_html)
73#    from html: Extract generated code and images from latex2html run
74#                    (l2h_init_from_html)
75# 2) l2h_do_tex called each time a @tex or @math command is encountered
76#               in the output tree.
77
78# init l2h defaults for files and names
79
80my ($l2h_name, $l2h_latex_file, $l2h_cache_file, $l2h_html_file, $l2h_prefix);
81
82# holds the status of latex2html operations. If 0 it means that there was
83# an error
84my $status = 0;
85
86my $debug;
87my $verbose;
88my $docu_rdir;
89my $docu_volume;
90my $docu_directories;
91my $docu_name;
92
93my %commands_counters;
94
95# init_from_html
96my $extract_error_count;
97my $invalid_counter_count;
98
99# change_image_file_names
100my %l2h_img;            # associate src file to destination file
101                        # such that files are not copied twice
102my $image_count;
103
104# do_tex
105my $html_output_count = 0;   # html text outputed in html result file
106
107##########################
108#
109# First stage: Generation of Latex file
110# Initialize with: init
111# Add content with: l2h_to_latex ($text) --> HTML placeholder comment
112# Finish with: finish_to_latex
113#
114
115my $l2h_latex_preamble = <<EOT;
116% This document was automatically generated by the l2h extenstion of texi2html
117% DO NOT EDIT !!!
118\\documentclass{article}
119\\usepackage{html}
120\\begin{document}
121EOT
122
123my $l2h_latex_closing = <<EOT;
124\\end{document}
125EOT
126
127my %l2h_to_latex = ();         # associate a latex text with the index in the
128                               # html result array.
129my @l2h_to_latex = ();         # array used to associate the index with
130                               # the original latex text.
131my $latex_count = 0;           # number of latex texts really stored
132my $latex_converted_count = 0; # number of latex texts passed through latex2html
133my $to_latex_count = 0;        # total number of latex texts processed
134my $cached_count = 0;          # number of cached latex texts
135my %l2h_cache = ();            # the cache hash. Associate latex text with
136                               # html from the previous run
137my @l2h_from_html;             # array of resulting html
138
139my %global_count = ();         # associate a command name and the
140                               # corresponding counter to the index in the
141                               # html result array
142
143# set $status to 1, if l2h could be initalized properly, to 0 otherwise
144sub l2h_process($$)
145{
146  my $self = shift;
147  my $document_root = shift;
148  %l2h_to_latex = ();         # associate a latex text with the index in the
149                              # html result array.
150  @l2h_to_latex = ();         # array used to associate the index with
151                              # the original latex text.
152  $latex_count = 0;           # number of latex texts really stored
153  $latex_converted_count = 0; # number of latex texts passed through latex2html
154  $to_latex_count = 0;        # total number of latex texts processed
155  $cached_count = 0;          # number of cached latex texts
156  %l2h_cache = ();            # the cache hash. Associate latex text with
157                              # html from the previous run
158  @l2h_from_html = ();        # array of resulting html
159
160  %global_count = ();         # associate a command name and the
161                              # corresponding counter to the index in the
162                              # html result array
163  %commands_counters = ();
164  $extract_error_count = 0;
165  $invalid_counter_count = 0;
166  %l2h_img = ();       # associate src file to destination file
167                       # such that files are not copied twice
168  $image_count = 1;
169
170  $html_output_count = 0;   # html text outputed in html result file
171  $status = 0;
172  return if (defined($self->get_conf('OUTFILE'))
173        and $Texinfo::Common::null_device_file{$self->get_conf('OUTFILE')});
174
175
176  $docu_name = $self->{'document_name'};
177  $docu_rdir = $self->{'destination_directory'};
178  $docu_rdir = '' if (!defined($docu_rdir));
179  my $no_file;
180  ($docu_volume, $docu_directories, $no_file)
181      = File::Spec->splitpath($docu_rdir, 1);
182  $l2h_name =  "${docu_name}_l2h";
183  $l2h_latex_file = File::Spec->catpath($docu_volume, $docu_directories,
184                                        "${l2h_name}.tex");
185  $l2h_cache_file = File::Spec->catpath($docu_volume, $docu_directories,
186                                        "${docu_name}-l2h_cache.pm");
187  # destination dir -- generated images are put there, should be the same
188  # as dir of enclosing html document --
189  $l2h_html_file = File::Spec->catpath($docu_volume, $docu_directories,
190                                       "${l2h_name}.html");
191  $l2h_prefix = "${l2h_name}_";
192  $debug = $self->get_conf('DEBUG');
193  $verbose = $self->get_conf('VERBOSE');
194
195  unless ($self->get_conf('L2H_SKIP')) {
196    unless (open(L2H_LATEX, ">$l2h_latex_file")) {
197      $self->document_error(sprintf(__(
198              "l2h: could not open latex file %s for writing: %s"),
199                                    $l2h_latex_file, $!));
200      $status = 0;
201      return;
202    }
203    warn "# l2h: use ${l2h_latex_file} as latex file\n" if ($verbose);
204    print L2H_LATEX $l2h_latex_preamble;
205  }
206  # open the database that holds cached text
207  l2h_init_cache($self) if (!defined($self->get_conf('L2H_SKIP'))
208                   or $self->get_conf('L2H_SKIP'));
209
210  my @replaced_commands = ('tex', 'math', 'displaymath');
211  my $collected_commands = Texinfo::Common::collect_commands_in_tree($document_root, \@replaced_commands);
212  foreach my $command (@replaced_commands) {
213    ## we rely on @tex and @math being recorded as 'global commands'
214    #if ($self->{'extra'}->{$command}) {
215    if (scalar(@{$collected_commands->{$command}}) > 0) {
216      my $counter = 0;
217      #foreach my $root (@{$self->{'extra'}->{$command}}) {
218      foreach my $root (@{$collected_commands->{$command}}) {
219        $counter++;
220        my $tree;
221        if ($command eq 'math') {
222          $tree = $root->{'args'}->[0];
223        } else {
224          $tree = {'contents' => [@{$root->{'contents'}}]};
225          if ($tree->{'contents'}->[0]
226              and $tree->{'contents'}->[0]->{'type'}
227              and $tree->{'contents'}->[0]->{'type'} eq 'empty_line_after_command') {
228            shift @{$tree->{'contents'}};
229          }
230          if ($tree->{'contents'}->[-1]->{'cmdname'}
231              and $tree->{'contents'}->[-1]->{'cmdname'} eq 'end') {
232            pop @{$tree->{'contents'}};
233          }
234        }
235        my $text = Texinfo::Convert::Texinfo::convert($tree);
236        #$text .= "\n" if ($command eq 'tex');
237        l2h_to_latex($self, $command, $text, $counter);
238        $commands_counters{$root} = $counter;
239      }
240    }
241  }
242  $status = l2h_finish_to_latex($self);
243  if ($status) {
244    $status = l2h_to_html($self);
245  }
246  if ($status) {
247    $status = l2h_init_from_html($self);
248  }
249  # FIXME use $status?  That is abort when something goes wrong on the
250  # latex2html front?
251  return 1;
252}
253
254
255# print text (2nd arg) into latex file (if not already there nor in cache)
256# which can be later on replaced by the latex2html generated text.
257#
258sub l2h_to_latex($$$$)
259{
260  my $self = shift;
261  my $command = shift;
262  my $text = shift;
263  my $counter = shift;
264
265  if ($command eq 'tex') {
266    $text .= ' ';
267  } elsif ($command eq 'math') {
268    $text = "\$".$text."\$";
269  } elsif ($command eq 'displaymath') {
270    $text = "\$\$".$text."\$\$";
271  }
272  $to_latex_count++;
273  $text =~ s/(\s*)$//;
274  # try whether we have text already on things to do
275  my $count = $l2h_to_latex{$text};
276  unless ($count) {
277    $latex_count++;
278    $count = $latex_count;
279    # try whether we can get it from cache
280    my $cached_text = l2h_from_cache($text);
281    if (defined($cached_text)) {
282      $cached_count++;
283      # put the cached result in the html result array
284      $l2h_from_html[$count] = $cached_text;
285    } else {
286      $latex_converted_count++;
287      unless ($self->get_conf('L2H_SKIP')) {
288        print L2H_LATEX "\\begin{rawhtml}\n\n";
289        print L2H_LATEX "<!-- l2h_begin $l2h_name $count -->\n";
290        print L2H_LATEX "\\end{rawhtml}\n";
291
292        print L2H_LATEX "$text\n";
293
294        print L2H_LATEX "\\begin{rawhtml}\n";
295        print L2H_LATEX "<!-- l2h_end $l2h_name $count -->\n\n";
296        print L2H_LATEX "\\end{rawhtml}\n";
297      }
298    }
299    $l2h_to_latex[$count] = $text;
300    $l2h_to_latex{$text} = $count;
301  }
302  $global_count{"${command}_$counter"} = $count;
303  return 1;
304}
305
306# print closing into latex file and close it
307sub l2h_finish_to_latex($)
308{
309  my $self = shift;
310  my $reused = $to_latex_count - $latex_converted_count - $cached_count;
311  unless ($self->get_conf('L2H_SKIP')) {
312    print L2H_LATEX $l2h_latex_closing;
313    close (L2H_LATEX);
314  }
315  warn "# l2h: finished to latex ($cached_count cached, $reused reused, $latex_converted_count to process)\n" if ($verbose);
316  unless ($latex_count) {
317    # no @tex nor @math
318    l2h_finish($self);
319    return 0;
320  }
321  return 1;
322}
323
324###################################
325# Use latex2html to generate corresponding html code and images
326#
327# to_html([$l2h_latex_file, [$l2h_html_dir]]):
328#   Call latex2html on $l2h_latex_file
329#   Put images (prefixed with $l2h_name."_") and html file(s) in $l2h_html_dir
330#   Return 1, on success
331#          0, otherwise
332#
333sub l2h_to_html($)
334{
335  my $self = shift;
336  my ($call, $dotbug);
337  # when there are no tex constructs to convert (happens in case everything
338  # comes from the cache), there is no latex2html run
339  if ($self->get_conf('L2H_SKIP') or ($latex_converted_count == 0)) {
340     warn "# l2h: skipping latex2html run\n" if ($verbose);
341     return 1;
342  }
343  # Check for dot in directory where dvips will work
344  if ($self->get_conf('L2H_TMP')) {
345    if ($self->get_conf('L2H_TMP') =~ /\./) {
346      $self->document_warn(__("l2h: L2H_TMP directory contains a dot"));
347      $dotbug = 1;
348    }
349  } else {
350    if (cwd() =~ /\./) {
351      $self->document_warn(__("l2h: current directory contains a dot"));
352      $dotbug = 1;
353    }
354  }
355  return 0 if ($dotbug);
356
357  $call = $self->get_conf('L2H_L2H');
358  # use init file, if specified
359  my $init_file = $self->get_conf('L2H_FILE');
360  $call = $call . " -init_file " . $init_file
361    if (defined($init_file) and $init_file ne ''
362        and -f $init_file and -r $init_file);
363  # set output dir
364  $call .=  (($docu_rdir ne '') ? " -dir $docu_rdir" : " -no_subdir");
365  # use l2h_tmp, if specified
366  $call .= " -tmp ".$self->get_conf('L2H_TMP')
367    if (defined($self->get_conf('L2H_TMP'))
368        and $self->get_conf('L2H_TMP') ne '');
369  # use a given html version if specified
370  $call .= " -html_version ".$self->get_conf('L2H_HTML_VERSION')
371    if (defined($self->get_conf('L2H_HTML_VERSION'))
372        and $self->get_conf('L2H_HTML_VERSION') ne '');
373  # options we want to be sure of
374  $call .= " -address 0 -info 0 -split 0 -no_navigation -no_auto_link";
375  $call .= " -prefix $l2h_prefix $l2h_latex_file";
376
377  warn "# l2h: executing '$call'\n" if ($verbose);
378  if (system($call)) {
379    $self->document_error(sprintf(__("l2h: command did not succeed: %s"),
380                                  $call));
381    return 0;
382  } else  {
383     warn "# l2h: latex2html finished successfully\n" if ($verbose);
384     return 1;
385  }
386}
387
388##########################
389# Third stage: Extract generated contents from latex2html run
390# Initialize with: init_from_html
391#   open $l2h_html_file for reading
392#   reads in contents into array indexed by numbers
393#   return 1,  on success -- 0, otherwise
394# Finish with: finish
395#   closes $l2h_html_dir/$l2h_name.".$docu_ext"
396
397
398# the images generated by latex2html have names like ${docu_name}_l2h_img?.png
399# they are copied to ${docu_name}_?.png, and html is changed accordingly.
400
401# FIXME is it really necessary to bother doing that? Looks like an unneeded
402# complication to me (pertusus, 2009), and it could go bad if there is some
403# SRC="(.*?)" in the text (though the regexp could be made more specific).
404
405# %l2h_img;            # associate src file to destination file
406                        # such that files are not copied twice
407sub l2h_change_image_file_names($$)
408{
409  my $self = shift;
410  my $content = shift;
411  my @images = ($content =~ /SRC="(.*?)"/g);
412  my ($src, $dest);
413
414  for $src (@images) {
415    $dest = $l2h_img{$src};
416    unless ($dest) {
417      my $ext = '';
418      if ($src =~ /.*\.(.*)$/ and (!defined($self->get_conf('EXTENSION'))
419                                    or $1 ne $self->get_conf('EXTENSION'))) {
420        $ext = ".$1";
421      } else {
422        # A warning when the image extension is the same than the
423        # document extension. copying the file could result in
424        # overwriting an output file (almost surely if the default
425        # texi2html file names are used).
426        $self->document_warn(sprintf(__(
427                            "l2h: image has invalid extension: %s"), $src));
428        next;
429      }
430      while (-e File::Spec->catpath($docu_volume, $docu_directories,
431                                    "${docu_name}_${image_count}$ext")) {
432        $image_count++;
433      }
434      my $file_src
435        = File::Spec->catpath($docu_volume, $docu_directories, $src);
436      $dest = "${docu_name}_${image_count}$ext";
437      my $file_dest
438        = File::Spec->catpath($docu_volume, $docu_directories, $dest);
439      if ($debug) {
440        copy($file_src, $file_dest);
441      } else {
442        if (!rename($file_src, $file_dest)) {
443          $self->document_warn(sprintf(__("l2h: rename %s as %s failed: %s"),
444                                       $file_src, $file_dest, $!));
445        }
446      }
447      $l2h_img{$src} = $dest;
448    }
449  $content =~ s/SRC="$src"/SRC="$dest"/g;
450  }
451  return $content;
452}
453
454sub l2h_init_from_html($)
455{
456  my $self = shift;
457  # when there are no tex constructs to convert (happens in case everything
458  # comes from the cache), the html file that was generated by previous
459  # latex2html runs isn't reused.
460  if ($latex_converted_count == 0) {
461    return 1;
462  }
463
464  if (! open(L2H_HTML, "<$l2h_html_file")) {
465    $self->document_warn(sprintf(__("l2h: could not open %s: %s"),
466                                 $l2h_html_file, $!));
467    return 0;
468  }
469  warn "# l2h: use $l2h_html_file as html file\n" if ($verbose);
470
471  my $html_converted_count = 0;   # number of html resulting texts
472                                  # retrieved in the file
473
474  my ($count, $h_line);
475  while ($h_line = <L2H_HTML>) {
476    if ($h_line =~ /!-- l2h_begin $l2h_name ([0-9]+) --/) {
477      $count = $1;
478      my $h_content = '';
479      my $h_end_found = 0;
480      while ($h_line = <L2H_HTML>) {
481        if ($h_line =~ /!-- l2h_end $l2h_name $count --/) {
482          $h_end_found = 1;
483          chomp $h_content;
484          chomp $h_content;
485          $html_converted_count++;
486          # transform image file names and copy image files
487          $h_content = l2h_change_image_file_names($self, $h_content);
488          # store result in the html result array
489          $l2h_from_html[$count] = $h_content;
490          # also add the result in cache hash
491          $l2h_cache{$l2h_to_latex[$count]} = $h_content;
492          last;
493        }
494        $h_content = $h_content.$h_line;
495      }
496      unless ($h_end_found) {
497        # couldn't found the closing comment. Should be a bug.
498        $self->document_warn(sprintf(__("latex2html.pm: end of \@%s item %d not found"),
499                                      $l2h_name, $count));
500        close(L2H_HTML);
501        return 0;
502      }
503    }
504  }
505
506  # Not the same number of converted elements and retrieved elements
507  if ($latex_converted_count != $html_converted_count) {
508    $self->document_warn(sprintf(__(
509      "latex2html.pm: processing produced %d items in HTML; expected %d, the number of items found in the document"),
510                          $html_converted_count, $latex_converted_count));
511  }
512
513  warn "# l2h: Got $html_converted_count of $latex_count html contents\n"
514    if ($verbose);
515
516  close(L2H_HTML);
517  return 1;
518}
519
520# $html_output_count = 0;   # html text outputed in html result file
521
522# called each time a construct handled by latex2html is encountered, should
523# output the corresponding html
524sub l2h_do_tex($$)
525{
526  my $self = shift;
527  my $cmdname = shift;;
528  my $command = shift;
529  my $content = shift;
530
531  my $counter = $commands_counters{$command};
532  return '' unless ($status);
533  my $count = $global_count{"${cmdname}_$counter"};
534  ################################## begin debug section (incorrect counts)
535  if (!defined($count)) {
536    # counter is undefined
537    $invalid_counter_count++;
538    $self->document_warn(
539           sprintf(__("l2h: could not determine the fragment %d for \@%s"),
540                   $counter, $cmdname));
541    return ("<!-- l2h: ". __LINE__ . " undef count for ${cmdname}_$counter -->")
542      if ($debug);
543    return '';
544  } elsif(($count <= 0) or ($count > $latex_count)) {
545    # counter out of range
546    $invalid_counter_count++;
547    $self->_bug_message("l2h: request of $count out of range [0,$latex_count]");
548    return ("<!-- l2h: ". __LINE__ . " out of range count $count -->")
549      if ($debug);
550    return '';
551  }
552  ################################## end debug section (incorrect counts)
553
554  # this seems to be a valid counter
555  my $result = '';
556  $result = "<!-- l2h_begin $l2h_name $count -->" if ($debug);
557  if (defined($l2h_from_html[$count])) {
558    $html_output_count++;
559    $result .= $l2h_from_html[$count];
560    $result .= "\n" if ($cmdname eq 'tex');
561  } else {
562    # if the result is not in @l2h_from_html, there is an error somewhere.
563    $extract_error_count++;
564    $self->document_warn(sprintf(__(
565       "l2h: could not extract the fragment %d for \@%s with output counter %d from HTML"),
566                   $counter, $cmdname, $count));
567    # try simple (ordinary) substitution (without l2h)
568    $result .= "<!-- l2h: ". __LINE__ . " use default -->" if ($debug);
569    $result .= &{$self->default_commands_conversion($cmdname)}($self,
570                                                 $cmdname, $command, $content);
571  }
572  $result .= "<!-- l2h_end $l2h_name $count -->" if ($debug);
573  return $result;
574}
575
576# store results in the cache and remove temporary files.
577sub l2h_finish($)
578{
579  my $self = shift;
580  return 1 unless($status);
581
582  if ($verbose) {
583    if ($extract_error_count + $invalid_counter_count) {
584      warn "# l2h: finished from html ($extract_error_count extract and $invalid_counter_count invalid counter errors)\n";
585    } else {
586      warn "# l2h: finished from html (no error)\n";
587    }
588    if ($html_output_count != $latex_converted_count) {
589      # this may happen if @-commands are collected at some places
590      # but @-command at those places are not expanded later. For
591      # example @math on @multitable lines.
592      warn "# l2h: $html_output_count html outputed for $latex_converted_count converted\n";
593    }
594  }
595  l2h_store_cache($self);
596  if ($self->get_conf('L2H_CLEAN')) {
597    warn "# l2h: removing temporary files generated by l2h extension\n"
598     if ($verbose);
599    my $quoted_l2h_name = quotemeta($l2h_name);
600    my $dir = $docu_rdir;
601    $dir = File::Spec->curdir() if ($dir eq '');
602    if (opendir (DIR, $dir)) {
603      foreach my $file (grep { /^$quoted_l2h_name/ } readdir(DIR)) {
604      # FIXME error condition not checked
605        unlink File::Spec->catpath($docu_volume, $docu_directories, $file);
606      }
607    }
608  }
609  warn "# l2h: Finished\n" if $verbose;
610  return 1;
611}
612
613##############################
614# stuff for l2h caching
615#
616# FIXME it is clear that l2h stuff takes very long compared with texi2any
617# which is already quite long. However this also adds some complexity
618
619# I tried doing this with a dbm data base, but it did not store all
620# keys/values. Hence, I did as latex2html does it
621sub l2h_init_cache($)
622{
623  my $self = shift;
624  if (-r $l2h_cache_file) {
625    my $rdo = do "$l2h_cache_file";
626    $self->document_error(sprintf(__("l2h: could not load %s: %s"),
627                                  $l2h_cache_file, $@))
628      unless ($rdo);
629  }
630}
631
632# store all the text obtained through latex2html
633sub l2h_store_cache($)
634{
635  my $self = shift;
636  return unless $latex_count;
637  my ($key, $value);
638  unless (open(FH, ">$l2h_cache_file")) {
639    $self->document_error(sprintf(__("l2h: could not open %s for writing: %s"),
640                                  $l2h_cache_file, $!));
641    return;
642  }
643  foreach my $key(sort(keys(%l2h_cache))) {
644  #while (($key, $value) = each %l2h_cache) {
645    my $value = $l2h_cache{$key};
646    # escape stuff
647    $key =~ s|/|\\/|g;
648    $key =~ s|\\\\/|\\/|g;
649    # weird, a \ at the end of the key results in an error
650    # maybe this also broke the dbm database stuff
651    $key =~ s|\\$|\\\\|;
652    $value =~ s/\|/\\\|/go;
653    $value =~ s/\\\\\|/\\\|/go;
654    $value =~ s|\\\\|\\\\\\\\|g;
655    print FH "\n\$l2h_cache_key = q/$key/;\n";
656    print FH "\$l2h_cache{\$l2h_cache_key} = q|$value|;\n";
657  }
658  print FH "1;";
659  close (FH);
660}
661
662# return cached html, if it exists for text, and if all pictures
663# are there, as well
664sub l2h_from_cache($)
665{
666  my $text = shift;
667  my $cached = $l2h_cache{$text};
668  if (defined($cached)) {
669    while ($cached =~ m/SRC="(.*?)"/g) {
670      unless (-e File::Spec->catpath($docu_volume, $docu_directories, $1)) {
671        return undef;
672      }
673    }
674    return $cached;
675  }
676  return undef;
677}
678
6791;
680