source: main/trunk/greenstone2/perllib/plugins/ConvertBinaryFile.pm@ 32205

Last change on this file since 32205 was 32205, checked in by ak19, 6 years ago

First set of commits to do with implementing the new 'paged_html' output option of PDFPlugin that uses using xpdftools' new pdftohtml. So far tested only on Linux (64 bit), but things work there so I'm optimistically committing the changes since they work. 2. Committing the pre-built Linux binaries of XPDFtools for both 32 and 64 bit built by the XPDF group. 2. To use the correct bitness variant of xpdftools, setup.bash now exports the BITNESS env var, consulted by gsConvert.pl. 3. All the perl code changes to do with using xpdf tools' pdftohtml to generate paged_html and feed it in the desired form into GS(3): gsConvert.pl, PDFPlugin.pm and its parent ConvertBinaryPFile.pm have been modified to make it all work. xpdftools' pdftohtml generates a folder containing an html file and a screenshot for each page in a PDF (as well as an index.html linking to each page's html). However, we want a single html file that contains each individual 'page' html's content in a div, and need to do some further HTML style, attribute and structure modifications to massage the xpdftool output to what we want for GS. In order to parse and manipulate the HTML 'DOM' to do this, we're using the Mojo::DOM package that Dr Bainbridge found and which he's compiled up. Mojo::DOM is therefore also committed in this revision. Some further changes and some display fixes are required, but need to check with the others about that.

  • Property svn:keywords set to Author Date Id Revision
File size: 20.0 KB
Line 
1###########################################################################
2#
3# ConvertBinaryFile.pm -- plugin that facilitates conversion of binary files
4# through gsConvert.pl
5#
6# A component of the Greenstone digital library software
7# from the New Zealand Digital Library Project at the
8# University of Waikato, New Zealand.
9#
10# Copyright (C) 1999 New Zealand Digital Library Project
11#
12# This program is free software; you can redistribute it and/or modify
13# it under the terms of the GNU General Public License as published by
14# the Free Software Foundation; either version 2 of the License, or
15# (at your option) any later version.
16#
17# This program is distributed in the hope that it will be useful,
18# but WITHOUT ANY WARRANTY; without even the implied warranty of
19# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20# GNU General Public License for more details.
21#
22# You should have received a copy of the GNU General Public License
23# along with this program; if not, write to the Free Software
24# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25#
26###########################################################################
27
28# This plugin is inherited by such plugins as WordPlugin, PowerPointPlugin,
29# PostScriptPlugin,
30# RTFPlugin and PDFPlugin. It facilitates the conversion of these document types
31# to either HTML, Text or a series of images. It works by dynamically loading
32# an appropriate secondary plugin (HTMLPlug, StructuredHTMLPlug,
33# PagedImagePlugin or TextPlugin) based on the plugin argument 'convert_to'.
34
35package ConvertBinaryFile;
36
37use AutoExtractMetadata;
38use ghtml;
39use HTMLPlugin;
40use TextPlugin;
41use PagedImagePlugin;
42
43use strict;
44no strict 'refs'; # allow filehandles to be variables and viceversa
45no strict 'subs';
46use util;
47use FileUtils;
48
49
50sub BEGIN {
51 @ConvertBinaryFile::ISA = ('AutoExtractMetadata');
52}
53
54my $convert_to_list =
55 [ { 'name' => "auto",
56 'desc' => "{ConvertBinaryFile.convert_to.auto}" },
57 { 'name' => "html",
58 'desc' => "{ConvertBinaryFile.convert_to.html}" },
59 { 'name' => "text",
60 'desc' => "{ConvertBinaryFile.convert_to.text}" }
61 ];
62
63my $arguments =
64 [ { 'name' => "convert_to",
65 'desc' => "{ConvertBinaryFile.convert_to}",
66 'type' => "enum",
67 'reqd' => "yes",
68 'list' => $convert_to_list,
69 'deft' => "auto" },
70 { 'name' => "keep_original_filename",
71 'desc' => "{ConvertBinaryFile.keep_original_filename}",
72 'type' => "flag" },
73 { 'name' => "title_sub",
74 'desc' => "{HTMLPlugin.title_sub}",
75 'type' => "string",
76 #'type' => "regexp",
77 'deft' => "" },
78 { 'name' => "apply_fribidi",
79 'desc' => "{ConvertBinaryFile.apply_fribidi}",
80 'type' => "flag",
81 'reqd' => "no" },
82 { 'name' => "use_strings",
83 'desc' => "{ConvertBinaryFile.use_strings}",
84 'type' => "flag",
85 'reqd' => "no" },
86 ];
87
88my $options = { 'name' => "ConvertBinaryFile",
89 'desc' => "{ConvertBinaryFile.desc}",
90 'abstract' => "yes",
91 'inherits' => "yes",
92 'args' => $arguments };
93
94
95sub load_secondary_plugins
96{
97 my $self = shift (@_);
98 my ($class,$input_args,$hashArgOptLists) = @_;
99
100 my @convert_to_list = split(",",$self->{'convert_to_plugin'});
101 my $secondary_plugins = {};
102 # find the plugin
103
104 foreach my $convert_to (@convert_to_list) {
105 # load in "convert_to" plugin package
106 my $plugin_class = $convert_to;
107 my $plugin_package = $plugin_class.".pm";
108
109 my $colplugname = undef;
110 if (defined $ENV{'GSDLCOLLECTDIR'}) {
111 $colplugname = &FileUtils::filenameConcatenate($ENV{'GSDLCOLLECTDIR'},
112 "perllib","plugins",
113 $plugin_package);
114 }
115
116 my $mainplugname = &FileUtils::filenameConcatenate($ENV{'GSDLHOME'},
117 "perllib","plugins",
118 $plugin_package);
119
120 if ((defined $colplugname) && (-e $colplugname)) { require $colplugname;}
121 elsif (-e $mainplugname) { require $mainplugname; }
122 else {
123 &gsprintf(STDERR, "{plugin.could_not_find_plugin}\n",
124 $plugin_class);
125 die "\n";
126 }
127
128 # call its constructor with extra options that we've worked out!
129 my $arglist = $input_args->{$plugin_class};
130
131 my ($secondary_plugin);
132 eval("\$secondary_plugin = new $plugin_class([],\$arglist)");
133 die "$@" if $@;
134 $secondary_plugins->{$plugin_class} = $secondary_plugin;
135 }
136 $self->{'secondary_plugins'} = $secondary_plugins;
137}
138
139sub new {
140 my ($class) = shift (@_);
141 my ($pluginlist,$inputargs,$hashArgOptLists) = @_;
142 push(@$pluginlist, $class);
143 my $classPluginName = (defined $pluginlist->[0]) ? $pluginlist->[0] : $class;
144 push(@{$hashArgOptLists->{"ArgList"}},@{$arguments});
145 push(@{$hashArgOptLists->{"OptList"}},$options);
146
147 my $self = new AutoExtractMetadata($pluginlist, $inputargs, $hashArgOptLists);
148
149 return bless $self, $class;
150}
151
152# should be called by subclasses after checking and setting
153# $self->{'convert_to'}
154sub set_standard_convert_settings {
155 my $self =shift (@_);
156
157 my $convert_to = $self->{'convert_to'};
158 if ($convert_to eq "auto") {
159 $convert_to = "html";
160 $self->{'convert_to'} = "html";
161 }
162
163 if ($convert_to =~ /^html/ || $convert_to eq "paged_html") { # may be html or html_multi, or paged_html with the new Xpdf's own pdftohtml
164 $self->{'convert_to_plugin'} = "HTMLPlugin";
165 $self->{'convert_to_ext'} = "html";
166 } elsif ($convert_to eq "text") {
167 $self->{'convert_to_plugin'} = "TextPlugin";
168 $self->{'convert_to_ext'} = "txt";
169 } elsif ($convert_to eq "structuredhtml") {
170 $self->{'convert_to_plugin'} = "StructuredHTMLPlugin";
171 $self->{'convert_to_ext'} = "html";
172 } elsif ($convert_to =~ /^pagedimg/) {
173 $self->{'convert_to_plugin'} = "PagedImagePlugin";
174 my ($convert_to_ext) = $convert_to =~ /pagedimg\_(jpg|gif|png)/i;
175 $convert_to_ext = 'jpg' unless defined $convert_to_ext;
176 $self->{'convert_to_ext'} = $convert_to_ext;
177 }
178}
179sub init {
180 my $self = shift (@_);
181 my ($verbosity, $outhandle, $failhandle) = @_;
182
183 $self->SUPER::init($verbosity,$outhandle,$failhandle);
184
185 my $secondary_plugins = $self->{'secondary_plugins'};
186
187 foreach my $plug_name (keys %$secondary_plugins) {
188 my $plugin = $secondary_plugins->{$plug_name};
189 $plugin->init($verbosity,$outhandle,$failhandle);
190 }
191}
192
193sub deinit {
194 # called only once, after all plugin passes have been done
195
196 my ($self) = @_;
197
198 my $secondary_plugins = $self->{'secondary_plugins'};
199
200 foreach my $plug_name (keys %$secondary_plugins) {
201 my $plugin = $secondary_plugins->{$plug_name};
202 $plugin->deinit();
203 }
204}
205
206sub convert_post_process
207{
208 # by default do no post processing
209 return;
210}
211
212
213# Run conversion utility on the input file.
214#
215# The conversion takes place in a collection specific 'tmp' directory so
216# that we don't accidentally damage the input.
217#
218# The desired output type is indicated by $output_ext. This is usually
219# something like "html" or "word", but can be "best" (or the empty string)
220# to indicate that the conversion utility should do the best it can.
221sub tmp_area_convert_file {
222 my $self = shift (@_);
223 my ($output_ext, $input_filename, $textref) = @_;
224
225 my $outhandle = $self->{'outhandle'};
226 my $convert_to = $self->{'convert_to'};
227 my $failhandle = $self->{'failhandle'};
228 my $convert_to_ext = $self->{'convert_to_ext'};
229
230
231 my $upgraded_input_filename = &util::upgrade_if_dos_filename($input_filename);
232
233 # derive tmp filename from input filename
234 my ($tailname, $dirname, $suffix)
235 = &File::Basename::fileparse($upgraded_input_filename, "\\.[^\\.]+\$");
236
237 # softlink to collection tmp dir
238 my $tmp_dirname = &util::get_timestamped_tmp_folder();
239 if (defined $tmp_dirname) {
240 $self->{'tmp_dir'} = $tmp_dirname;
241 } else {
242 $tmp_dirname = $dirname;
243 }
244
245# # convert to utf-8 otherwise we have problems with the doc.xml file later on
246# my $utf8_tailname = (&unicode::check_is_utf8($tailname)) ? $tailname : $self->filepath_to_utf8($tailname);
247
248 # make sure filename to be used can be stored OK in a UTF-8 compliant doc.xml file
249 my $utf8_tailname = &unicode::raw_filename_to_utf8_url_encoded($tailname);
250
251
252 # URLEncode this since htmls with images where the html filename is utf8 don't seem
253 # to work on Windows (IE or Firefox), as browsers are looking for filesystem-encoded
254 # files on the filesystem.
255 $utf8_tailname = &util::rename_file($utf8_tailname, $self->{'file_rename_method'}, "without_suffix");
256
257 my $lc_suffix = lc($suffix);
258 my $tmp_filename = &FileUtils::filenameConcatenate($tmp_dirname, "$utf8_tailname$lc_suffix");
259
260 # If gsdl is remote, we're given relative path to input file, of the form import/utf8_tailname.suffix
261 # But we can't softlink to relative paths. Therefore, we need to ensure that
262 # the input_filename is the absolute path, see http://perldoc.perl.org/File/Spec.html
263 my $ensure_path_absolute = 1; # true
264 &FileUtils::softLink($input_filename, $tmp_filename, $ensure_path_absolute);
265
266 my $output_filename = $self->run_conversion_command($tmp_dirname, $tmp_filename,
267 $utf8_tailname, $lc_suffix, $tailname, $suffix);
268
269 return $output_filename;
270}
271
272# The latter half of tmp_area_convert_file: runs the conversion command and returns the output file name
273# Split from tmp_area_convert_file because UnknownConverterPlugin can then inherit all of
274# tmp_area_convert_file and only needs to override this part:
275sub run_conversion_command {
276 my $self = shift (@_);
277 my ($tmp_dirname, $tmp_filename, $utf8_tailname, $lc_suffix, $tailname, $suffix) = @_;
278
279 my $outhandle = $self->{'outhandle'};
280 my $convert_to = $self->{'convert_to'};
281 my $failhandle = $self->{'failhandle'};
282
283 my $verbosity = $self->{'verbosity'};
284 if ($verbosity > 0) {
285 print $outhandle "Converting $tailname$suffix to $convert_to format\n";
286 }
287
288 my $errlog = &FileUtils::filenameConcatenate($tmp_dirname, "err.log");
289
290 # Execute the conversion command and get the type of the result,
291 # making sure the converter gives us the appropriate output type
292 my $output_type=$self->{'convert_to'};
293# if ($convert_to =~ m/PagedImage/i) {
294# $output_type = lc($convert_to)."_".lc($convert_to_ext);
295# } else {
296# $output_type = lc($convert_to);
297# }
298
299 my $cmd = "\"".&util::get_perl_exec()."\" -S gsConvert.pl -verbose $verbosity ";
300 if (defined $self->{'convert_options'}) {
301 $cmd .= $self->{'convert_options'} . " ";
302 }
303 if ($self->{'use_strings'}) {
304 $cmd .= "-use_strings ";
305 }
306 $cmd .= "-errlog \"$errlog\" -output $output_type \"$tmp_filename\"";
307 print STDERR "calling cmd $cmd\n";
308 $output_type = `$cmd`;
309
310 # remove symbolic link to original file
311 &FileUtils::removeFiles($tmp_filename);
312
313 # Check STDERR here
314 chomp $output_type;
315 if ($output_type eq "fail") {
316 print $outhandle "Could not convert $tailname$suffix to $convert_to format\n";
317 print $failhandle "$tailname$suffix: " . ref($self) . " failed to convert to $convert_to\n";
318 # The following meant that if a conversion failed, the document would be counted twice - do we need it for anything?
319 #$self->{'num_not_processed'} ++;
320 if (-s "$errlog") {
321 open(ERRLOG, "$errlog");
322 while (<ERRLOG>) {
323 print $outhandle "$_";
324 }
325 print $outhandle "\n";
326 close ERRLOG;
327 }
328 &FileUtils::removeFiles("$errlog") if (-e "$errlog");
329 return "";
330 }
331
332 # store the *actual* output type and return the output filename
333 # it's possible we requested conversion to html, but only to text succeeded
334 #$self->{'convert_to_ext'} = $output_type;
335 if ($output_type =~ /html/i) {
336 $self->{'converted_to'} = "HTML";
337 } elsif ($output_type =~ /te?xt/i) {
338 $self->{'converted_to'} = "Text";
339 } elsif ($output_type =~ /item/i){
340 $self->{'converted_to'} = "PagedImage";
341 }
342
343 my $output_filename = $tmp_filename;
344 if ($output_type =~ /item/i) {
345 # running under windows
346 if ($ENV{'GSDLOS'} =~ /^windows$/i) {
347 $output_filename = $tmp_dirname . "\\$utf8_tailname\\" . $utf8_tailname . ".$output_type";
348 } else {
349 $output_filename = $tmp_dirname . "\/$utf8_tailname\/" . $utf8_tailname . ".$output_type";
350 }
351 } elsif ($output_type eq "paged_html") {
352 $output_filename =~ s/$lc_suffix$/.html/;
353 } else {
354 $output_filename =~ s/$lc_suffix$/.$output_type/;
355 }
356
357 return $output_filename;
358}
359
360
361# Override BasPlug read_into_doc_obj - we need to call secondary plugin stuff
362sub read_into_doc_obj {
363 my $self = shift (@_);
364 my ($pluginfo, $base_dir, $file, $block_hash, $metadata, $processor, $maxdocs, $total_count, $gli) = @_;
365
366 my $outhandle = $self->{'outhandle'};
367
368 my ($filename_full_path, $filename_no_path) = &util::get_full_filenames($base_dir, $file);
369
370 my $output_ext = $self->{'convert_to_ext'};
371 my $conv_filename = "";
372 $conv_filename = $self->tmp_area_convert_file($output_ext, $filename_full_path);
373
374 if ("$conv_filename" eq "") {return -1;} # had an error, will be passed down pipeline
375
376 # We used to return -1 here if $conv_filename didn't exist at this stage
377 # However, for "paged_html" convert_to mode, the converted HTML file $conv_filename
378 # will only be created from conversion products *after* convert_post_process() returns
379 my $output_type=$self->{'convert_to'};
380 if ($output_type ne "paged_html" && ! -e "$conv_filename") {return -1;}
381 $self->{'conv_filename'} = $conv_filename;
382 $self->convert_post_process($conv_filename);
383 if ($output_type eq "paged_html" && ! -e "$conv_filename") {return -1;}
384
385 # Run the "fribidi" (http://fribidi.org) Unicode Bidirectional Algorithm program over the converted file
386 # Added for fixing up Persian PDFs after being processed by pdftohtml, but may be useful in other cases too
387 if ($self->{'apply_fribidi'} && $self->{'converted_to'} =~ /(HTML|Text)/) {
388 my $fribidi_command = "fribidi \"$conv_filename\" >\"${conv_filename}.tmp\"";
389 if (system($fribidi_command) != 0) {
390 print STDERR "ERROR: Cannot run fribidi on \"$conv_filename\".\n";
391 }
392 else {
393 &FileUtils::moveFiles("${conv_filename}.tmp", $conv_filename);
394 }
395 }
396
397 my $secondary_plugins = $self->{'secondary_plugins'};
398 my $num_secondary_plugins = scalar(keys %$secondary_plugins);
399
400 if ($num_secondary_plugins == 0) {
401 print $outhandle "Warning: No secondary plugin to use in conversion. Skipping $file\n";
402 return 0; # effectively block it
403 }
404
405 my @plugin_names = keys %$secondary_plugins;
406 my $plugin_name = shift @plugin_names;
407
408 if ($num_secondary_plugins > 1) {
409 print $outhandle "Warning: Multiple secondary plugins not supported yet! Choosing $plugin_name\n.";
410 }
411
412 my $secondary_plugin = $secondary_plugins->{$plugin_name};
413
414 # note: metadata is not carried on to the next level
415## **** I just replaced $metadata with {} in following
416 my ($rv,$doc_obj)
417 = $secondary_plugin->read_into_doc_obj ($pluginfo,"", $conv_filename, $block_hash, {}, $processor, $maxdocs, $total_count, $gli);
418
419 if ((!defined $rv) || ($rv<1)) {
420 # wasn't processed
421 return $rv;
422 }
423
424 # Override previous gsdlsourcefilename set by secondary plugin
425 my $collect_file = &util::filename_within_collection($filename_full_path);
426 my $collect_conv_file = &util::filename_within_collection($conv_filename);
427 $doc_obj->set_source_filename ($collect_file, $self->{'file_rename_method'});
428 ## set_source_filename does not set the doc_obj source_path which is used in archives dbs for incremental
429 # build. so set it manually.
430 $doc_obj->set_source_path($filename_full_path);
431 $doc_obj->set_converted_filename($collect_conv_file);
432
433 my $plugin_filename_encoding = $self->{'filename_encoding'};
434 my $filename_encoding = $self->deduce_filename_encoding($file,$metadata,$plugin_filename_encoding);
435 $self->set_Source_metadata($doc_obj, $filename_full_path, $filename_encoding);
436
437 $doc_obj->set_utf8_metadata_element($doc_obj->get_top_section(), "Plugin", "$self->{'plugin_type'}");
438 $doc_obj->set_utf8_metadata_element($doc_obj->get_top_section(), "FileSize", (-s $filename_full_path));
439
440 # ****
441 my ($tailname, $dirname, $suffix)
442 = &File::Basename::fileparse($filename_full_path, "\\.[^\\.]+\$");
443 $doc_obj->set_utf8_metadata_element($doc_obj->get_top_section(), "FilenameRoot", $tailname);
444
445 # do plugin specific processing of doc_obj
446 unless (defined ($self->process($pluginfo, $base_dir, $file, $metadata, $doc_obj, $gli))) {
447 print STDERR "<ProcessingError n='$file'>\n" if ($gli);
448 return -1;
449 }
450
451 my $topsection = $doc_obj->get_top_section();
452 $self->add_associated_files($doc_obj, $filename_full_path);
453
454 # extra_metadata is already called by sec plugin in process??
455 $self->extra_metadata($doc_obj, $topsection, $metadata); # do we need this here??
456 # do any automatic metadata extraction
457 $self->auto_extract_metadata ($doc_obj);
458
459 # have we found a Title??
460 $self->title_fallback($doc_obj,$topsection,$filename_no_path);
461
462 # force a new OID - this will use OIDtype option set for this plugin.
463 $self->add_OID($doc_obj, 1);
464
465 return (1, $doc_obj);
466
467}
468
469sub process {
470 my $self = shift (@_);
471 my ($pluginfo, $base_dir, $file, $metadata, $doc_obj, $gli) = @_;
472
473 return $self->process_type($base_dir, $file, $doc_obj);
474}
475
476# do plugin specific processing of doc_obj for doc_ext type
477sub process_type {
478 my $self = shift (@_);
479 my ($base_dir, $file, $doc_obj) = @_;
480
481 # need to check that not empty
482 my ($doc_ext) = $file =~ /\.(\w+)$/;
483 $doc_ext = lc($doc_ext);
484 my $file_type = "unknown";
485 $file_type = $self->{'file_type'} if defined $self->{'file_type'};
486
487 # associate original file with doc object
488 my $cursection = $doc_obj->get_top_section();
489 my $filename = &FileUtils::filenameConcatenate($base_dir, $file);
490 my $assocfilename = "doc.$doc_ext";
491 if ($self->{'keep_original_filename'} == 1) {
492 # this should be the same filename that was used for the Source and SourceFile metadata,
493 # as we will use SourceFile in the srclink (below)
494 $assocfilename = $doc_obj->get_assocfile_from_sourcefile();
495 }
496
497 $doc_obj->associate_file($filename, $assocfilename, undef, $cursection);
498
499 # We use set instead of add here because we only want one value
500 $doc_obj->set_utf8_metadata_element($cursection, "FileFormat", $file_type);
501 my $srclink_filename = "doc.$doc_ext";
502 if ($self->{'keep_original_filename'} == 1) {
503 $srclink_filename = $doc_obj->get_sourcefile();
504 }
505 # srclink_file is now deprecated because of the "_" in the metadataname. Use srclinkFile
506 $doc_obj->add_utf8_metadata ($cursection, "srcicon", "_icon".$doc_ext."_");
507 $doc_obj->add_utf8_metadata ($cursection, "srclink_file", $srclink_filename);
508 $doc_obj->add_utf8_metadata ($cursection, "srclinkFile", $srclink_filename);
509 return 1;
510}
511
512sub clean_up_after_doc_obj_processing {
513 my $self = shift(@_);
514
515 my $tmp_dir = $self->{'tmp_dir'};
516 if (defined $tmp_dir && -d $tmp_dir) {
517 ##print STDERR "**** Suppressing clean up of tmp dir\n";
518 &FileUtils::removeFilesRecursive($tmp_dir);
519 $self->{'tmp_dir'} = undef;
520 }
521
522
523}
524
525# This sub is shared across PowerPointPlugin and UnknownConverterPlugin,
526# so it's been copied into here from the former.
527sub generate_item_file {
528 my $self = shift(@_);
529 my ($input_filename) = @_;
530 my $outhandle = $self->{'outhandle'};
531 my ($tailname, $dirname, $suffix)
532 = &File::Basename::fileparse($input_filename, "\\.[^\\.]+\$");
533
534 my $plugin_name = $self->{'plugin_type'}; # inherited from BaseImporter
535
536 # find all the files in the directory
537 if (!opendir (DIR, $dirname)) {
538 print $outhandle "$plugin_name: Couldn't read directory $dirname\n";
539 return $input_filename;
540 }
541
542 my @dir = readdir (DIR);
543 closedir (DIR);
544
545 # start the item file
546 my $itemfile_name = &util::filename_cat($dirname, "$tailname.item");
547
548 # encoding specification????
549 if (!open (ITEMFILE, ">$itemfile_name")) {
550 print $outhandle "$plugin_name: Couldn't open $itemfile_name for writing\n";
551 }
552 print ITEMFILE "<GeneratedBy>$plugin_name\n";
553 # print the first page
554 my @sorted_dir = sort alphanum_sort @dir;
555 for (my $i = 0; $i < scalar(@sorted_dir); $i++) {
556 my $file = $sorted_dir[$i];
557 if ($file =~ /^img(\d+)\.jpg$/) {
558 my $num = $1;
559 $self->tidy_up_html(&util::filename_cat($dirname, "text$num.html"));
560 print ITEMFILE "$num:img$num.jpg:text$num.html:\n";
561 }
562 }
563 close ITEMFILE;
564 return $itemfile_name;
565
566}
567
5681;
569
570
571
572
573
574
575
Note: See TracBrowser for help on using the repository browser.