# Converter.pm: Common code for Converters. # # Copyright 2011-2024 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 . # # Original author: Patrice Dumas package Texinfo::Convert::Converter; use 5.006; use strict; # To check if there is no erroneous autovivification #no autovivification qw(fetch delete exists store strict); # for fileparse use File::Basename; # for file names portability use File::Spec; use Encode qw(decode); # for dclone use Storable; #use Data::Dumper; use Carp qw(cluck confess); use Texinfo::Convert::ConvertXS; use Texinfo::XSLoader; use Texinfo::Options; use Texinfo::Common; use Texinfo::Report; use Texinfo::Convert::Utils; use Texinfo::Convert::Unicode; use Texinfo::Convert::Texinfo; use Texinfo::Convert::Text; use Texinfo::Convert::NodeNameNormalization; use Texinfo::OutputUnits; use Texinfo::Translations; require Exporter; our @ISA = qw(Exporter); our @EXPORT_OK = qw( xml_protect_text xml_comment xml_accent xml_accents ); our $VERSION = '7.2'; my $XS_convert = Texinfo::XSLoader::XS_convert_enabled(); our $module_loaded = 0; my %XS_overrides = ( # fully overriden for all the converters "Texinfo::Convert::Converter::_generic_converter_init", => "Texinfo::Convert::ConvertXS::generic_converter_init", "Texinfo::Convert::Converter::set_document" => "Texinfo::Convert::ConvertXS::converter_set_document", "Texinfo::Convert::Converter::set_conf" => "Texinfo::Convert::ConvertXS::set_conf", "Texinfo::Convert::Converter::force_conf" => "Texinfo::Convert::ConvertXS::force_conf", "Texinfo::Convert::Converter::get_conf" => "Texinfo::Convert::ConvertXS::get_conf", "Texinfo::Convert::Converter::get_converter_errors" => "Texinfo::Convert::ConvertXS::get_converter_errors", "Texinfo::Convert::Converter::converter_line_error" => "Texinfo::Convert::ConvertXS::converter_line_error", "Texinfo::Convert::Converter::converter_line_warn" => "Texinfo::Convert::ConvertXS::converter_line_warn", "Texinfo::Convert::Converter::converter_document_error" => "Texinfo::Convert::ConvertXS::converter_document_error", "Texinfo::Convert::Converter::converter_document_warn" => "Texinfo::Convert::ConvertXS::converter_document_warn", "Texinfo::Convert::Converter::get_converter_indices_sorted_by_letter" => "Texinfo::Convert::ConvertXS::get_converter_indices_sorted_by_letter", "Texinfo::Convert::Converter::get_converter_indices_sorted_by_index" => "Texinfo::Convert::ConvertXS::get_converter_indices_sorted_by_index", "Texinfo::Convert::Converter::set_global_document_commands" => "Texinfo::Convert::ConvertXS::converter_set_global_document_commands", # XS only "Texinfo::Convert::Converter::reset_converter" => "Texinfo::Convert::ConvertXS::reset_converter", "Texinfo::Convert::Converter::destroy" => "Texinfo::Convert::ConvertXS::destroy", "Texinfo::Convert::Converter::XS_get_unclosed_stream" => "Texinfo::Convert::ConvertXS::get_unclosed_stream", ); sub import { if (!$module_loaded) { if ($XS_convert) { foreach my $sub (keys %XS_overrides) { Texinfo::XSLoader::override ($sub, $XS_overrides{$sub}); } } $module_loaded = 1; } # The usual import method goto &Exporter::import; } # values for integer and string options in code generated from # Texinfo/Convert/converters_defaults.txt my $regular_defaults = Texinfo::Options::get_converter_regular_options('converter'); my %defaults = %$regular_defaults; # values for integer and string options in code generated from # Texinfo/Convert/converters_defaults.txt my $common_defaults = Texinfo::Options::get_converter_regular_options('common'); # defaults for all converters that are not defined elsewhere. # undef values in general marks information passed by the caller that # is valid in the initialization hash, but is not considered # as "configuration/customization". It is not available through get_conf() # but is available directly in the converter as a hash key. # FIXME separate the two types of information and check that those # items are not valid customization options? Or make them customization # variables that can only be set from init files, like buttons or icons? # NOTE converters for now only set customization variables. # It would be good to keep it that way. my %common_converters_defaults = ( # Following are set in the main program 'deprecated_config_directories' => undef, # Not set in the main program 'translated_commands' => {'error' => 'error@arrow{}',}, # integer and string customization variables common for all the converters # with values different from main program values %$common_defaults ); my %all_converters_defaults = (%Texinfo::Options::converter_cmdline_options, %Texinfo::Options::converter_customization_options, %Texinfo::Options::unique_at_command_options, %Texinfo::Options::multiple_at_command_options, %common_converters_defaults ); # For translation of in document string. if (0) { my $self; # TRANSLATORS: expansion of @error{} as Texinfo code $self->cdt('error@arrow{}'); } our %default_args_code_style = ( 'email' => [1], 'anchor' => [1], 'uref' => [1], 'url' => [1], 'math' => [1], 'inforef' => [1,undef,1], 'image' => [1, 1, 1, undef, 1], # and type? 'float' => [1], ); foreach my $code_style_command (keys(%Texinfo::Commands::brace_code_commands)) { $default_args_code_style{$code_style_command} = [1]; } foreach my $ref_cmd ('pxref', 'xref', 'ref') { $default_args_code_style{$ref_cmd} = [1, undef, undef, 1]; } ################################################################ # converter API # convert_tree() and convert() should be implemented in converters. # There is an implementation of output() below but in general # output() should also be implemented by Converters. The simple # implementation of convert_output_unit() below is likely to be # ok for most converters. # Functions that should be defined in specific converters sub converter_defaults($$) { return \%defaults; } # should be redefined by specific converters sub converter_initialize($) { } sub conversion_initialization($;$) { my $converter = shift; my $document = shift; if ($document) { $converter->set_document($document); } } sub conversion_finalization($) { #my $converter = shift; } sub output_internal_links($) { my $self = shift; return undef; } sub set_document($$) { my $converter = shift; my $document = shift; $converter->{'document'} = $document; Texinfo::Common::set_output_encoding($converter, $document); $converter->{'convert_text_options'} = Texinfo::Convert::Text::copy_options_for_convert_text($converter); # In general, OUTPUT_PERL_ENCODING set below is needed for the output() # entry point through Texinfo::Convert::Utils::output_files_open_out. It is # also sometime needed for the converter itself. If not, in general it # is not needed for the convert() entry point, so the call could also be # done more finely in converters, but it is not really important. Texinfo::Common::set_output_perl_encoding($converter); } # initialization either in generic XS converter or in Perl sub _generic_converter_init($$;$) { my $converter = shift; my $format_defaults = shift; my $conf = shift; my %defaults = %all_converters_defaults; if ($format_defaults) { foreach my $key (keys(%$format_defaults)) { $defaults{$key} = $format_defaults->{$key}; } } $converter->{'conf'} = {}; foreach my $key (keys(%defaults)) { if (Texinfo::Common::valid_customization_option($key)) { $converter->{'conf'}->{$key} = $defaults{$key}; } else { $converter->{$key} = $defaults{$key}; } } $converter->{'configured'} = {}; if (defined($conf)) { foreach my $key (keys(%$conf)) { if (Texinfo::Common::valid_customization_option($key)) { $converter->{'conf'}->{$key} = $conf->{$key}; } elsif (!exists($defaults{$key})) { my $class = ref($converter); warn "$class: $key not a possible configuration\n"; } else { $converter->{$key} = $conf->{$key}; } # configuration set here, from the argument of the converter, # in general coming from command-line or from init files will not # be reset by set_conf. $converter->{'configured'}->{$key} = 1; } } # set $converter->{'converter_init_conf'} to the customization # options obtained after setting the defaults and applying # the customization passed as argument. $converter->{'converter_init_conf'} = { %{$converter->{'conf'}} }; # used for output files information, to register opened # and not closed files. Accessed through output_files_information() $converter->{'output_files'} = Texinfo::Convert::Utils::output_files_initialize(); # setup expanded formats as a hash. my $expanded_formats = $converter->{'conf'}->{'EXPANDED_FORMATS'}; $converter->{'expanded_formats'} = {}; if (defined($expanded_formats)) { foreach my $expanded_format (@$expanded_formats) { $converter->{'expanded_formats'}->{$expanded_format} = 1; } } $converter->{'error_warning_messages'} = []; } # this function is designed so as to be used in specific Converters # and not redefined. sub converter($;$) { my $class = shift; my $conf = shift; my $converter = {}; bless $converter, $class; my $format_defaults = $converter->converter_defaults($conf); _generic_converter_init($converter, $format_defaults, $conf); $converter->converter_initialize(); return $converter; } sub convert_output_unit($$) { my $self = shift; my $output_unit = shift; my $result = ''; foreach my $element (@{$output_unit->{'unit_contents'}}) { $result .= $self->convert_tree($element); } return $result; } # should be redefined by specific converters sub conversion_output_begin($;$$) { my $self = shift; my $output_file = shift; my $output_filename = shift; return ''; } sub conversion_output_end($) { my $self = shift; return ''; } sub output_tree($$) { my $self = shift; my $document = shift; $self->conversion_initialization($document); my $root = $document->tree(); my ($output_file, $destination_directory, $output_filename) = $self->determine_files_and_directory( $self->get_conf('TEXINFO_OUTPUT_FORMAT')); my ($encoded_destination_directory, $dir_encoding) = $self->encoded_output_file_name($destination_directory); my $succeeded = $self->create_destination_directory($encoded_destination_directory, $destination_directory); unless ($succeeded) { $self->conversion_finalization(); return undef; } my $fh; my $encoded_output_file; if (! $output_file eq '') { my $path_encoding; ($encoded_output_file, $path_encoding) = $self->encoded_output_file_name($output_file); my $error_message; # the third return information, set if the file has already been used # in this files_information is not checked as this cannot happen. ($fh, $error_message) = Texinfo::Convert::Utils::output_files_open_out( $self->output_files_information(), $self, $encoded_output_file); if (!$fh) { $self->converter_document_error( sprintf(__("could not open %s for writing: %s"), $output_file, $error_message)); $self->conversion_finalization(); return undef; } } my $output_beginning = $self->conversion_output_begin($output_file, $output_filename); my $result = ''; $result .= $self->write_or_return($output_beginning, $fh); $result .= $self->write_or_return($self->convert_tree($root), $fh); my $output_end = $self->conversion_output_end(); $result .= $self->write_or_return($output_end, $fh); if ($fh and $output_file ne '-') { Texinfo::Convert::Utils::output_files_register_closed( $self->output_files_information(), $encoded_output_file); if (!close ($fh)) { $self->converter_document_error( sprintf(__("error on closing %s: %s"), $output_file, $!)); } } $self->conversion_finalization(); return $result; } # Nothing to do in Perl. XS function resets converter sub reset_converter($) { } # Nothing to do in Perl. XS function frees memory sub destroy($) { } sub XS_get_unclosed_stream($$) { return undef; } sub output_files_information($) { my $self = shift; return $self->{'output_files'}; }