From: Bruno Haible Date: Mon, 18 Sep 2023 18:26:48 +0000 (+0200) Subject: xgettext: Perl: Add test against unbounded nesting_depth growth. X-Git-Tag: v0.22.1~8 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1cc9b49ab946fed8afb19bfd76527af154d6ffb1;p=thirdparty%2Fgettext.git xgettext: Perl: Add test against unbounded nesting_depth growth. Reported by Gavin D. Smith at and . * gettext-tools/tests/xgettext-perl-stackovfl-5: New file. * gettext-tools/tests/testdata/xg-pl-so-5.pl: New file, taken from https://git.savannah.gnu.org/gitweb/?p=texinfo.git;a=blob;f=tp/Texinfo/Convert/HTML.pm;hb=c8d9edd94d9b1a3e675e811208d9e66eaf9a7daa * gettext-tools/tests/Makefile.am (TESTS): Add xgettext-perl-stackovfl-5. (EXTRA_DIST): Add testdata/xg-pl-so-5.pl. --- diff --git a/gettext-tools/tests/Makefile.am b/gettext-tools/tests/Makefile.am index e2114f8c9..3c5f9422f 100644 --- a/gettext-tools/tests/Makefile.am +++ b/gettext-tools/tests/Makefile.am @@ -134,6 +134,7 @@ TESTS = gettext-1 gettext-2 \ xgettext-perl-9 xgettext-perl-10 \ xgettext-perl-stackovfl-1 xgettext-perl-stackovfl-2 \ xgettext-perl-stackovfl-3 xgettext-perl-stackovfl-4 \ + xgettext-perl-stackovfl-5 \ xgettext-php-1 xgettext-php-2 xgettext-php-3 xgettext-php-4 \ xgettext-php-5 \ xgettext-php-stackovfl-1 xgettext-php-stackovfl-2 \ @@ -238,6 +239,7 @@ EXTRA_DIST += init.sh init.cfg $(TESTS) \ xgettext-c-1 xg-c-comment-6.c xg-c-escape-3.c xg-vala-2.vala \ common/supplemental/plurals.xml \ testdata/xg-el-so-3.el testdata/xg-el-so-4.el \ + testdata/xg-pl-so-5.pl \ testdata/xg-po-3.po testdata/xg-po-4.po XGETTEXT = ../src/xgettext diff --git a/gettext-tools/tests/testdata/xg-pl-so-5.pl b/gettext-tools/tests/testdata/xg-pl-so-5.pl new file mode 100644 index 000000000..8b1091871 --- /dev/null +++ b/gettext-tools/tests/testdata/xg-pl-so-5.pl @@ -0,0 +1,11945 @@ +# HTML.pm: output tree as HTML. +# +# Copyright 2011-2022 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 . +# +# +# The documentation of the HTML customization API which is both +# used and implemented in the current file is in the customization_api +# Texinfo manual. +# +# Formatting and conversion functions that can be replaced by user-defined +# functions should only use documented functions to pass information +# and formatted content, such that users can overrides them independently +# without risking unwanted results. Also in formatting functions, the state of +# the converter should only be accessed through functions, such as in_math, +# in_preformatted, preformatted_classes_stack and similar functions. +# +# Original author: Patrice Dumas + +package Texinfo::Convert::HTML; + +use 5.00405; + +# See 'The "Unicode Bug"' under 'perlunicode' man page. This means +# that regular expressions will treat characters 128-255 in a Perl string +# the same regardless of whether the string is using a UTF-8 encoding. +# For older Perls, you can use utf8::upgrade on the strings, where the +# difference matters. +use if $] >= 5.012, feature => 'unicode_strings'; + +use if $] >= 5.014, re => '/a'; # ASCII-only character classes in regexes + +use strict; + +# To check if there is no erroneous autovivification +#no autovivification qw(fetch delete exists store strict); + +use Carp qw(cluck confess); + +use File::Copy qw(copy); + +use Storable; + +use Encode qw(find_encoding decode encode); + +use Texinfo::Commands; +use Texinfo::Common; +use Texinfo::Config; +use Texinfo::Convert::Unicode; +use Texinfo::Convert::Texinfo; +use Texinfo::Convert::Utils; +use Texinfo::Convert::Text; +use Texinfo::Convert::NodeNameNormalization; +use Texinfo::Structuring; +use Texinfo::Convert::Converter; + +# used to convert Texinfo to LaTeX math in @math and @displaymath +# for further conversion by softwares that only convert LaTeX. +# NOTE mathjax does not implement some constructs output by the +# Texinfo::Convert::LaTeX converter. Examples in 2022: +# \mathord{\text{}} \textsl{} \copyright{} \mathsterling{} +use Texinfo::Convert::LaTeX; + + +require Exporter; +use vars qw($VERSION @ISA); +@ISA = qw(Texinfo::Convert::Converter); + +$VERSION = '7.0dev'; + +our $module_loaded = 0; +sub import { + if (!$module_loaded) { + Texinfo::XSLoader::override( + "Texinfo::Convert::HTML::_default_format_protect_text", + "Texinfo::MiscXS::default_format_protect_text"); + Texinfo::XSLoader::override( + "Texinfo::Convert::HTML::_entity_text", + "Texinfo::MiscXS::entity_text"); + $module_loaded = 1; + } + # The usual import method + goto &Exporter::import; +} + + + +my %nobrace_commands = %Texinfo::Commands::nobrace_commands; +my %line_commands = %Texinfo::Commands::line_commands; +my %nobrace_symbol_text = %Texinfo::Common::nobrace_symbol_text; +my %accent_commands = %Texinfo::Commands::accent_commands; +my %sectioning_heading_commands = %Texinfo::Commands::sectioning_heading_commands; +my %def_commands = %Texinfo::Commands::def_commands; +my %ref_commands = %Texinfo::Commands::ref_commands; +my %brace_commands = %Texinfo::Commands::brace_commands; +my %block_commands = %Texinfo::Commands::block_commands; +my %root_commands = %Texinfo::Commands::root_commands; +my %preformatted_commands = %Texinfo::Commands::preformatted_commands; +my %math_commands = %Texinfo::Commands::math_commands; +my %preformatted_code_commands = %Texinfo::Commands::preformatted_code_commands; +my %letter_no_arg_commands = %Texinfo::Commands::letter_no_arg_commands; + +my %formatted_line_commands = %Texinfo::Commands::formatted_line_commands; +my %formatted_nobrace_commands = %Texinfo::Commands::formatted_nobrace_commands; +my %formattable_line_commands = %Texinfo::Commands::formattable_line_commands; +my %explained_commands = %Texinfo::Commands::explained_commands; +my %inline_format_commands = %Texinfo::Commands::inline_format_commands; +my %brace_code_commands = %Texinfo::Commands::brace_code_commands; +my %default_index_commands = %Texinfo::Commands::default_index_commands; +my %small_block_associated_command = %Texinfo::Common::small_block_associated_command; + +foreach my $def_command (keys(%def_commands)) { + $formatted_line_commands{$def_command} = 1 if ($line_commands{$def_command}); +} + +# FIXME remove raw commands? +my %format_context_commands = (%block_commands, %root_commands); + +foreach my $misc_context_command('tab', 'item', 'itemx', 'headitem') { + $format_context_commands{$misc_context_command} = 1; +} + +my %HTML_align_commands; +foreach my $align_command('raggedright', 'flushleft', 'flushright', 'center') { + $HTML_align_commands{$align_command} = 1; +} + +my %composition_context_commands = (%preformatted_commands, %root_commands, + %HTML_align_commands); +$composition_context_commands{'float'} = 1; +my %format_raw_commands; +foreach my $block_command (keys(%block_commands)) { + $composition_context_commands{$block_command} = 1 + if ($block_commands{$block_command} eq 'menu'); + $format_raw_commands{$block_command} = 1 + if ($block_commands{$block_command} eq 'format_raw'); +} + +# FIXME allow customization? (also in DocBook) +my %upper_case_commands = ( 'sc' => 1 ); + + +# API for html formatting + +sub _collect_css_element_class($$) +{ + my $self = shift; + my $element_class = shift; + + #if (not defined($self->{'current_filename'})) { + # cluck "CFND"; + #} + if (defined($self->{'css_element_class_styles'}->{$element_class})) { + if ($self->{'document_global_context'}) { + $self->{'document_global_context_css'}->{$element_class} = 1; + } elsif (defined($self->{'current_filename'})) { + $self->{'file_css'}->{$self->{'current_filename'}} = {} + if (!$self->{'file_css'}->{$self->{'current_filename'}}); + $self->{'file_css'}->{$self->{'current_filename'}}->{$element_class} = 1; + } + } +} + +# $classes should be an array reference or undef +sub html_attribute_class($$;$) +{ + my $self = shift; + my $element = shift; + my $classes = shift; + + if (defined($classes) and ref($classes) ne 'ARRAY') { + confess("html_attribute_class: $classes not an array ref (for $element)"); + } + if (!defined($classes) or scalar(@$classes) == 0 + # API info: get_conf() API code conforming would be: + # or $self->get_conf('NO_CSS')) { + or $self->{'conf'}->{'NO_CSS'}) { + if ($element eq 'span') { + return ''; + } else { + return "<$element"; + } + } + + my $style = ''; + + # API info: get_conf() API code conforming would be: + # if ($self->get_conf('INLINE_CSS_STYLE')) { + if ($self->{'conf'}->{'INLINE_CSS_STYLE'}) { + my @styles = (); + foreach my $style_class (@$classes) { + if (not defined($style_class)) { + confess ("class not defined (for $element)"); + } + if (defined($self->{'css_element_class_styles'} + ->{"$element.$style_class"})) { + push @styles, + $self->{'css_element_class_styles'}->{"$element.$style_class"}; + } + } + if (scalar(@styles) > 0) { + $style = ' style="'.join(';', @styles).'"'; + } + } else { + foreach my $style_class (@$classes) { + if (not defined($style_class)) { + confess ("class not defined (for $element)"); + } + $self->_collect_css_element_class("$element.$style_class"); + } + } + my $class_str = join(' ', map {_protect_class_name($self, $_)} @$classes); + return "<$element class=\"$class_str\"$style"; +} + +# for rules that cannot be collected during document output since they +# are not associated with a class attribute element setting +my %css_rules_not_collected = ( +); + +# returns an array of CSS element.class seen in the $FILENAME +sub html_get_css_elements_classes($;$) +{ + my $self = shift; + my $filename = shift; + + my %css_elements_classes = %css_rules_not_collected; + if ($self->{'document_global_context_css'}) { + %css_elements_classes = ( %css_elements_classes, + %{$self->{'document_global_context_css'}} ); + } + + if (defined($filename) and $self->{'file_css'} + and $self->{'file_css'}->{$filename}) { + %css_elements_classes = ( %css_elements_classes, + %{$self->{'file_css'}->{$filename}} ); + } + + if ($css_elements_classes{'a.copiable-link'}) { + $css_elements_classes{'span:hover a.copiable-link'} = 1; + } + + return sort(keys(%css_elements_classes)); +} + +sub close_html_lone_element($$) { + my $self = shift; + my $html_element = shift; + if ($self->get_conf('USE_XML_SYNTAX')) { + return $html_element . '/>'; + } + return $html_element .'>'; +} + +my $xml_numeric_entity_nbsp = '&#'.hex('00A0').';'; +my $xml_named_entity_nbsp = ' '; + +my $html_default_entity_nbsp = $xml_named_entity_nbsp; + +sub substitute_html_non_breaking_space($$) +{ + my $self = shift; + my $text = shift; + + # do not use get_info() as it may not be set yet + my $non_breaking_space = $self->{'non_breaking_space'}; + # using \Q \E on the substitution leads to spurious \ + $text =~ s/\Q$html_default_entity_nbsp\E/$non_breaking_space/g; + return $text; +} + +my @image_files_extensions = ('.png', '.jpg', '.jpeg', '.gif'); + +# this allows init files to get the location of the image files +# which cannot be determined from the result, as the file +# location is not used in the element output. +# FIXME use filenametext or url? url is always UTF-8 encoded +# to fit with percent encoding, filenametext uses the output +# encoding. As a file name, filenametext could make sense, +# although the underlying character obtained with utf-8 may also +# make sense. It is also used as the path part of a url. +# In practice, the user should check that the output encoding +# and the commands used in file names match, so url or +# filenametext should be the same. +sub html_image_file_location_name($$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + + my @extensions = @image_files_extensions; + + my $image_file; + my $image_basefile; + my $image_extension; + # this variable is bytes encoded in the filesystem encoding + my ($image_path, $image_path_encoding); + if (defined($args->[0]->{'filenametext'}) + and $args->[0]->{'filenametext'} ne '') { + $image_basefile = $args->[0]->{'filenametext'}; + my $extension; + if (defined($args->[4]) and defined($args->[4]->{'filenametext'})) { + $extension = $args->[4]->{'filenametext'}; + unshift @extensions, ("$extension", ".$extension"); + } + foreach my $extension (@extensions) { + my ($file_name, $file_name_encoding) + = $self->encoded_input_file_name($image_basefile.$extension); + my $located_image_path + = $self->Texinfo::Common::locate_include_file($file_name); + if (defined($located_image_path) and $located_image_path ne '') { + $image_path = $located_image_path; + $image_path_encoding = $file_name_encoding; + # use the @-command argument and not the file found using the + # include paths. It is considered that the files in include paths + # will be moved by the caller anyway. + # If the file path found was to be used it should be decoded to perl + # codepoints too. + $image_file = $image_basefile.$extension; + $image_extension = $extension; + last; + } + } + if (!defined($image_file) or $image_file eq '') { + if (defined($extension) and $extension ne '') { + $image_file = $image_basefile.$extension; + $image_extension = $extension; + } else { + $image_file = "$image_basefile.jpg"; + $image_extension = 'jpg'; + } + } + } + return ($image_file, $image_basefile, $image_extension, $image_path, + $image_path_encoding); +} + +sub css_add_info($$$;$) +{ + my $self = shift; + my $spec = shift; + my $css_info = shift; + my $css_style = shift; + + if ($spec eq 'rules') { + push @{$self->{'css_rule_lines'}}, $css_info; + } elsif ($spec eq 'imports') { + push @{$self->{'css_import_lines'}}, $css_info; + } else { + $self->{'css_element_class_styles'}->{$css_info} = $css_style; + } +} + +sub css_get_info($$;$) { + my $self = shift; + my $spec = shift; + my $css_info = shift; + + if ($spec eq 'rules') { + if (defined($self->{'css_rule_lines'})) { + return @{$self->{'css_rule_lines'}}; + } else { + return (); + } + } elsif ($spec eq 'imports') { + if (defined($self->{'css_import_lines'})) { + return @{$self->{'css_import_lines'}}; + } else { + return (); + } + } else { + if (defined($css_info)) { + if ($self->{'css_element_class_styles'}->{$css_info}) { + return $self->{'css_element_class_styles'}->{$css_info}; + } else { + return undef; + } + } else { + return { %{$self->{'css_element_class_styles'}} }; + } + } +} + +my %default_css_string_commands_conversion; +my %default_css_string_types_conversion; +my %default_css_string_formatting_references; + +sub html_convert_css_string($$;$) +{ + my $self = shift; + my $element = shift; + my $explanation = shift; + + my $saved_commands = {}; + my $saved_types = {}; + my $saved_formatting_references = {}; + foreach my $cmdname (keys(%default_css_string_commands_conversion)) { + $saved_commands->{$cmdname} = $self->{'commands_conversion'}->{$cmdname}; + $self->{'commands_conversion'}->{$cmdname} + = $default_css_string_commands_conversion{$cmdname}; + } + foreach my $type (keys(%default_css_string_types_conversion)) { + $saved_types->{$type} = $self->{'types_conversion'}->{$type}; + $self->{'types_conversion'}->{$type} + = $default_css_string_types_conversion{$type}; + } + foreach my $formatting_reference + (keys(%default_css_string_formatting_references)) { + $saved_formatting_references->{$formatting_reference} + = $self->{'formatting_function'}->{$formatting_reference}; + $self->{'formatting_function'}->{$formatting_reference} + = $default_css_string_formatting_references{$formatting_reference}; + } + + my $result = $self->convert_tree_new_formatting_context( + {'type' => '_string', + 'contents' => [$element]}, + 'css_string', $explanation); + foreach my $cmdname (keys (%default_css_string_commands_conversion)) { + $self->{'commands_conversion'}->{$cmdname} = $saved_commands->{$cmdname}; + } + foreach my $type (keys(%default_css_string_types_conversion)) { + $self->{'types_conversion'}->{$type} = $saved_types->{$type}; + } + foreach my $formatting_reference (keys(%default_css_string_formatting_references)) { + $self->{'formatting_function'}->{$formatting_reference} + = $saved_formatting_references->{$formatting_reference}; + } + return $result; +} + +my %special_list_mark_css_string_no_arg_command = ( +# tried to use HYPHEN BULLET \2043 for use as in a bullet list, but, at least +# with my test of firefox the result is very different from a bullet. +# hyphen minus or hyphen \2010 are even smaller than hyphen bullet. +# Use the Unicode codepoint used normally for a mathematical minus \2212 +# even though it is too large, since the others are too short... +# (which is actually the default, but this could change). + #'minus' => '-', + #'minus' => '\2010 ', + 'minus' => '\2212 ', +); + +sub html_convert_css_string_for_list_mark($$;$) +{ + my $self = shift; + my $element = shift; + my $explanation = shift; + + my $saved_css_string_no_arg_command = {}; + foreach my $command (keys(%special_list_mark_css_string_no_arg_command)) { + $saved_css_string_no_arg_command->{$command} + = $self->{'no_arg_commands_formatting'}->{'css_string'}->{$command}; + $self->{'no_arg_commands_formatting'}->{'css_string'}->{$command} + = $special_list_mark_css_string_no_arg_command{$command}; + } + my $result = $self->html_convert_css_string($element, $explanation); + foreach my $command (keys(%special_list_mark_css_string_no_arg_command)) { + $self->{'no_arg_commands_formatting'}->{'css_string'}->{$command} + = $saved_css_string_no_arg_command->{$command}; + } + return $result; +} + +# API to access converter state for conversion + +sub in_math($) +{ + my $self = shift; + return $self->{'document_context'}->[-1]->{'math'}; +} + +# set if in menu or preformatted command +sub in_preformatted($) +{ + my $self = shift; + my $context = $self->{'document_context'}->[-1]->{'composition_context'}->[-1]; + if ($preformatted_commands{$context} + or $self->{'pre_class_types'}->{$context} + or ($block_commands{$context} + and $block_commands{$context} eq 'menu' + and $self->_in_preformatted_in_menu())) { + return $context; + } else { + return undef; + } +} + +sub in_upper_case($) +{ + my $self = shift; + return $self->{'document_context'}->[-1]->{'formatting_context'}->[-1] + ->{'upper_case'}; +} + +sub in_non_breakable_space($) +{ + my $self = shift; + return $self->{'document_context'}->[-1]->{'formatting_context'}->[-1] + ->{'no_break'}; +} + +sub in_space_protected($) +{ + my $self = shift; + return $self->{'document_context'}->[-1]->{'formatting_context'}->[-1] + ->{'space_protected'}; +} + +sub in_code($) +{ + my $self = shift; + return $self->{'document_context'}->[-1]->{'monospace'}->[-1]; +} + +sub in_string($) +{ + my $self = shift; + return $self->{'document_context'}->[-1]->{'string'}; +} + +sub in_verbatim($) +{ + my $self = shift; + return $self->{'document_context'}->[-1]->{'verbatim'}; +} + +sub in_raw($) +{ + my $self = shift; + return $self->{'document_context'}->[-1]->{'raw'}; +} + +sub in_multi_expanded($) +{ + my $self = shift; + if (scalar(@{$self->{'multiple_pass'}})) { + return $self->{'multiple_pass'}->[-1]; + } + return undef; +} + +sub paragraph_number($) +{ + my $self = shift; + return $self->{'document_context'}->[-1]->{'formatting_context'}->[-1] + ->{'paragraph_number'}; +} + +sub preformatted_number($) +{ + my $self = shift; + return $self->{'document_context'}->[-1]->{'formatting_context'}->[-1] + ->{'preformatted_number'}; +} + +sub count_elements_in_filename($$$) +{ + my $self = shift; + my $spec = shift; + my $filename = shift; + + if ($spec eq 'total') { + if (defined($self->{'elements_in_file_count'}->{$filename})) { + return $self->{'elements_in_file_count'}->{$filename}; + } + } elsif ($spec eq 'remaining') { + if (defined($self->{'file_counters'}->{$filename})) { + return $self->{'file_counters'}->{$filename}; + } + } elsif ($spec eq 'current') { + if (defined($self->{'file_counters'}->{$filename})) { + return $self->{'elements_in_file_count'}->{$filename} + - $self->{'file_counters'}->{$filename} +1; + } + } + return undef; +} + +sub top_block_command($) +{ + my $self = shift; + return $self->{'document_context'}->[-1]->{'block_commands'}->[-1]; +} + +sub preformatted_classes_stack($) +{ + my $self = shift; + return @{$self->{'document_context'}->[-1]->{'preformatted_classes'}}; +} + +sub in_align($) +{ + my $self = shift; + my $context + = $self->{'document_context'}->[-1]->{'composition_context'}->[-1]; + if ($HTML_align_commands{$context}) { + return $context; + } else { + return undef; + } +} + +sub is_format_expanded($$) +{ + my $self = shift; + my $format = shift; + + return $self->{'expanded_formats_hash'}->{$format}; +} + +# the main data structure of the element target API is a hash reference, called +# the target information. +# The 'target' and 'filename' keys should be set for every type of element, +# but the other keys will only be set on some elements. +# +# The following keys can be set: +# +# Strings +# +# 'target': A unique string representing the target. Used as argument to +# 'id' attribute. +# 'contents_target': A unique string representing the target to the location +# of the element in the table of content. +# 'shortcontents_target': A unique string representing the target to the +# location of the element in the short table of contents +# 'node_filename': the file name deriving from the element node name +# 'section_filename': the file name deriving from the element section name +# 'special_element_filename': the file name of special elements +# (separate contents, about...) +# 'filename': the file name the element content is output to +# 'text', 'text_nonumber': a textual representation of the element where +# there is no restriction on the text formatting (ie HTML elements +# can be used). +# With _nonumber, no section number. +# 'string', 'string_nonumber': a textual representation of the element with +# restrictions on the available formatting, in practice no +# HTML elements, only entities to be able to use in attributes. +# With _nonumber, no section number. +# +# Other types +# +# 'tree', 'tree_nonumber: a Texinfo tree element which conversion should +# correspond to the element name. +# With _nonumber, no section number. +# 'node_command': the node element associated with the target element. +# 'root_element_command': the command associated to the top level element +# associated with the target element. +# +# Some functions cache their results in these hashes. + +# $COMMAND should be a tree element which is a possible target of a link. +# return the target information. +sub _get_target($$) +{ + my $self = shift; + my $command = shift; + my $target; + if (!defined($command)) { + cluck("_get_target command not defined"); + } + if ($self->{'targets'}->{$command}) { + $target = $self->{'targets'}->{$command}; + } elsif ($command->{'cmdname'} + # This should only happen for @*heading*, root_commands targets should + # already be set. + and $sectioning_heading_commands{$command->{'cmdname'}} + and !$root_commands{$command->{'cmdname'}}) { + $target = $self->_new_sectioning_command_target($command); + } + return $target; +} + +# API for links and elements directions formatting + +# This returns the id specific of the $COMMAND tree element +sub command_id($$) +{ + my $self = shift; + my $command = shift; + my $target = $self->_get_target($command); + if ($target) { + return $target->{'target'}; + } else { + return undef; + } +} + +sub command_contents_target($$$) +{ + my $self = shift; + my $command = shift; + my $contents_or_shortcontents = shift; + $contents_or_shortcontents = 'shortcontents' + if ($contents_or_shortcontents eq 'summarycontents'); + + my $target = $self->_get_target($command); + if ($target) { + return $target->{$contents_or_shortcontents .'_target'}; + } else { + return undef; + } +} + +sub _get_footnote_location_target($$) +{ + my $self = shift; + my $command = shift; + + if (defined($self->{'special_targets'}) + and defined($self->{'special_targets'}->{'footnote_location'}) + and defined($self->{'special_targets'}->{'footnote_location'}->{$command})) { + return $self->{'special_targets'}->{'footnote_location'}->{$command}; + } + return undef; +} + +sub footnote_location_target($$) +{ + my $self = shift; + my $command = shift; + + my $footnote_location_special_target = _get_footnote_location_target($self, + $command); + if (defined($footnote_location_special_target)) { + return $footnote_location_special_target->{'target'}; + } +} + +sub command_filename($$) +{ + my $self = shift; + my $command = shift; + + my $target = $self->_get_target($command); + if ($target) { + if (exists($target->{'filename'})) { + return $target->{'filename'}; + } + # this finds a special element for footnote command if such an element + # exists. This is best, the special element filename is the footnote + # filename. + my ($root_element, $root_command) + = $self->_html_get_tree_root_element($command, 1); + + if (defined($root_element) + and $root_element->{'structure'} + and exists($root_element->{'structure'}->{'unit_filename'})) { + $target->{'filename'} + = $root_element->{'structure'}->{'unit_filename'}; + return $root_element->{'structure'}->{'unit_filename'}; + } else { + $target->{'filename'} = undef; + } + } + return undef; +} + +sub command_root_element_command($$) +{ + my $self = shift; + my $command = shift; + + my $target = $self->_get_target($command); + if ($target) { + if (not exists($target->{'root_element_command'})) { + # in contrast with command_filename() we find the root element through + # the location holding the @footnote command. It is better, as the + # footnote special element is not associated with a root command, + # it is better to stay in the document to find a root element. + my ($root_element, $root_command) + = $self->_html_get_tree_root_element($command); + if ($root_element and $root_element->{'extra'}) { + $target->{'root_element_command'} + = $root_element->{'extra'}->{'unit_command'}; + } else { + $target->{'root_element_command'} = undef; + } + } + return $target->{'root_element_command'}; + } + return undef; +} + +sub tree_unit_element_command($$) +{ + my $self = shift; + my $element = shift; + + if ($element and $element->{'extra'}) { + if ($element->{'extra'}->{'unit_command'}) { + return $element->{'extra'}->{'unit_command'}; + } elsif (defined($element->{'type'}) + and $element->{'type'} eq 'special_element') { + return $element; + } + } + return undef; +} + +sub command_node($$) +{ + my $self = shift; + my $command = shift; + + my $target = $self->_get_target($command); + if ($target) { + if (not exists($target->{'node_command'})) { + # this finds a special element for footnote command if + # such an element exists + my ($root_element, $root_command) + = $self->_html_get_tree_root_element($command, 1); + if (defined($root_command)) { + if ($root_command->{'cmdname'} and $root_command->{'cmdname'} eq 'node') { + $target->{'node_command'} = $root_command; + } + if ($root_command->{'extra'} + and $root_command->{'extra'}->{'associated_node'}) { + $target->{'node_command'} + = $root_command->{'extra'}->{'associated_node'}; + } + } else { + $target->{'node_command'} = undef; + } + } + return $target->{'node_command'}; + } + return undef; +} + +# Return string for linking to $COMMAND with +sub command_href($$;$$$) +{ + my $self = shift; + my $command = shift; + my $source_filename = shift; + # for messages only + my $source_command = shift; + # to specify explicitly the target + my $specified_target = shift; + + $source_filename = $self->{'current_filename'} if (!defined($source_filename)); + + if ($command->{'manual_content'}) { + return $self->_external_node_href($command, $source_filename, + $source_command); + } + + my $target; + if (defined($specified_target)) { + $target = $specified_target; + } else { + my $target_command = $command; + # for sectioning command prefer the associated node + if ($command->{'extra'} and $command->{'extra'}->{'associated_node'}) { + $target_command = $command->{'extra'}->{'associated_node'}; + } + my $target_information = $self->_get_target($target_command); + $target = $target_information->{'target'} if ($target_information); + } + return '' if (!defined($target)); + my $href = ''; + + my $target_filename = $self->command_filename($command); + if (!defined($target_filename)) { + # Happens if there are no pages, for example if OUTPUT is set to '' + # as in the test cases. Also for things in @titlepage when + # titlepage is not output. + if ($self->{'tree_units'} and $self->{'tree_units'}->[0] + and $self->{'tree_units'}->[0]->{'structure'} + and defined($self->{'tree_units'}->[0] + ->{'structure'}->{'unit_filename'})) { + # In that case use the first page. + $target_filename + = $self->{'tree_units'}->[0]->{'structure'}->{'unit_filename'}; + } + } + if (defined($target_filename)) { + if (!defined($source_filename) + or $source_filename ne $target_filename) { + $href .= $target_filename; + # omit target if the command is an element command, there is only + # one element in file and there is a file in the href + my $command_root_element_command + = $self->command_root_element_command($command); + if (defined($source_filename) + and defined($command_root_element_command) + and ($command_root_element_command eq $command + or (defined($command_root_element_command->{'extra'}) + and defined($command_root_element_command->{'extra'} + ->{'associated_section'}) + and $command_root_element_command->{'extra'}->{'associated_section'} + eq $command))) { + my $count_elements_in_file + = $self->count_elements_in_filename('total', $target_filename); + if (defined($count_elements_in_file) and $count_elements_in_file == 1) { + $target = ''; + } + } + } + } + $href .= '#' . $target if ($target ne ''); + return $href; +} + +my %contents_command_special_element_variety = ( + 'contents' => 'contents', + 'shortcontents' => 'shortcontents', + 'summarycontents' => 'shortcontents', +); + +# Return string for linking to $CONTENTS_OR_SHORTCONTENTS associated +# element from $COMMAND with +sub command_contents_href($$$;$) +{ + my $self = shift; + my $command = shift; + my $contents_or_shortcontents = shift; + my $source_filename = shift; + + $source_filename = $self->{'current_filename'} + if (not defined($source_filename)); + + my ($special_element_variety, $target_element, $class_base, + $special_element_direction) + = $self->command_name_special_element_information($contents_or_shortcontents); + my $target + = $self->command_contents_target($command, $contents_or_shortcontents); + my $target_filename; + # !defined happens when called as convert() and not output() + if (defined($target_element)) { + $target_filename = $self->command_filename($target_element); + } + my $href = ''; + if (defined($target_filename) and + (!defined($source_filename) + or $source_filename ne $target_filename)) { + $href .= $target_filename; + } + $href .= '#' . $target if ($target ne ''); + return $href; +} + +sub footnote_location_href($$;$$$) +{ + my $self = shift; + my $command = shift; + my $source_filename = shift; + my $specified_target = shift; + my $target_filename = shift; + + $source_filename = $self->{'current_filename'} + if (not defined($source_filename)); + + my $special_target = _get_footnote_location_target($self, $command); + my $target = ''; + if (defined($specified_target)) { + $target = $specified_target; + } elsif (defined($special_target)) { + $target = $special_target->{'target'}; + } + # In the default footnote formatting functions, which calls + # footnote_location_href, the target file is always known as the + # footnote in the document appears before the footnote text formatting. + # $target_filename is therefore always defined. It is a good thing + # for the case of @footnote being formatted more than once (in multiple + # @insertcopying for instance) as the file found just below may not be the + # correct one in such a case. + if (not defined($target_filename)) { + if (defined($special_target) and defined($special_target->{'filename'})) { + $target_filename = $special_target->{'filename'}; + } else { + # in contrast with command_filename() we find the location holding + # the @footnote command, not the footnote element with footnotes + my ($root_element, $root_command) + = $self->_html_get_tree_root_element($command); + if (defined($root_element)) { + if (not defined($special_target)) { + $self->{'special_targets'}->{'footnote_location'}->{$command} = {}; + $special_target + = $self->{'special_targets'}->{'footnote_location'}->{$command}; + } + $special_target->{'filename'} + = $root_element->{'structure'}->{'unit_filename'}; + $target_filename = $special_target->{'filename'}; + } + } + } + my $href = ''; + if (defined($target_filename) and + (!defined($source_filename) + or $source_filename ne $target_filename)) { + $href .= $target_filename; + } + $href .= '#' . $target if ($target ne ''); + return $href; +} + +# Return text to be used for a hyperlink to $COMMAND. +# $TYPE refers to the type of value returned from this function: +# 'text' - return text +# 'tree' - return a tree +# 'tree_nonumber' - return tree representing text without a chapter number +# being included. +# 'string' - return simpler text that can be used in element attributes +sub command_text($$;$) +{ + my $self = shift; + my $command = shift; + my $type = shift; + + if (!defined($type)) { + $type = 'text'; + } + if (!defined($command)) { + cluck "in command_text($type) command not defined"; + } + + if ($command->{'manual_content'}) { + my $node_content = []; + $node_content = $command->{'node_content'} + if (defined($command->{'node_content'})); + my $tree; + if ($command->{'manual_content'}) { + $tree = {'type' => '_code', + 'contents' => [{'text' => '('}, @{$command->{'manual_content'}}, + {'text' => ')'}, @$node_content]}; + } else { + $tree = {'type' => '_code', + 'contents' => $node_content}; + } + if ($type eq 'tree') { + return $tree; + } else { + if ($type eq 'string') { + $tree = {'type' => '_string', + 'contents' => [$tree]}; + } + my $result = $self->convert_tree_new_formatting_context( + # FIXME check if $document_global_context argument is really needed + $tree, $command->{'cmdname'}, 'command_text manual_content'); + return $result; + } + } + + my $target = $self->_get_target($command); + if ($target) { + my $explanation; + $explanation = "command_text:$type \@$command->{'cmdname'}" + if ($command->{'cmdname'}); + if (defined($target->{$type})) { + return $target->{$type}; + } + my $tree; + if (!$target->{'tree'}) { + if (defined($command->{'type'}) + and $command->{'type'} eq 'special_element') { + my $special_element_variety + = $command->{'extra'}->{'special_element_variety'}; + $tree + = $self->special_element_info('heading_tree', + $special_element_variety); + $tree = {} if (!defined($tree)); + $explanation = "command_text $special_element_variety"; + } elsif ($command->{'cmdname'} and ($command->{'cmdname'} eq 'node' + or $command->{'cmdname'} eq 'anchor')) { + # FIXME is it possible not to have contents (nor args)? + $tree = {'type' => '_code', + 'contents' => $command->{'args'}->[0]->{'contents'}}; + } elsif ($command->{'cmdname'} and ($command->{'cmdname'} eq 'float')) { + $tree = $self->float_type_number($command); + } elsif ($command->{'extra'} + and $command->{'extra'}->{'missing_argument'}) { + if ($type eq 'tree' or $type eq 'tree_nonumber') { + return {}; + } else { + return ''; + } + } else { + my $section_arg_contents = []; + $section_arg_contents = $command->{'args'}->[0]->{'contents'} + if $command->{'args'}->[0]->{'contents'}; + if ($command->{'structure'} + and defined($command->{'structure'}->{'section_number'}) + and ($self->get_conf('NUMBER_SECTIONS') + or !defined($self->get_conf('NUMBER_SECTIONS')))) { + if ($command->{'cmdname'} eq 'appendix' + and $command->{'structure'}->{'section_level'} == 1) { + $tree = $self->gdt('Appendix {number} {section_title}', + {'number' => {'text' => $command->{'structure'} + ->{'section_number'}}, + 'section_title' + => {'contents' => $section_arg_contents}}); + } else { + # TRANSLATORS: numbered section title + $tree = $self->gdt('{number} {section_title}', + {'number' => {'text' => $command->{'structure'} + ->{'section_number'}}, + 'section_title' + => {'contents' => $section_arg_contents}}); + } + } else { + $tree = {'contents' => $section_arg_contents}; + } + + $target->{'tree_nonumber'} + = {'contents' => $section_arg_contents}; + } + $target->{'tree'} = $tree; + } else { + $tree = $target->{'tree'}; + } + return $target->{'tree_nonumber'} if ($type eq 'tree_nonumber' + and $target->{'tree_nonumber'}); + return $tree if ($type eq 'tree' or $type eq 'tree_nonumber'); + + $self->_new_document_context($command->{'cmdname'}, $explanation); + + if ($type eq 'string') { + $tree = {'type' => '_string', + 'contents' => [$tree]}; + } + + if ($type =~ /^(.*)_nonumber$/) { + $tree = $target->{'tree_nonumber'} + if (defined($target->{'tree_nonumber'})); + } + $self->{'ignore_notice'}++; + push @{$self->{'referred_command_stack'}}, $command; + $target->{$type} = $self->_convert($tree, $explanation); + pop @{$self->{'referred_command_stack'}}; + $self->{'ignore_notice'}--; + + $self->_pop_document_context(); + return $target->{$type}; + } + return undef; +} + +# Return the element in the tree that $LABEL refers to. +sub label_command($$) +{ + my $self = shift; + my $label = shift; + if (!defined($label)) { + cluck; + } + if ($self->{'labels'}) { + return $self->{'labels'}->{$label}; + } + return undef; +} + +sub special_direction_element($$) +{ + my $self = shift; + my $direction = shift; + return $self->{'special_elements_directions'}->{$direction}; +} + +sub command_name_special_element_information($$) +{ + my $self = shift; + my $cmdname = shift; + + my $special_element_variety; + if (exists($contents_command_special_element_variety{$cmdname})) { + $special_element_variety + = $contents_command_special_element_variety{$cmdname}; + } elsif ($cmdname eq 'footnote') { + $special_element_variety = 'footnotes'; + } else { + return (undef, undef, undef, undef); + } + my $special_element_direction + = $self->special_element_info('direction', $special_element_variety); + my $special_element + = $self->special_direction_element($special_element_direction); + my $class_base + = $self->special_element_info('class', $special_element_variety); + return ($special_element_variety, $special_element, $class_base, + $special_element_direction); +} + +sub global_direction_element($$) +{ + my $self = shift; + my $direction = shift; + return $self->{'global_target_elements_directions'}->{$direction}; +} + +sub get_element_root_command_element($$) +{ + my $self = shift; + my $element = shift; + + my ($root_element, $root_command) = _html_get_tree_root_element($self, $element); + if (defined($root_command)) { + if ($self->get_conf('USE_NODES')) { + if ($root_command->{'cmdname'} and $root_command->{'cmdname'} eq 'node') { + return ($root_element, $root_command); + } elsif ($root_command->{'extra'} + and $root_command->{'extra'}->{'associated_node'}) { + return ($root_element, $root_command->{'extra'}->{'associated_node'}); + } + } elsif ($root_command->{'cmdname'} + and $root_command->{'cmdname'} eq 'node' + and $root_command->{'extra'} + and $root_command->{'extra'}->{'associated_section'}) { + return ($root_element, $root_command->{'extra'}->{'associated_section'}); + } + } + return ($root_element, $root_command); +} + +my %valid_direction_return_type = ( + # a string that can be used in a href linking to the direction + 'href' => 1, + # a string representing the direction that can be used in + # context where only entities are available (attributes) + 'string' => 1, + # a string representing the direction to be used in contexts + # not restricted in term of available formatting (ie with HTML elements) + 'text' => 1, + # Texinfo tree element representing the direction + 'tree' => 1, + # string representing the target, typically used as id and in href + 'target' => 1, + # same as 'text', but select node in priority + 'node' => 1, + # same as 'text_nonumber' but select section in priority + 'section' => 1 +); + +foreach my $no_number_type ('text', 'tree', 'string') { + # without section number + $valid_direction_return_type{$no_number_type .'_nonumber'} = 1; +} + +# sub from_element_direction($SELF, $DIRECTION, $TYPE, $SOURCE_ELEMENT, +# $SOURCE_FILENAME, $SOURCE_FOR_MESSAGES) +# +# Return text used for linking from $SOURCE_ELEMENT in direction $DIRECTION. +# The text returned depends on $TYPE. +# +# This is used both for tree unit elements and external nodes +# +# If $SOURCE_ELEMENT is undef, $self->{'current_root_element'} is used. +# +# $SOURCE_FOR_MESSAGES is an element used for messages formatting, to get a +# location in input file. It is better to choose the node and not the +# sectioning command associated with the element, as the error messages +# are about external nodes not found. +# +# $self->{'current_root_element'} undef happens at least when there is no +# output file, or for the table of content when frames are used. That call +# would result for instance from from_element_direction being called from +# _get_links, itself called from 'format_begin_file' which, in the default case +# points to _default_format_begin_file. +# TODO are there other cases? +sub from_element_direction($$$;$$$) +{ + my $self = shift; + my $direction = shift; + my $type = shift; + my $source_element = shift; + my $source_filename = shift; + # for messages only + my $source_command = shift; + + my $target_element; + my $command; + my $target; + + $source_element = $self->{'current_root_element'} if (!defined($source_element)); + $source_filename = $self->{'current_filename'} if (!defined($source_filename)); + + if (!$valid_direction_return_type{$type}) { + print STDERR "Incorrect type $type in from_element_direction call\n"; + return undef; + } + my $global_target_element = $self->global_direction_element($direction); + if ($global_target_element) { + $target_element = $global_target_element; + # output TOP_NODE_UP related infos even if element is not + # defined which should mostly correspond to cases when there is no + # output file, for example in the tests. + } elsif ((not defined($source_element) + or ($source_element + and $self->element_is_tree_unit_top($source_element))) + and defined($self->get_conf('TOP_NODE_UP_URL')) + and ($direction eq 'Up' or $direction eq 'NodeUp')) { + if ($type eq 'href') { + return $self->get_conf('TOP_NODE_UP_URL'); + } elsif ($type eq 'text' or $type eq 'node' or $type eq 'string' + or $type eq 'section') { + return $self->get_conf('TOP_NODE_UP'); + } else { + cluck("type $type not available for TOP_NODE_UP\n"); + return ''; + } + } elsif (not $target_element and $source_element + and $source_element->{'structure'} + and $source_element->{'structure'}->{'directions'} + and $source_element->{'structure'}->{'directions'}->{$direction}) { + $target_element + = $source_element->{'structure'}->{'directions'}->{$direction}; + } + + if ($target_element) { + ######## debug + if (!$target_element->{'type'}) { + die "No type for element_target $direction $target_element: " + . Texinfo::Common::debug_print_element_details($target_element, 1) + . "directions :" + . Texinfo::Structuring::print_element_directions($source_element); + } + ######## + if ($target_element->{'type'} eq 'external_node') { + my $external_node = $target_element->{'extra'}; + #print STDERR "FROM_ELEMENT_DIRECTION ext node $type $direction\n" + # if ($self->get_conf('DEBUG')); + if ($type eq 'href') { + return $self->command_href($external_node, $source_filename, + $source_command); + } elsif ($type eq 'text' or $type eq 'node') { + return $self->command_text($external_node); + } elsif ($type eq 'string') { + return $self->command_text($external_node, $type); + } + } elsif ($type eq 'node') { + if ($target_element->{'extra'} + and $target_element->{'extra'}->{'unit_command'}) { + if ($target_element->{'extra'}->{'unit_command'}->{'cmdname'} eq 'node') { + $command = $target_element->{'extra'}->{'unit_command'}; + } elsif ($target_element->{'extra'}->{'unit_command'}->{'extra'} + and $target_element->{'extra'}->{'unit_command'} + ->{'extra'}->{'associated_node'}) { + $command = $target_element->{'extra'}->{'unit_command'} + ->{'extra'}->{'associated_node'}; + } + } + $target = $self->{'targets'}->{$command} if ($command); + $type = 'text'; + } elsif ($type eq 'section') { + if ($target_element->{'extra'} + and $target_element->{'extra'}->{'unit_command'}) { + if ($target_element->{'extra'}->{'unit_command'}->{'cmdname'} ne 'node') { + $command = $target_element->{'extra'}->{'unit_command'}; + } elsif ($target_element->{'extra'}->{'unit_command'}->{'extra'} + and $target_element->{'extra'}->{'unit_command'} + ->{'extra'}->{'associated_section'}) { + $command = $target_element->{'extra'}->{'unit_command'} + ->{'extra'}->{'associated_section'}; + } + } + $target = $self->{'targets'}->{$command} if ($command); + $type = 'text_nonumber'; + } else { + if (defined($target_element->{'type'}) + and $target_element->{'type'} eq 'special_element') { + $command = $target_element; + } elsif ($target_element->{'extra'}) { + $command = $target_element->{'extra'}->{'unit_command'}; + } + if ($type eq 'href') { + if (defined($command)) { + return $self->command_href($command, $source_filename); + } else { + return ''; + } + } + $target = $self->{'targets'}->{$command} if ($command); + } + } elsif ($self->special_direction_element($direction)) { + $target_element = $self->special_direction_element($direction); + $command = $target_element; + if ($type eq 'href') { + return $self->command_href($target_element, $source_filename); + } + $target = $self->{'targets'}->{$target_element}; + } else { + return undef; + } + + if ($target and exists($target->{$type})) { + return $target->{$type}; + } elsif ($type eq 'target') { + return undef; + } elsif ($command) { + #print STDERR "FROM_ELEMENT_DIRECTION $type $direction\n" + # if ($self->get_conf('DEBUG')); + return $self->command_text($command, $type); + } +} + + +my %valid_direction_string_type = ( + # accesskey associated to the direction + 'accesskey' => 1, + # direction button name + 'button' => 1, + # description of the direction + 'description' => 1, + # section number corresponding to the example in About text + 'example' => 1, + # rel/ref string associated to the direction + 'rel' => 1, + # few words text associated to the direction + 'text' => 1, +); + +my %valid_direction_string_context = ( + 'normal' => 1, + 'string' => 1, +); + +my %direction_type_translation_context = ( + 'button' => 'button label', + 'description' => 'description', + 'text' => 'string', +); + +sub direction_string($$$;$) +{ + my $self = shift; + my $direction = shift; + my $string_type = shift; + my $context = shift; + + if (!$valid_direction_string_type{$string_type}) { + print STDERR "Incorrect type $string_type in direction_string call\n"; + return undef; + } + + $context = 'normal' if (!defined($context)); + + if (!$valid_direction_string_context{$context}) { + print STDERR "Incorrect context $context in direction_string call\n"; + return undef; + } + + $direction =~ s/^FirstInFile//; + + if (not exists($self->{'directions_strings'}->{$string_type}->{$direction}) + or not exists($self->{'directions_strings'}->{$string_type} + ->{$direction}->{$context})) { + $self->{'directions_strings'}->{$string_type}->{$direction} = {} + if not exists($self->{'directions_strings'}->{$string_type}->{$direction}); + my $translated_directions_strings = $self->{'translated_direction_strings'}; + if (defined($translated_directions_strings->{$string_type} + ->{$direction}->{'converted'})) { + # translate already converted direction strings + my $converted_directions + = $translated_directions_strings->{$string_type}->{$direction}->{'converted'}; + my $context_converted_string; + if ($converted_directions->{$context}) { + $context_converted_string = $converted_directions->{$context}; + } elsif ($context eq 'string' + and defined($converted_directions->{'normal'})) { + $context_converted_string = $converted_directions->{'normal'}; + } + if (defined($context_converted_string)) { + my $result_string + = $self->gdt($context_converted_string, undef, undef, 'translated_text'); + $self->{'directions_strings'}->{$string_type}->{$direction}->{$context} + = $self->substitute_html_non_breaking_space($result_string); + } else { + $self->{'directions_strings'}->{$string_type}->{$direction}->{$context} + = undef; + } + } elsif (defined($translated_directions_strings->{$string_type} + ->{$direction}->{'to_convert'})) { + # translate direction strings that need to be translated and converted + my $context_string = $direction; + $context_string .= ' (current section)' if ($direction eq 'This'); + $context_string = $context_string.' direction ' + .$direction_type_translation_context{$string_type}; + my $translated_tree + = $self->pgdt($context_string, + $translated_directions_strings->{$string_type} + ->{$direction}->{'to_convert'}); + my $converted_tree; + if ($context eq 'string') { + $converted_tree = { + 'type' => '_string', + 'contents' => [$translated_tree]}; + } else { + $converted_tree = $translated_tree; + } + my $result_string = $self->convert_tree_new_formatting_context($converted_tree, + "direction $direction", undef, "direction $direction"); + $self->{'directions_strings'}->{$string_type}->{$direction}->{$context} + = $result_string; + } else { + # FIXME or '' + $self->{'directions_strings'}->{$string_type}->{$direction}->{$context} + = undef; + } + } + return $self->{'directions_strings'}->{$string_type}->{$direction}->{$context}; +} + +my %default_translated_special_element_info; + +# if SPECIAL_ELEMENT_VARIETY is not set, return all the varieties +sub special_element_info($$;$) { + my $self = shift; + my $type = shift; + my $special_element_variety = shift; + + if ($self->{'translated_special_element_info'}->{$type}) { + my $translated_special_element_info + = $self->{'translated_special_element_info'}->{$type}->[1]; + if (not defined($special_element_variety)) { + return sort(keys(%{$translated_special_element_info})); + } + + if (not exists($self->{'special_element_info'}->{$type} + ->{$special_element_variety})) { + my $special_element_info_string = $translated_special_element_info + ->{$special_element_variety}; + my $translated_tree; + if (defined($special_element_info_string)) { + my $translation_context = "$special_element_variety section heading"; + $translated_tree = $self->pgdt($translation_context, + $special_element_info_string); + } + $self->{'special_element_info'}->{$type}->{$special_element_variety} + = $translated_tree; + } + } + if (not defined($special_element_variety)) { + return sort(keys(%{$self->{'special_element_info'}->{$type}})); + } + return $self->{'special_element_info'}->{$type}->{$special_element_variety}; +} + +# API for misc conversion and formatting functions + +# it is considered 'top' only if element corresponds to @top or +# element is a node +sub element_is_tree_unit_top($$) +{ + my $self = shift; + my $element = shift; + my $top_element = $self->global_direction_element('Top'); + return (defined($top_element) and $top_element eq $element + and $element->{'extra'} + and $element->{'extra'}->{'unit_command'} + and ($element->{'extra'}->{'unit_command'}->{'cmdname'} eq 'node' + or $element->{'extra'}->{'unit_command'}->{'cmdname'} eq 'top')); +} + +my %default_formatting_references; +sub default_formatting_function($$) +{ + my $self = shift; + my $format = shift; + return $default_formatting_references{$format}; +} + +sub formatting_function($$) +{ + my $self = shift; + my $format = shift; + return $self->{'formatting_function'}->{$format}; +} + +my %defaults_format_special_body_contents; + +sub defaults_special_element_body_formatting($$) +{ + my $self = shift; + my $special_element_variety = shift; + + return $defaults_format_special_body_contents{$special_element_variety}; +} + +sub special_element_body_formatting($$) +{ + my $self = shift; + my $special_element_variety = shift; + + return $self->{'special_element_body'}->{$special_element_variety}; +} + +# Return the default for the function references used for +# the formatting of commands, in case a user still wants to call +# default @-commands formatting functions when replacing functions, +# using code along +# &{$self->default_command_conversion($cmdname)}($self, $cmdname, $command, args, $content) +my %default_commands_conversion; + +sub default_command_conversion($$) +{ + my $self = shift; + my $command = shift; + return $default_commands_conversion{$command}; +} + +sub command_conversion($$) +{ + my $self = shift; + my $command = shift; + return $self->{'commands_conversion'}->{$command}; +} + +my %default_commands_open; + +sub default_command_open($$) +{ + my $self = shift; + my $command = shift; + return $default_commands_open{$command}; +} + +# used for customization only (in t2h_singular.init) +sub get_value($$) +{ + my $self = shift; + my $value = shift; + if (defined($self->{'values'}) + and exists ($self->{'values'}->{$value})) { + return $self->{'values'}->{$value}; + } else { + return undef; + } +} + +# $INITIALIZATION_VALUE is only used for the initialization. +# If it is not a reference, it is turned into a scalar reference. +sub shared_conversion_state($$;$) +{ + my $self = shift; + my $state_name = shift; + my $initialization_value = shift; + + if (not defined($self->{'shared_conversion_state'}->{$state_name})) { + if (not ref($initialization_value)) { + $self->{'shared_conversion_state'}->{$state_name} = \$initialization_value; + } else { + $self->{'shared_conversion_state'}->{$state_name} = $initialization_value; + } + } + return $self->{'shared_conversion_state'}->{$state_name}; +} + +sub register_footnote($$$$$$$) +{ + my ($self, $command, $footid, $docid, $number_in_doc, + $footnote_location_filename, $multi_expanded_region) = @_; + my $in_skipped_node_top + = $self->shared_conversion_state('in_skipped_node_top', 0); + if ($$in_skipped_node_top != 1) { + push @{$self->{'pending_footnotes'}}, [$command, $footid, $docid, + $number_in_doc, $footnote_location_filename, $multi_expanded_region]; + } +} + +sub get_pending_footnotes($) +{ + my $self = shift; + + my @result = @{$self->{'pending_footnotes'}}; + @{$self->{'pending_footnotes'}} = (); + return @result; +} + + +# API to register, cancel and get inline content that should be output +# when in an inline situation, mostly in a paragraph or preformatted +sub register_pending_formatted_inline_content($$$) +{ + my $self = shift; + my $category = shift; + my $inline_content = shift; + + if (not defined($self->{'pending_inline_content'})) { + $self->{'pending_inline_content'} = []; + } + push @{$self->{'pending_inline_content'}}, [$category, $inline_content]; +} + +# cancel only the first pending content for the category +sub cancel_pending_formatted_inline_content($$$) +{ + my $self = shift; + my $category = shift; + + if (defined($self->{'pending_inline_content'})) { + my @other_category_contents = (); + while (@{$self->{'pending_inline_content'}}) { + my $category_inline_content = pop @{$self->{'pending_inline_content'}}; + if ($category_inline_content->[0] eq $category) { + push @{$self->{'pending_inline_content'}}, @other_category_contents; + return $category_inline_content->[1]; + } + unshift @other_category_contents, $category_inline_content; + } + push @{$self->{'pending_inline_content'}}, @other_category_contents; + } + return undef; +} + +sub get_pending_formatted_inline_content($) { + my $self = shift; + + if (not defined($self->{'pending_inline_content'})) { + return ''; + } else { + my $result = ''; + foreach my $category_inline_content (@{$self->{'pending_inline_content'}}) { + if (defined($category_inline_content->[1])) { + $result .= $category_inline_content->[1]; + } + } + $self->{'pending_inline_content'} = undef; + return $result; + } +} + +# API to associate inline content to an element, typically +# paragraph or preformatted. Allows to associate the pending +# content to the first inline element. +sub associate_pending_formatted_inline_content($$$) { + my $self = shift; + my $element = shift; + my $inline_content = shift; + + if (not $self->{'associated_inline_content'}->{$element}) { + $self->{'associated_inline_content'}->{$element} = ''; + } + $self->{'associated_inline_content'}->{$element} .= $inline_content; +} + +sub get_associated_formatted_inline_content($$) { + my $self = shift; + my $element = shift; + + if ($self->{'associated_inline_content'}->{$element}) { + my $result = $self->{'associated_inline_content'}->{$element}; + delete $self->{'associated_inline_content'}->{$element}; + return $result; + } + return ''; +} + +# API to register an information to a file and get it. To be able to +# set an information during conversion and get it back during headers +# and footers conversion +sub register_file_information($$;$) +{ + my $self = shift; + my $key = shift; + my $value = shift; + + $self->{'files_information'}->{$self->{'current_filename'}} = {} + if (!$self->{'files_information'}->{$self->{'current_filename'}}); + $self->{'files_information'}->{$self->{'current_filename'}}->{$key} = $value; +} + +sub get_file_information($$;$) +{ + my $self = shift; + my $key = shift; + my $filename = shift; + + if (not defined($filename)) { + $filename = $self->{'current_filename'}; + } + if (not defined($filename) + or not $self->{'files_information'} + or not $self->{'files_information'}->{$filename} + or not exists($self->{'files_information'}->{$filename}->{$key})) { + return (0, undef); + } + return (1, $self->{'files_information'}->{$filename}->{$key}) +} + +# information from converter available 'read-only', in general set up before +# really starting the formatting (except for current_filename). +# 'floats', 'global_commands' and 'structuring' are set up in the generic +# converter +my %available_converter_info; +foreach my $converter_info ('copying_comment', 'current_filename', + 'destination_directory', 'document_name', 'documentdescription_string', + 'floats', 'global_commands', + 'index_entries', 'index_entries_by_letter', 'indices_information', + 'jslicenses', 'line_break_element', 'non_breaking_space', 'paragraph_symbol', + 'simpletitle_command_name', 'simpletitle_tree', 'structuring', + 'title_string', 'title_tree', 'title_titlepage') { + $available_converter_info{$converter_info} = 1; +} + +sub get_info($$) +{ + my $self = shift; + my $converter_info = shift; + + if (not $available_converter_info{$converter_info}) { + confess("BUG: $converter_info not an available converter info"); + } + if (defined($self->{'converter_info'}->{$converter_info})) { + if (ref($self->{'converter_info'}->{$converter_info}) eq 'SCALAR') { + return ${$self->{'converter_info'}->{$converter_info}}; + } else { + return $self->{'converter_info'}->{$converter_info}; + } + #} else { + # cluck(); + } +} + +# This function should be used in formatting functions when some +# Texinfo tree need to be converted. +sub convert_tree_new_formatting_context($$;$$$) +{ + my $self = shift; + my $tree = shift; + my $context_string = shift; + my $multiple_pass = shift; + my $document_global_context = shift; + + my $context_string_str = ''; + if (defined($context_string)) { + $self->_new_document_context($context_string, $document_global_context); + $context_string_str = "C($context_string)"; + } + my $multiple_pass_str = ''; + if ($multiple_pass) { + $self->{'ignore_notice'}++; + push @{$self->{'multiple_pass'}}, $multiple_pass; + $multiple_pass_str = '|M' + } + print STDERR "new_fmt_ctx ${context_string_str}${multiple_pass_str}\n" + if ($self->get_conf('DEBUG')); + my $result = $self->convert_tree($tree, "new_fmt_ctx ${context_string_str}"); + if (defined($context_string)) { + $self->_pop_document_context(); + } + if ($multiple_pass) { + $self->{'ignore_notice'}--; + pop @{$self->{'multiple_pass'}}; + } + return $result; +} + +my %defaults = ( + 'AVOID_MENU_REDUNDANCY' => 0, + 'BIG_RULE' => '
', + 'BODYTEXT' => undef, + 'CHAPTER_HEADER_LEVEL' => 2, + 'CLOSE_QUOTE_SYMBOL' => undef, + 'CONTENTS_OUTPUT_LOCATION' => 'after_top', + 'CONVERT_TO_LATEX_IN_MATH' => undef, + 'COMPLEX_FORMAT_IN_TABLE' => 0, + 'COPIABLE_LINKS' => 1, + 'DATE_IN_HEADER' => 0, + 'DEFAULT_RULE' => '
', + 'documentlanguage' => 'en', + 'DOCTYPE' => '', + 'DO_ABOUT' => 0, + 'OUTPUT_CHARACTERS' => 0, + 'EXTENSION' => 'html', + 'EXTERNAL_CROSSREF_EXTENSION' => undef, # based on EXTENSION + 'FOOTNOTE_END_HEADER_LEVEL' => 4, + 'FOOTNOTE_SEPARATE_HEADER_LEVEL' => 4, + 'FORMAT_MENU' => 'sectiontoc', + 'HEADERS' => 1, + 'INDEX_ENTRY_COLON' => '', +# if set style is added in attribute. + 'INLINE_CSS_STYLE' => 0, + 'JS_WEBLABELS' => 'generate', + 'JS_WEBLABELS_FILE' => 'js_licenses.html', # no clash with node name + 'MAX_HEADER_LEVEL' => 4, + 'MENU_ENTRY_COLON' => ':', + 'MENU_SYMBOL' => undef, + 'MONOLITHIC' => 1, + 'NO_CUSTOM_HTML_ATTRIBUTE' => 0, +# if set, no css is used. + 'NO_CSS' => 0, + 'NO_NUMBER_FOOTNOTE_SYMBOL' => '*', + 'NODE_NAME_IN_MENU' => 1, + 'OPEN_QUOTE_SYMBOL' => undef, + 'OUTPUT_ENCODING_NAME' => 'utf-8', + 'SECTION_NAME_IN_TITLE' => 0, + 'SHORT_TOC_LINK_TO_TOC' => 1, + 'SHOW_TITLE' => undef, + 'SPLIT' => 'node', + 'TOP_FILE' => 'index.html', # ignores EXTENSION + 'TOP_NODE_FILE_TARGET' => 'index.html', # ignores EXTENSION + 'USE_ACCESSKEY' => 1, + 'USE_ISO' => 1, + 'USE_LINKS' => 1, + 'USE_NODES' => 1, + 'USE_NODE_DIRECTIONS' => undef, + 'USE_REL_REV' => 1, + 'USE_TITLEPAGE_FOR_TITLE' => 1, + 'WORDS_IN_PAGE' => 300, + 'XREF_USE_NODE_NAME_ARG' => undef, + 'XREF_USE_FLOAT_LABEL' => 0, + 'xrefautomaticsectiontitle' => 'on', + + # obsolete + 'FRAMESET_DOCTYPE' => '', + + # Non-string customization variables + # _default_panel_button_dynamic_direction use nodes direction based on USE_NODE_DIRECTIONS + # or USE_NODES if USE_NODE_DIRECTIONS is undefined + 'SECTION_BUTTONS' => [[ 'Next', \&_default_panel_button_dynamic_direction ], + [ 'Prev', \&_default_panel_button_dynamic_direction ], + [ 'Up', \&_default_panel_button_dynamic_direction ], ' ', + 'Contents', 'Index', 'About'], + 'SECTION_FOOTER_BUTTONS' => [[ 'Next', \&_default_panel_button_dynamic_direction_section_footer ], + [ 'Prev', \&_default_panel_button_dynamic_direction_section_footer ], + [ 'Up', \&_default_panel_button_dynamic_direction_section_footer ], ' ', + 'Contents', 'Index'], + 'LINKS_BUTTONS' => ['Top', 'Index', 'Contents', 'About', + 'NodeUp', 'NodeNext', 'NodePrev'], + 'NODE_FOOTER_BUTTONS' => [[ 'Next', \&_default_panel_button_dynamic_direction_node_footer ], + [ 'Prev', \&_default_panel_button_dynamic_direction_node_footer ], + [ 'Up', \&_default_panel_button_dynamic_direction_node_footer ], + ' ', 'Contents', 'Index'], + 'ACTIVE_ICONS' => undef, + 'PASSIVE_ICONS' => undef, + # obsolete + 'frame_pages_file_string' => { + 'Frame' => '_frame', + 'Toc_Frame' => '_toc_frame', + }, + + # non-customization variable converter defaults + 'converted_format' => 'html', +); + +foreach my $buttons ('CHAPTER_BUTTONS', 'MISC_BUTTONS', 'TOP_BUTTONS') { + $defaults{$buttons} = [@{$defaults{'SECTION_BUTTONS'}}]; +} + +foreach my $buttons ('CHAPTER_FOOTER_BUTTONS') { + $defaults{$buttons} = [@{$defaults{'SECTION_FOOTER_BUTTONS'}}]; +} + + +my %default_special_element_info = ( + + 'class' => { + 'about' => 'about', + 'contents' => 'contents', + 'shortcontents' => 'shortcontents', + 'footnotes' => 'footnotes', + }, + + 'direction' => { + 'about' => 'About', + 'contents' => 'Contents', + 'shortcontents' => 'Overview', + 'footnotes' => 'Footnotes', + }, + + 'order' => { + 'contents' => 30, + 'shortcontents' => 20, + 'footnotes' => 10, + 'about' => 40, + }, + + 'file_string' => { + 'contents' => '_toc', + 'shortcontents' => '_ovr', + 'footnotes' => '_fot', + 'about' => '_abt', + }, + + 'target' => { + 'shortcontents' => 'SEC_Shortcontents', + 'contents' => 'SEC_Contents', + 'footnotes' => 'SEC_Footnotes', + 'about' => 'SEC_About', + }, +); + +# translation context should be consistent with special_element_info() +%default_translated_special_element_info = ( + + 'heading' => { + 'about' => Texinfo::Common::pgdt('about section heading', + 'About This Document'), + 'contents' => Texinfo::Common::pgdt('contents section heading', + 'Table of Contents'), + 'shortcontents' => Texinfo::Common::pgdt( + 'shortcontents section heading', + 'Short Table of Contents'), + 'footnotes' => Texinfo::Common::pgdt('footnotes section heading', + 'Footnotes'), + }, +); + +my @global_directions = ('First', 'Last', 'Index', 'Top'); +my %global_and_special_directions; +foreach my $global_direction (@global_directions) { + $global_and_special_directions{$global_direction} = 1; +} +foreach my $special_direction (values( + %{$default_special_element_info{'direction'}})) { + $global_and_special_directions{$special_direction} = 1; +} + +my %default_converted_directions_strings = ( + + # see http://www.w3.org/TR/REC-html40/types.html#type-links + 'rel' => + { + 'Top', 'start', + 'Contents', 'contents', + 'Overview', '', + 'Index', 'index', + 'This', '', + 'Back', 'prev', + 'FastBack', '', + 'Prev', 'prev', + 'Up', 'up', + 'Next', 'next', + 'NodeUp', 'up', + 'NodeNext', 'next', + 'NodePrev', 'prev', + 'NodeForward', '', + 'NodeBack', '', + 'Forward', 'next', + 'FastForward', '', + 'About' , 'help', + 'First', '', + 'Last', '', + 'NextFile', 'next', + 'PrevFile', 'prev', + }, + + 'accesskey' => + { + 'Top', '', + 'Contents', '', + 'Overview', '', + 'Index', '', + 'This', '', + 'Back', 'p', + 'FastBack', '', + 'Prev', 'p', + 'Up', 'u', + 'Next', 'n', + 'NodeUp', 'u', + 'NodeNext', 'n', + 'NodePrev', 'p', + 'NodeForward', '', + 'NodeBack', '', + 'Forward', 'n', + 'FastForward', '', + 'About' , '', + 'First', '', + 'Last', '', + 'NextFile', '', + 'PrevFile', '', + }, + + 'example' => + { + 'Top', ' '.$html_default_entity_nbsp.' ', + 'Contents', ' '.$html_default_entity_nbsp.' ', + 'Overview', ' '.$html_default_entity_nbsp.' ', + 'Index', ' '.$html_default_entity_nbsp.' ', + 'This', '1.2.3', + 'Back', '1.2.2', + 'FastBack', '1', + 'Prev', '1.2.2', + 'Up', '1.2', + 'Next', '1.2.4', + 'NodeUp', '1.2', + 'NodeNext', '1.2.4', + 'NodePrev', '1.2.2', + 'NodeForward', '1.2.4', + 'NodeBack', '1.2.2', + 'Forward', '1.2.4', + 'FastForward', '2', + 'About', ' '.$html_default_entity_nbsp.' ', + 'First', '1.', + 'Last', '1.2.4', + 'NextFile', ' '.$html_default_entity_nbsp.' ', + 'PrevFile', ' '.$html_default_entity_nbsp.' ', + }, +); + +# translation contexts should be consistent with +# %direction_type_translation_context. If the direction is not used +# as is, it should also be taken into account in direction_string(). +# For now 'This' becomes 'This (current section)'. +my %default_translated_directions_strings = ( + 'text' => { + ' ' => {'converted' => ' '.$html_default_entity_nbsp.' '}, + 'Top' => {'to_convert' + => Texinfo::Common::pgdt('Top direction string', 'Top')}, + 'Contents' => {'to_convert' + => Texinfo::Common::pgdt('Contents direction string', 'Contents')}, + 'Overview' => {'to_convert' + => Texinfo::Common::pgdt( + 'Overview direction string', 'Overview')}, + 'Index' => {'to_convert' + => Texinfo::Common::pgdt('Index direction string', 'Index')}, + 'This' => {'to_convert' + => Texinfo::Common::pgdt('This (current section) direction string', + 'current')}, + 'Back' => {'converted' => ' < '}, + 'FastBack' => {'converted' => ' << '}, + 'Prev' => {'to_convert' + => Texinfo::Common::pgdt('Prev direction string', 'Prev')}, + 'Up' => {'to_convert' + => Texinfo::Common::pgdt('Up direction string', ' Up ')}, + 'Next' => {'to_convert' + => Texinfo::Common::pgdt('Next direction string', 'Next')}, + 'NodeUp' => {'to_convert' + => Texinfo::Common::pgdt('NodeUp direction string', 'Up')}, + 'NodeNext' => {'to_convert' + => Texinfo::Common::pgdt('NodeNext direction string', 'Next')}, + 'NodePrev' => {'to_convert' + => Texinfo::Common::pgdt('NodePrev direction string', 'Previous')}, + 'NodeForward' => {'to_convert' + => Texinfo::Common::pgdt('NodeForward direction string', 'Forward node')}, + 'NodeBack' => {'to_convert' + => Texinfo::Common::pgdt('NodeBack direction string', 'Back node')}, + 'Forward' => {'converted' => ' > '}, + 'FastForward' => {'converted' => ' >> '}, + 'About' => {'converted' => ' ? '}, + 'First' => {'converted' => ' |< '}, + 'Last' => {'converted' => ' >| '}, + 'NextFile' => {'to_convert' + => Texinfo::Common::pgdt('NextFile direction string', 'Next file')}, + 'PrevFile' => {'to_convert' + => Texinfo::Common::pgdt('PrevFile direction string', 'Previous file')}, + }, + + 'description' => { + 'Top' => {'to_convert' => Texinfo::Common::pgdt( + 'Top direction description', 'Cover (top) of document')}, + 'Contents' => {'to_convert' => Texinfo::Common::pgdt( + 'Contents direction description', + 'Table of contents')}, + 'Overview' => {'to_convert' => Texinfo::Common::pgdt( + 'Overview direction description', + 'Short table of contents')}, + 'Index' => {'to_convert' => Texinfo::Common::pgdt( + 'Index direction description', 'Index')}, + 'This' => {'to_convert' => Texinfo::Common::pgdt( + 'This (current section) direction description', + 'Current section')}, + 'Back' => {'to_convert' => Texinfo::Common::pgdt( + 'Back direction description', + 'Previous section in reading order')}, + 'FastBack' => {'to_convert' => Texinfo::Common::pgdt( + 'FastBack direction description', + 'Beginning of this chapter or previous chapter')}, + 'Prev' => {'to_convert' => Texinfo::Common::pgdt( + 'Prev direction description', + 'Previous section on same level')}, + 'Up' => {'to_convert' => Texinfo::Common::pgdt( + 'Up direction description', 'Up section')}, + 'Next' => {'to_convert' => Texinfo::Common::pgdt( + 'Next direction description', 'Next section on same level')}, + 'NodeUp' => {'to_convert' => Texinfo::Common::pgdt( + 'NodeUp direction description', 'Up node')}, + 'NodeNext' => {'to_convert' => Texinfo::Common::pgdt( + 'NodeNext direction description', 'Next node')}, + 'NodePrev' => {'to_convert' => Texinfo::Common::pgdt( + 'NodePrev direction description', 'Previous node')}, + 'NodeForward' => {'to_convert' => Texinfo::Common::pgdt( + 'NodeForward direction description', + 'Next node in node reading order')}, + 'NodeBack' => {'to_convert' => Texinfo::Common::pgdt( + 'NodeBack direction description', + 'Previous node in node reading order')}, + 'Forward' => {'to_convert' => Texinfo::Common::pgdt( + 'Forward direction description', + 'Next section in reading order')}, + 'FastForward' => {'to_convert' => Texinfo::Common::pgdt( + 'FastForward direction description', 'Next chapter')}, + 'About' => {'to_convert' => Texinfo::Common::pgdt( + 'About direction description', 'About (help)')}, + 'First' => {'to_convert' => Texinfo::Common::pgdt( + 'First direction description', + 'First section in reading order')}, + 'Last' => {'to_convert' => Texinfo::Common::pgdt( + 'Last direction description', + 'Last section in reading order')}, + 'NextFile' => {'to_convert' => Texinfo::Common::pgdt( + 'NextFile direction description', + 'Forward section in next file')}, + 'PrevFile' => {'to_convert' => Texinfo::Common::pgdt( + 'PrevFile direction description', + 'Back section in previous file')}, + }, + + 'button' => { + ' ' => {'converted' => ' '}, + 'Top' => {'to_convert' + => Texinfo::Common::pgdt('Top direction button label', 'Top')}, + 'Contents' => {'to_convert' + => Texinfo::Common::pgdt('Contents direction button label', 'Contents')}, + 'Overview' => {'to_convert' + => Texinfo::Common::pgdt('Overview direction button label', 'Overview')}, + 'Index' => {'to_convert' + => Texinfo::Common::pgdt('Index direction button label', 'Index')}, + 'This' => {'to_convert' + => Texinfo::Common::pgdt('This direction button label', 'This')}, + 'Back' => {'to_convert' + => Texinfo::Common::pgdt('Back direction button label', 'Back')}, + 'FastBack' => {'to_convert' + => Texinfo::Common::pgdt('FastBack direction button label', 'FastBack')}, + 'Prev' => {'to_convert' + => Texinfo::Common::pgdt('Prev direction button label', 'Prev')}, + 'Up' => {'to_convert' + => Texinfo::Common::pgdt('Up direction button label', 'Up')}, + 'Next' => {'to_convert' + => Texinfo::Common::pgdt('Next direction button label', 'Next')}, + 'NodeUp' => {'to_convert' + => Texinfo::Common::pgdt('NodeUp direction button label', 'NodeUp')}, + 'NodeNext' => {'to_convert' + => Texinfo::Common::pgdt('NodeNext direction button label', 'NodeNext')}, + 'NodePrev' => {'to_convert' + => Texinfo::Common::pgdt('NodePrev direction button label', 'NodePrev')}, + 'NodeForward' => {'to_convert' + => Texinfo::Common::pgdt('NodeForward direction button label', 'NodeForward')}, + 'NodeBack' => {'to_convert' + => Texinfo::Common::pgdt('NodeBack direction button label', 'NodeBack')}, + 'Forward' => {'to_convert' + => Texinfo::Common::pgdt('Forward direction button label', 'Forward')}, + 'FastForward' => {'to_convert' + => Texinfo::Common::pgdt('FastForward direction button label', 'FastForward')}, + 'About' => {'to_convert' + => Texinfo::Common::pgdt('About direction button label', 'About')}, + 'First' => {'to_convert' + => Texinfo::Common::pgdt('First direction button label', 'First')}, + 'Last' => {'to_convert' + => Texinfo::Common::pgdt('Last direction button label', 'Last')}, + 'NextFile' => {'to_convert' + => Texinfo::Common::pgdt('NextFile direction button label', 'NextFile')}, + 'PrevFile' => {'to_convert' + => Texinfo::Common::pgdt('PrevFile direction button label', 'PrevFile')}, + } +); + +sub _translate_names($) +{ + my $self = shift; + print STDERR "\nTRANSLATE_NAMES encoding_name: " + .$self->get_conf('OUTPUT_ENCODING_NAME') + ." documentlanguage: ".$self->get_conf('documentlanguage')."\n" + if ($self->get_conf('DEBUG')); + + # reset strings such that they are translated when needed. + foreach my $string_type (keys(%default_translated_directions_strings)) { + $self->{'directions_strings'}->{$string_type} = {}; + } + + # could also use keys of $self->{'translated_special_element_info'} + foreach my $type (keys(%default_translated_special_element_info)) { + $self->{'special_element_info'}->{$type.'_tree'} = {}; + } + + # delete the tree and formatted results for special elements + # such that they are redone with the new tree when needed. + foreach my $special_element_variety ($self->special_element_info('direction')) { + my $special_element_direction + = $self->special_element_info('direction', $special_element_variety); + my $special_element + = $self->special_direction_element($special_element_direction); + if ($special_element and + $self->{'targets'}->{$special_element}) { + my $target = $self->{'targets'}->{$special_element}; + foreach my $key ('text', 'string', 'tree') { + delete $target->{$key}; + } + } + } + my %translated_commands; + foreach my $context ('normal', 'preformatted', 'string', 'css_string') { + foreach my $command (keys(%{$self->{'no_arg_commands_formatting'} + ->{$context}})) { + if (defined($self->{'no_arg_commands_formatting'} + ->{$context}->{$command}->{'translated_converted'}) + and not $self->{'no_arg_commands_formatting'} + ->{$context}->{$command}->{'unset'}) { + $translated_commands{$command} = 1; + $self->{'no_arg_commands_formatting'}->{$context}->{$command}->{'text'} + = $self->gdt($self->{'no_arg_commands_formatting'} + ->{$context}->{$command}->{'translated_converted'}, + undef, undef, 'translated_text'); + } elsif ($context eq 'normal') { + my $translated_tree; + if (defined($self->{'no_arg_commands_formatting'} + ->{$context}->{$command}->{'translated_to_convert'})) { + $translated_tree = $self->gdt($self->{'no_arg_commands_formatting'} + ->{$context}->{$command}->{'translated_to_convert'}); + } else { + # default translated commands + $translated_tree + = Texinfo::Convert::Utils::translated_command_tree($self, $command); + } + if (defined($translated_tree) and $translated_tree ne '') { + $self->{'no_arg_commands_formatting'}->{$context}->{$command}->{'tree'} + = $translated_tree; + $translated_commands{$command} = 1; + } + } + } + } + foreach my $command (keys(%translated_commands)) { + $self->_complete_no_arg_commands_formatting($command, 1); + } + + print STDERR "END TRANSLATE_NAMES\n\n" if ($self->get_conf('DEBUG')); +} + +# redefined functions +# +# Texinfo::Translations::gdt redefined to call user defined function. +sub gdt($$;$$$$) +{ + my ($self, $message, $replaced_substrings, $message_context, $type, $lang) = @_; + if (defined($self->{'formatting_function'}->{'format_translate_string'})) { + my $format_lang = $lang; + $format_lang = $self->get_conf('documentlanguage') + if ($self and !defined($format_lang)); + my $translated_string + = &{$self->{'formatting_function'}->{'format_translate_string'}}($self, + $message, $format_lang, $replaced_substrings, + $message_context, $type); + if (defined($translated_string)) { + return $translated_string; + } + } + return $self->SUPER::gdt($message, $replaced_substrings, $message_context, + $type, $lang); +} + + +sub converter_defaults($$) +{ + my $self = shift; + my $conf = shift; + if ($conf and defined($conf->{'TEXI2HTML'})) { + my $default_ref = { %defaults }; + my %texi2html_defaults = %$default_ref; + _set_variables_texi2html(\%texi2html_defaults); + return %texi2html_defaults; + } + return %defaults; +} + +my %css_element_class_styles = ( + %css_rules_not_collected, + + 'ul.toc-numbered-mark' => 'list-style: none', + 'pre.menu-comment-preformatted' => 'font-family: serif', + # using display: inline is an attempt to avoid a line break when in + # preformatted in menu. In 2022 it does not seems to work in firefox, + # there is still a line break. + 'pre.menu-entry-description-preformatted' => 'font-family: serif; display: inline', + 'pre.menu-preformatted' => 'font-family: serif', + 'a.summary-letter-printindex' => 'text-decoration: none', + 'pre.display-preformatted' => 'font-family: inherit', + 'span.program-in-footer' => 'font-size: smaller', # used with PROGRAM_NAME_IN_FOOTER + 'span.sansserif' => 'font-family: sans-serif; font-weight: normal', + 'span.r' => 'font-family: initial; font-weight: normal; font-style: normal', + 'td.index-entry-level-1' => 'padding-left: 1.5em', + 'td.index-entry-level-2' => 'padding-left: 3.0em', + 'kbd.key' => 'font-style: normal', + 'kbd.kbd' => 'font-style: oblique', + 'strong.def-name' => 'font-family: monospace; font-weight: bold; ' + .'font-size: larger', + 'p.flushleft-paragraph' => 'text-align:left', + 'p.flushright-paragraph' => 'text-align:right', + 'h1.centerchap' => 'text-align:center', + 'h2.centerchap' => 'text-align:center', + 'h3.centerchap' => 'text-align:center', + 'h1.settitle' => 'text-align:center', + 'h1.shorttitlepage' => 'text-align:center', + 'h3.subtitle' => 'text-align:right', + 'h4.centerchap' => 'text-align:center', + 'div.center' => 'text-align:center', + 'blockquote.indentedblock' => 'margin-right: 0em', + 'td.printindex-index-entry' => 'vertical-align: top', + 'td.printindex-index-section' => 'vertical-align: top; padding-left: 1em', + 'td.printindex-index-see-also' => 'vertical-align: top; padding-left: 1em', + 'td.menu-entry-destination' => 'vertical-align: top', + 'td.menu-entry-description' => 'vertical-align: top', + 'th.entries-header-printindex' => 'text-align:left', + 'th.sections-header-printindex' => 'text-align:left; padding-left: 1em', + 'th.menu-comment' => 'text-align:left', + 'td.category-def' => 'text-align:right', + 'td.call-def' => 'text-align:left', + 'td.button-direction-about' => 'text-align:center', + 'td.name-direction-about' => 'text-align:center', + + # The anchor element is wrapped in a rather than a block level + # element to avoid it appearing unless the mouse pointer is directly + # over the text, as it is annoying for anchors to flicker when + # you are moving your pointer elsewhere. "line-height: 0em" stops the + # invisible text from changing vertical spacing. + 'a.copiable-link' => 'visibility: hidden; ' + .'text-decoration: none; line-height: 0em', + 'span:hover a.copiable-link' => 'visibility: visible', +); + +$css_element_class_styles{'pre.format-preformatted'} + = $css_element_class_styles{'pre.display-preformatted'}; + +my %preformatted_commands_context = %preformatted_commands; +$preformatted_commands_context{'verbatim'} = 1; + +my %pre_class_commands; +foreach my $preformatted_command (keys(%preformatted_commands_context)) { + # no class for the @small* variants + if ($small_block_associated_command{$preformatted_command}) { + $pre_class_commands{$preformatted_command} + = $small_block_associated_command{$preformatted_command}; + } else { + $pre_class_commands{$preformatted_command} = $preformatted_command; + } +} +$pre_class_commands{'menu'} = 'menu'; + +my %default_pre_class_types; +$default_pre_class_types{'menu_comment'} = 'menu-comment'; + +my %indented_preformatted_commands; +foreach my $indented_format ('example', 'display', 'lisp') { + $indented_preformatted_commands{$indented_format} = 1; + $indented_preformatted_commands{"small$indented_format"} = 1; + + $css_element_class_styles{"div.$indented_format"} = 'margin-left: 3.2em'; +} +delete $css_element_class_styles{"div.lisp"}; # output as div.example instead + +# types that are in code style in the default case. '_code' is not +# a type that can appear in the tree built from Texinfo code, it is used +# to format a tree fragment as if it was in a @code @-command. +my %default_code_types = ( + '_code' => 1, +); + +# specification of arguments formatting +my %default_commands_args = ( + 'anchor' => [['monospacestring']], + 'email' => [['url', 'monospacestring'], ['normal']], + 'footnote' => [[]], + 'printindex' => [[]], + 'uref' => [['url', 'monospacestring'], ['normal'], ['normal']], + 'url' => [['url', 'monospacestring'], ['normal'], ['normal']], + 'sp' => [[]], + 'inforef' => [['monospace'],['normal'],['filenametext']], + 'xref' => [['monospace'],['normal'],['normal'],['filenametext'],['normal']], + 'pxref' => [['monospace'],['normal'],['normal'],['filenametext'],['normal']], + 'ref' => [['monospace'],['normal'],['normal'],['filenametext'],['normal']], + 'link' => [['monospace'],['normal'],['filenametext']], + 'image' => [['url', 'filenametext', 'monospacestring'],['filenametext'],['filenametext'],['string', 'normal'],['filenametext']], + # FIXME shouldn't it better not to convert if later ignored? + # note that right now ignored argument are in elided empty types + # but this could change. + 'inlinefmt' => [['monospacetext'],['normal']], + 'inlinefmtifelse' => [['monospacetext'],['normal'],['normal']], + 'inlineraw' => [['monospacetext'],['raw']], + 'inlineifclear' => [['monospacetext'],['normal']], + 'inlineifset' => [['monospacetext'],['normal']], + 'item' => [[]], + 'itemx' => [[]], + 'value' => [['monospacestring']], +); + +foreach my $explained_command (keys(%explained_commands)) { + $default_commands_args{$explained_command} + = [['normal'], ['string']]; +} + +# intercept warning and error messages to take 'ignore_notice' into +# account +sub _noticed_line_warn($$$) +{ + my $self = shift; + my $text = shift; + my $line_nr = shift; + return if ($self->{'ignore_notice'}); + return $self->line_warn($self, $text, $line_nr); +} + +my %kept_line_commands; + +# TODO add the possibility to customize to add more commands to +# @informative_global_commands? +my @informative_global_commands = ('documentlanguage', 'footnotestyle', + 'xrefautomaticsectiontitle', 'deftypefnnewline'); + +my @contents_commands = ('contents', 'shortcontents', 'summarycontents'); + +foreach my $line_command (@informative_global_commands, + @contents_commands, keys(%formattable_line_commands), + keys(%formatted_line_commands), + keys(%default_index_commands)) { + $kept_line_commands{$line_command} = 1; +} + +foreach my $line_command (keys(%line_commands)) { + $default_commands_conversion{$line_command} = undef + unless ($kept_line_commands{$line_command}); +} + +foreach my $nobrace_command (keys(%nobrace_commands)) { + $default_commands_conversion{$nobrace_command} = undef + unless ($formatted_nobrace_commands{$nobrace_command}); +} + +# formatted/formattable @-commands that are not converted in +# HTML in the default case. +$default_commands_conversion{'page'} = undef; +$default_commands_conversion{'need'} = undef; +$default_commands_conversion{'vskip'} = undef; + +foreach my $ignored_brace_commands ('caption', 'shortcaption', + 'hyphenation', 'sortas') { + $default_commands_conversion{$ignored_brace_commands} = undef; +} + +foreach my $ignored_block_commands ('ignore', 'macro', 'rmacro', 'linemacro', + 'copying', 'documentdescription', 'titlepage', 'direntry') { + $default_commands_conversion{$ignored_block_commands} = undef; +}; + +# Formatting of commands without args + +# The hash holding the defaults for the formatting of +# most commands without args. It has three contexts as keys, +# 'normal' in normal text, 'preformatted' in @example and similar +# commands, and 'string' for contexts where HTML elements should not +# be used. +my %default_no_arg_commands_formatting = ( + 'normal' => {}, + 'preformatted' => {}, + 'string' => {}, + # more internal + 'css_string' => {}, +); + +foreach my $command (keys(%Texinfo::Convert::Converter::xml_text_entity_no_arg_commands_formatting)) { + $default_no_arg_commands_formatting{'normal'}->{$command} = + {'text' => + $Texinfo::Convert::Converter::xml_text_entity_no_arg_commands_formatting{ + $command}}; +} + +$default_no_arg_commands_formatting{'normal'}->{' '} = {'text' => ' '}; +$default_no_arg_commands_formatting{'normal'}->{"\t"} = {'text' => ' '}; +$default_no_arg_commands_formatting{'normal'}->{"\n"} = {'text' => ' '}; + +# possible example of use, right now not used, as +# the generic Converter customization is directly used through +# the call to Texinfo::Convert::Utils::translated_command_tree(). +#$default_no_arg_commands_formatting{'normal'}->{'error'}->{'translated_converted'} = 'error-->'; +## This is used to have gettext pick up the chain to be translated +#if (0) { +# my $not_existing; +# $not_existing->gdt('error-->'); +#} + +$default_no_arg_commands_formatting{'normal'}->{'enddots'} + = {'element' => 'small', 'text' => '...'}; +$default_no_arg_commands_formatting{'preformatted'}->{'dots'} + = {'text' => '...'}; +$default_no_arg_commands_formatting{'preformatted'}->{'enddots'} + = {'text' => '...'}; +$default_no_arg_commands_formatting{'normal'}->{'*'} = {'text' => '
'}; +# this is used in math too, not sure that it is the best +# in that context, '
' could be better. +$default_no_arg_commands_formatting{'preformatted'}->{'*'} = {'text' => "\n"}; + +# escaped code points in CSS +# https://www.w3.org/TR/css-syntax/#consume-escaped-code-point +# Consume as many hex digits as possible, but no more than 5. Note that this means 1-6 hex digits have been consumed in total. If the next input code point is whitespace, consume it as well. Interpret the hex digits as a hexadecimal number. +# Note that in style= HTML attributes entities are used to +# protect CSS strings. For example, the CSS string a'b" +# is protected as CSS as a\'b", and " is escaped in an HTML style +# attribute: style="list-style-type: 'a\'b"'" + +foreach my $no_brace_command (keys(%nobrace_symbol_text)) { + $default_no_arg_commands_formatting{'css_string'}->{$no_brace_command} + = {'text' => $nobrace_symbol_text{$no_brace_command}}; +} + +foreach my $command (keys(%{$default_no_arg_commands_formatting{'normal'}})) { + if (defined($Texinfo::Convert::Unicode::unicode_map{$command}) + and $Texinfo::Convert::Unicode::unicode_map{$command} ne '') { + my $char_nr = hex($Texinfo::Convert::Unicode::unicode_map{$command}); + my $css_string; + if ($char_nr < 128) { # 7bit ascii + $css_string = chr($char_nr); + } else { + $css_string = "\\$Texinfo::Convert::Unicode::unicode_map{$command} "; + } + $default_no_arg_commands_formatting{'css_string'}->{$command} + = {'text' => $css_string}; + } elsif ($default_no_arg_commands_formatting{'preformatted'}->{$command}) { + $default_no_arg_commands_formatting{'css_string'}->{$command} + = {'text' + => $default_no_arg_commands_formatting{'preformatted'}->{$command}->{'text'}}; + } elsif ($default_no_arg_commands_formatting{'normal'}->{$command}->{'text'}) { + $default_no_arg_commands_formatting{'css_string'}->{$command} + = {'text' => $default_no_arg_commands_formatting{'normal'}->{$command}->{'text'}}; + } elsif (exists($nobrace_symbol_text{$command}) + and $nobrace_symbol_text{$command} eq '') { + # @- @/ @/ @| + $default_no_arg_commands_formatting{'css_string'}->{$command} + = {'text' => ''}; + } else { + warn "BUG: $command: no css_string\n"; + } +} + +# remove to force using only translations (as the command +# is in the default converter translated commands) +delete $default_no_arg_commands_formatting{'css_string'}->{'error'}; + +$default_no_arg_commands_formatting{'css_string'}->{'*'}->{'text'} = '\A '; + +$default_no_arg_commands_formatting{'css_string'}->{' '}->{'text'} = ' '; +$default_no_arg_commands_formatting{'css_string'}->{"\t"}->{'text'} = ' '; +$default_no_arg_commands_formatting{'css_string'}->{"\n"}->{'text'} = ' '; +$default_no_arg_commands_formatting{'css_string'}->{'tie'}->{'text'} = ' '; + +# w not in css_string, set the corresponding css_element_class_styles +# especially, which also has none and not w in the class +$css_element_class_styles{'ul.mark-none'} = 'list-style-type: none'; + +# setup css_element_class_styles for mark commands based on css strings +foreach my $mark_command (keys(%{$default_no_arg_commands_formatting{'css_string'}})) { + if (defined($brace_commands{$mark_command})) { + my $css_string; + if ($mark_command eq 'bullet') { + $css_string = 'disc'; + } elsif ($default_no_arg_commands_formatting{'css_string'}->{$mark_command} + and $default_no_arg_commands_formatting{'css_string'} + ->{$mark_command}->{'text'}) { + if ($special_list_mark_css_string_no_arg_command{$mark_command}) { + $css_string = $special_list_mark_css_string_no_arg_command{$mark_command}; + } else { + $css_string + = $default_no_arg_commands_formatting{'css_string'} + ->{$mark_command}->{'text'}; + } + $css_string =~ s/^(\\[A-Z0-9]+) $/$1/; + $css_string = '"'.$css_string.'"'; + } + if (defined($css_string)) { + $css_element_class_styles{"ul.mark-$mark_command"} + = "list-style-type: $css_string"; + } + } +} + +# used to show the built-in CSS rules +sub builtin_default_css_text() +{ + my $css_text = ''; + foreach my $css_rule (sort(keys(%css_element_class_styles))) { + if ($css_element_class_styles{$css_rule} ne '') { + $css_text .= "$css_rule {$css_element_class_styles{$css_rule}}\n"; + } + } + return $css_text; +} + +sub _text_element_conversion($$$) +{ + my $self = shift; + my $specification = shift; + my $command = shift; + + my $text = ''; + # note that there could be elements in text + if (exists($specification->{'text'})) { + $text = $specification->{'text'}; + } + + if (exists($specification->{'element'})) { + return $self->html_attribute_class($specification->{'element'}, [$command]) + .'>'. $text . '{'element'}.'>'; + } else { + return $text; + } +} + +sub _convert_no_arg_command($$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + + if ($cmdname eq 'click' and $command->{'extra'} + and exists($command->{'extra'}->{'clickstyle'})) { + my $click_cmdname = $command->{'extra'}->{'clickstyle'}; + if (($self->in_preformatted() or $self->in_math() + and $self->{'no_arg_commands_formatting'}->{'preformatted'} + ->{$click_cmdname}) + or ($self->in_string() and + $self->{'no_arg_commands_formatting'}->{'string'}->{$click_cmdname}) + or ($self->{'no_arg_commands_formatting'}->{'normal'}->{$click_cmdname})) { + $cmdname = $click_cmdname; + } + } + if ($self->in_upper_case() and $letter_no_arg_commands{$cmdname} + and $self->{'no_arg_commands_formatting'}->{'normal'}->{uc($cmdname)}) { + $cmdname = uc($cmdname); + } + + my $result; + + if ($self->in_preformatted() or $self->in_math()) { + $result = $self->_text_element_conversion( + $self->{'no_arg_commands_formatting'}->{'preformatted'}->{$cmdname}, + $cmdname); + } elsif ($self->in_string()) { + $result = $self->_text_element_conversion( + $result = $self->{'no_arg_commands_formatting'}->{'string'}->{$cmdname}, + $cmdname); + } else { + $result = $self->_text_element_conversion( + $result = $self->{'no_arg_commands_formatting'}->{'normal'}->{$cmdname}, + $cmdname); + } + + return $result; +} + +foreach my $command(keys(%{$default_no_arg_commands_formatting{'normal'}})) { + $default_commands_conversion{$command} = \&_convert_no_arg_command; +} + +sub _css_string_convert_no_arg_command($$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + + if ($cmdname eq 'click' and $command->{'extra'} + and exists($command->{'extra'}->{'clickstyle'})) { + my $click_cmdname = $command->{'extra'}->{'clickstyle'}; + if ($self->{'no_arg_commands_formatting'}->{'css_string'}->{$click_cmdname}) { + $cmdname = $click_cmdname; + } + } + if ($self->in_upper_case() and $letter_no_arg_commands{$cmdname} + and $self->{'no_arg_commands_formatting'}->{'css_string'}->{uc($cmdname)}) { + $cmdname = uc($cmdname); + } + #if (not defined($self->{'no_arg_commands_formatting'}->{'css_string'}->{$cmdname}->{'text'})) { + # cluck ("BUG: CSS $cmdname no text"); + #} + return $self->{'no_arg_commands_formatting'}->{'css_string'} + ->{$cmdname}->{'text'}; +} + +foreach my $command(keys(%{$default_no_arg_commands_formatting{'normal'}})) { + $default_css_string_commands_conversion{$command} = \&_css_string_convert_no_arg_command; +} + +sub _convert_today_command($$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + + my $tree = $self->Texinfo::Convert::Utils::expand_today(); + return $self->convert_tree($tree, 'convert today'); +} + +$default_commands_conversion{'today'} = \&_convert_today_command; + +# style commands + +my %quoted_style_commands; +foreach my $quoted_command ('samp') { + $quoted_style_commands{$quoted_command} = 1; +} + +my %style_commands_element = ('preformatted' => {}); +$style_commands_element{'normal'} = { + 'b' => 'b', + 'cite' => 'cite', + 'code' => 'code', + 'command' => 'code', + 'dfn' => 'em', + 'dmn' => 'span', + 'emph' => 'em', + 'env' => 'code', + 'file' => 'samp', + 'headitemfont' => 'b', # no effect: the @multitable prototypes are ignored + # and headitem are in rather than . + # The mapping is based on style used in other + # formats. + 'i' => 'i', + 'slanted' => 'i', + 'sansserif' => 'span', + 'kbd' => 'kbd', + 'key' => 'kbd', + 'option' => 'samp', + 'r' => 'span', + 'samp' => 'samp', + 'sc' => 'small', + 'strong' => 'strong', + 'sub' => 'sub', + 'sup' => 'sup', + 't' => 'code', + 'var' => 'var', + 'verb' => 'code', # other brace command +}; + +my %style_commands_formatting; +foreach my $formatting_context (keys(%style_commands_element)) { + $style_commands_formatting{$formatting_context} = {}; +} +$style_commands_formatting{'string'} = {}; + +my %style_brace_types = map {$_ => 1} ('style_other', 'style_code', + 'style_no_code'); +# @all_style_commands is the union of style brace commands and commands +# in $style_commands_element{'normal'}, a few not being style brace commands. +# Using keys of a map generated hash does like uniq, it avoids duplicates. +# The first grep selects style brace commands, ie commands with %brace_commands +# type in %style_brace_types. +my @all_style_commands = keys %{{ map { $_ => 1 } + ((grep {$style_brace_types{$brace_commands{$_}}} keys(%brace_commands)), + keys(%{$style_commands_element{'normal'}})) }}; + +foreach my $command (@all_style_commands) { + # default is no attribute. + if ($style_commands_element{'normal'}->{$command}) { + $style_commands_formatting{'normal'}->{$command} + = {'element' => $style_commands_element{'normal'}->{$command}}; + $style_commands_formatting{'preformatted'}->{$command} + = {'element' => $style_commands_element{'normal'}->{$command}}; + } + if ($style_commands_element{'preformatted'}->{$command}) { + $style_commands_formatting{'preformatted'}->{$command} + = {'element' => $style_commands_element{'preformatted'}->{$command}}; + } + if ($quoted_style_commands{$command}) { + foreach my $context ('normal', 'string', 'preformatted') { + $style_commands_formatting{$context}->{$command} = {} + if (!$style_commands_formatting{$context}->{$command}); + $style_commands_formatting{$context}->{$command}->{'quote'} = 1; + } + } + $default_commands_conversion{$command} = \&_convert_style_command; +} + +$style_commands_formatting{'preformatted'}->{'sc'}->{'element'} = 'span'; + +# currently unused, could be re-used if there is a need to have attributes +# specified in %style_commands_element +sub _parse_attribute($) +{ + my $element = shift; + return ('', '', '') if (!defined($element)); + my ($class, $attributes) = ('', ''); + if ($element =~ /^(\w+)(\s+.*)/) + { + $element = $1; + $attributes = $2; + if ($attributes =~ s/^\s+class=\"([^\"]+)\"//) { + $class = $1; + } + } + return ($element, $class, $attributes); +} + +sub _convert_style_command($$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + + my $text; + $text = $args->[0]->{'normal'} if ($args->[0]); + if (!defined($text)) { + # happens with bogus @-commands without argument, like @strong something + #cluck "text not defined in _convert_style_command"; + return ''; + } + my @classes; + # handle the effect of kbdinputstyle + if ($cmdname eq 'kbd' and $command->{'extra'} + and $command->{'extra'}->{'code'}) { + $cmdname = 'code'; + push @classes, 'as-code-kbd'; + } + unshift @classes, $cmdname; + + my $attribute_hash = {}; + if ($self->in_preformatted()) { + $attribute_hash = $self->{'style_commands_formatting'}->{'preformatted'}; + } elsif (!$self->in_string()) { + $attribute_hash = $self->{'style_commands_formatting'}->{'normal'}; + } + if (defined($attribute_hash->{$cmdname})) { + my $attribute_text = ''; + my $style; + if (defined($attribute_hash->{$cmdname}->{'element'})) { + # the commented out code is useful only if there are attributes in + # style_commands_element + #my $class; + #($style, $class, $attribute_text) + # = _parse_attribute($attribute_hash->{$cmdname}->{'element'}); + #if (defined($class) and $class ne '') { + # push @classes, $class; + #} + my $style = $attribute_hash->{$cmdname}->{'element'}; + my $open = $self->html_attribute_class($style, \@classes); + if ($open ne '') { + $text = $open . '>' . $text . ""; + # $text = $open . "$attribute_text>" . $text . ""; + #} elsif ($attribute_text ne '') { + # $text = "<$style $attribute_text>". $text . ""; + } + } + if (defined($attribute_hash->{$cmdname}->{'quote'})) { + $text = $self->get_conf('OPEN_QUOTE_SYMBOL') . $text + . $self->get_conf('CLOSE_QUOTE_SYMBOL'); + } + } + return $text; +} + +sub _convert_w_command($$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + + my $text; + $text = $args->[0]->{'normal'} if ($args->[0]); + + if (!defined($text)) { + $text = ''; + } + if ($self->in_string()) { + return $text; + } else { + return $text . ''; + } +} +$default_commands_conversion{'w'} = \&_convert_w_command; + +sub _convert_value_command($$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + + return $self->convert_tree($self->gdt('@{No value for `{value}\'@}', + {'value' => $args->[0]->{'monospacestring'}})); +} + +$default_commands_conversion{'value'} = \&_convert_value_command; + +sub _convert_email_command($$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + + my $mail_arg = shift @$args; + my $text_arg = shift @$args; + my $mail = ''; + my $mail_string; + if (defined($mail_arg)) { + $mail = $mail_arg->{'url'}; + $mail_string = $mail_arg->{'monospacestring'}; + } + my $text = ''; + if (defined($text_arg)) { + $text = $text_arg->{'normal'}; + } + $text = $mail_string unless ($text ne ''); + # match a non-space character. Both ascii and non-ascii spaces are + # considered as spaces. + return $text unless ($mail =~ /[^\v\h\s]/); + if ($self->in_string()) { + return "$mail_string ($text)"; + } else { + return $self->html_attribute_class('a', [$cmdname]) + .' href="'.$self->url_protect_url_text("mailto:$mail")."\">$text
"; + } +} + +$default_commands_conversion{'email'} = \&_convert_email_command; + +sub _convert_explained_command($$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + + my $with_explanation; + my $explanation_result; + my $explanation_string; + my $normalized_type = ''; + if ($command->{'args'}->[0] + and $command->{'args'}->[0]->{'contents'}) { + $normalized_type = Texinfo::Convert::NodeNameNormalization::normalize_node( + {'contents' => $command->{'args'}->[0]->{'contents'}}); + } + + my $explained_commands + = $self->shared_conversion_state('explained_commands', {}); + $explained_commands->{$cmdname} = {} if (!$explained_commands->{$cmdname}); + my $element_explanation_contents + = $self->shared_conversion_state('element_explanation_contents', {}); + if ($args->[1] and defined($args->[1]->{'string'}) + and $args->[1]->{'string'} =~ /\S/) { + $with_explanation = 1; + $explanation_string = $args->[1]->{'string'}; + + # Convert the explanation of the acronym. Must do this before we save + # the explanation for the future, otherwise we get infinite recursion + # for recursively-defined acronyms. + $explanation_result = $self->convert_tree($args->[1]->{'tree'}, + "convert $cmdname explanation"); + $explained_commands->{$cmdname}->{$normalized_type} = + $command->{'args'}->[1]->{'contents'}; + } elsif ($element_explanation_contents->{$command}) { + # if an acronym element is formatted more than once, this ensures that + # only the first explanation (including a lack of explanation) is reused. + # Note that this means that acronyms converted first on a sectioning + # command line for a direction text may not get the explanation + # from acronyms appearing later on in the document but before + # the sectioning command. + if (@{$element_explanation_contents->{$command}}) { + $explanation_string = $self->convert_tree_new_formatting_context( + {'type' => '_string', + 'contents' => $element_explanation_contents->{$command}}, + $cmdname, $cmdname); + } + } elsif ($explained_commands->{$cmdname}->{$normalized_type}) { + $explanation_string = $self->convert_tree_new_formatting_context( + {'type' => '_string', + 'contents' => $explained_commands + ->{$cmdname}->{$normalized_type}}, + $cmdname, $cmdname); + + $element_explanation_contents->{$command} + = $explained_commands->{$cmdname}->{$normalized_type}; + } else { + # Avoid ever giving an explanation for this element, even if an + # explanation could appear later on, for instance if acronym is + # formatted early on a sectioning command line and the acronym is + # defined before the sectioning command in the document. This prevents + # infinite recursion for a recursively-defined acronym, when an + # @acronym within the explanation could end up referring to the + # containing @acronym. + + $element_explanation_contents->{$command} = []; + } + my $result = $args->[0]->{'normal'}; + if (!$self->in_string()) { + my $explanation = ''; + $explanation = " title=\"$explanation_string\"" + if (defined($explanation_string)); + my $html_element = 'abbr'; + $result = $self->html_attribute_class($html_element, [$cmdname]) + ."${explanation}>".$result.""; + } + if ($with_explanation) { + # TRANSLATORS: abbreviation or acronym explanation + $result = $self->convert_tree($self->gdt('{explained_string} ({explanation})', + {'explained_string' => {'type' => '_converted', + 'text' => $result}, + 'explanation' => {'type' => '_converted', + 'text' => $explanation_result}}), "convert explained $cmdname"); + } + + return $result; +} + +foreach my $explained_command (keys(%explained_commands)) { + $default_commands_conversion{$explained_command} + = \&_convert_explained_command; +} + +sub _convert_anchor_command($$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + + my $id = $self->command_id($command); + if (defined($id) and $id ne '' and !$self->in_multi_expanded() + and !$self->in_string()) { + return &{$self->formatting_function('format_separate_anchor')}($self, + $id, 'anchor'); + } + return ''; +} + +$default_commands_conversion{'anchor'} = \&_convert_anchor_command; + +sub _convert_footnote_command($$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + + my $number_in_doc; + my $foot_num = $self->shared_conversion_state('footnote_number', 0); + ${$foot_num}++; + if ($self->get_conf('NUMBER_FOOTNOTES')) { + $number_in_doc = $$foot_num; + } else { + $number_in_doc = $self->get_conf('NO_NUMBER_FOOTNOTE_SYMBOL'); + } + + return "($number_in_doc)" if ($self->in_string()); + + #print STDERR "FOOTNOTE $command\n"; + my $footid = $self->command_id($command); + + # happens for bogus footnotes + if (!defined($footid)) { + return ''; + } + # ID for linking back to the main text from the footnote. + my $docid = $self->footnote_location_target($command); + + my $multiple_expanded_footnote = 0; + my $multi_expanded_region = $self->in_multi_expanded(); + if (defined($multi_expanded_region)) { + # to avoid duplicate names, use a prefix that cannot happen in anchors + my $target_prefix = "t_f"; + $footid = $target_prefix.$multi_expanded_region.'_'.$footid.'_'.$$foot_num; + $docid = $target_prefix.$multi_expanded_region.'_'.$docid.'_'.$$foot_num; + } else { + my $footnote_id_numbers + = $self->shared_conversion_state('footnote_id_numbers', {}); + if (!defined($footnote_id_numbers->{$footid})) { + $footnote_id_numbers->{$footid} = $$foot_num; + } else { + # This should rarely happen, except for @footnote in @copying and + # multiple @insertcopying... + # Here it is not checked that there is no clash with another anchor. + # However, unless there are more than 1000 footnotes this should not + # happen. + $footid .= '_'.$$foot_num; + $docid .= '_'.$$foot_num; + $multiple_expanded_footnote = 1; + } + } + my $footnote_href; + if ($self->get_conf('footnotestyle') eq 'end' + and (defined($multi_expanded_region) + or $multiple_expanded_footnote)) { + # if the footnote appears multiple times, command_href() will select + # one, but it may not be the one expanded at the location currently + # formatted (in general the first one, but it depends if it is in a + # tree element or not, for instance in @titlepage). + # With footnotestyle end, considering that the footnote is in the same file + # has a better change of being correct. + $footnote_href = "#$footid"; + } else { + $footnote_href = $self->command_href($command, undef, undef, $footid); + } + + $self->register_footnote($command, $footid, $docid, $number_in_doc, + $self->get_info('current_filename'), $multi_expanded_region); + + my $footnote_number_text; + if ($self->in_preformatted()) { + $footnote_number_text = "($number_in_doc)"; + } else { + $footnote_number_text = "$number_in_doc"; + } + return $self->html_attribute_class('a', [$cmdname]) + ." id=\"$docid\" href=\"$footnote_href\">$footnote_number_text"; +} +$default_commands_conversion{'footnote'} = \&_convert_footnote_command; + +sub _convert_uref_command($$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + + my @args = @$args; + my $url_arg = shift @args; + my $text_arg = shift @args; + my $replacement_arg = shift @args; + + my ($url, $url_string, $text, $replacement); + if (defined($url_arg)) { + $url = $url_arg->{'url'}; + $url_string = $url_arg->{'monospacestring'}; + } + $text = $text_arg->{'normal'} if defined($text_arg); + $replacement = $replacement_arg->{'normal'} if defined($replacement_arg); + + $text = $replacement if (defined($replacement) and $replacement ne ''); + $text = $url_string if (!defined($text) or $text eq ''); + return $text if (!defined($url) or $url eq ''); + return "$text ($url_string)" if ($self->in_string()); + + return $self->html_attribute_class('a', [$cmdname]) + .' href="'.$self->url_protect_url_text($url)."\">$text"; +} + +$default_commands_conversion{'uref'} = \&_convert_uref_command; +$default_commands_conversion{'url'} = \&_convert_uref_command; + +sub _convert_image_command($$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + + if (defined($args->[0]->{'filenametext'}) + and $args->[0]->{'filenametext'} ne '') { + my $basefile_string = ''; + $basefile_string = $args->[0]->{'monospacestring'} + if (defined($args->[0]->{'monospacestring'})); + return $basefile_string if ($self->in_string()); + my ($image_file, $image_basefile, $image_extension, $image_path) + = $self->html_image_file_location_name($cmdname, $command, $args); + if (not defined($image_path)) { + $self->_noticed_line_warn(sprintf( + __("\@image file `%s' (for HTML) not found, using `%s'"), + $image_basefile, $image_file), $command->{'source_info'}); + } + if (defined($self->get_conf('IMAGE_LINK_PREFIX'))) { + $image_file = $self->get_conf('IMAGE_LINK_PREFIX') . $image_file; + } + my $alt_string; + if (defined($args->[3]) and defined($args->[3]->{'string'})) { + $alt_string = $args->[3]->{'string'}; + } + if (!defined($alt_string) or ($alt_string eq '')) { + $alt_string = $basefile_string; + } + return $self->close_html_lone_element( + $self->html_attribute_class('img', [$cmdname]) + . ' src="'.$self->url_protect_file_text($image_file) + ."\" alt=\"$alt_string\""); + } + return ''; +} + +$default_commands_conversion{'image'} = \&_convert_image_command; + +sub _convert_math_command($$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + + my $arg = $args->[0]->{'normal'}; + + my $math_type = $self->get_conf('HTML_MATH'); + if ($math_type and $math_type eq 'mathjax') { + $self->register_file_information('mathjax', 1); + return $self->html_attribute_class('em', [$cmdname, 'tex2jax_process']) + .">\\($arg\\)"; + } + return $self->html_attribute_class('em', [$cmdname]).">$arg"; +} + +$default_commands_conversion{'math'} = \&_convert_math_command; + +sub _accent_entities_html_accent($$$;$$$) +{ + my $self = shift; + my $text = shift; + my $command = shift; + my $in_upper_case = shift; + my $use_numeric_entities = shift; + my $accent = $command->{'cmdname'}; + + if ($in_upper_case and $text =~ /^\w$/) { + $text = uc ($text); + } + + # do not return a dotless i or j as such if it is further composed + # with an accented letter, return the letter as is + if ($accent eq 'dotless') { + if ($Texinfo::Convert::Unicode::unicode_accented_letters{$accent} + and exists($Texinfo::Convert::Unicode::unicode_accented_letters{ + $accent}->{$text}) + and ($command->{'parent'} + and $command->{'parent'}->{'parent'} + and $command->{'parent'}->{'parent'}->{'cmdname'} + and $Texinfo::Convert::Unicode::unicode_accented_letters{ + $command->{'parent'}->{'parent'}->{'cmdname'} })) { + return $text; + } + } + + if ($use_numeric_entities) { + my $formatted_accent + = Texinfo::Convert::Converter::xml_numeric_entity_accent($accent, $text); + if (defined($formatted_accent)) { + return $formatted_accent; + } + } else { + my ($accent_command_entity, $accent_command_text_with_entities); + if ($self->{'accent_entities'}->{$accent}) { + ($accent_command_entity, $accent_command_text_with_entities) + = @{$self->{'accent_entities'}->{$accent}}; + } + return "&${text}$accent_command_entity;" + if ($accent_command_entity + and defined($accent_command_text_with_entities) + and ($text =~ /^[$accent_command_text_with_entities]$/)); + my $formatted_accent + = Texinfo::Convert::Converter::xml_numeric_entity_accent($accent, $text); + if (defined($formatted_accent)) { + return $formatted_accent; + } + } + return $self->xml_accent($text, $command, $in_upper_case, + $use_numeric_entities); +} + +sub _accent_entities_numeric_entities_accent($$$;$) +{ + my $self = shift; + my $text = shift; + my $command = shift; + my $in_upper_case = shift; + + return _accent_entities_html_accent($self, $text, $command, $in_upper_case, 1); +} + +sub _convert_accent_command($$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + + my $format_accents; + if ($self->get_conf('USE_NUMERIC_ENTITY')) { + $format_accents = \&_accent_entities_numeric_entities_accent; + } else { + $format_accents = \&_accent_entities_html_accent; + } + return $self->convert_accents($command, $format_accents, + $self->get_conf('OUTPUT_CHARACTERS'), + $self->in_upper_case()); +} + +foreach my $command (keys(%accent_commands)) { + $default_commands_conversion{$command} = \&_convert_accent_command; +} + +sub _css_string_accent($$$;$) +{ + my $self = shift; + my $text = shift; + my $command = shift; + my $in_upper_case = shift; + + my $accent = $command->{'cmdname'}; + + if ($in_upper_case and $text =~ /^\p{Word}$/) { + $text = uc ($text); + } + if (exists($Texinfo::Convert::Unicode::unicode_accented_letters{$accent}) + and exists($Texinfo::Convert::Unicode::unicode_accented_letters{ + $accent}->{$text})) { + return '\\' . + $Texinfo::Convert::Unicode::unicode_accented_letters{$accent}->{$text}. ' '; + } + if (exists($Texinfo::Convert::Unicode::unicode_diacritics{$accent})) { + my $diacritic = '\\' + .$Texinfo::Convert::Unicode::unicode_diacritics{$accent}. ' '; + if ($accent ne 'tieaccent') { + return $text . $diacritic; + } else { + # tieaccent diacritic is naturally and correctly composed + # between two characters + my $remaining_text = $text; + # we consider that letters are either characters or escaped characters + if ($remaining_text =~ s/^([\p{L}\d]|\\[a-zA-Z0-9]+ )([\p{L}\d]|\\[a-zA-Z0-9]+ )(.*)$/$3/) { + return $1.$diacritic.$2 . $remaining_text; + } else { + return $text . $diacritic; + } + } + } + # should never happen, there are diacritics for every accent command + return Texinfo::Convert::Text::ascii_accent($text, $command); +} + +sub _css_string_convert_accent_command($$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + + my $format_accents = \&_css_string_accent; + return $self->convert_accents($command, $format_accents, + $self->get_conf('OUTPUT_CHARACTERS'), + $self->in_upper_case()); +} + +foreach my $command (keys(%accent_commands)) { + $default_css_string_commands_conversion{$command} + = \&_css_string_convert_accent_command; +} + +# argument is formatted as code since indicateurl is in brace_code_commands +sub _convert_indicateurl_command($$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + + my $text = $args->[0]->{'normal'}; + if (!defined($text)) { + # happens with bogus @-commands without argument, like @strong something + return ''; + } + if (!$self->in_string()) { + return $self->get_conf('OPEN_QUOTE_SYMBOL'). + $self->html_attribute_class('code', [$cmdname]).'>'.$text + .''.$self->get_conf('CLOSE_QUOTE_SYMBOL'); + } else { + return $self->get_conf('OPEN_QUOTE_SYMBOL').$text. + $self->get_conf('CLOSE_QUOTE_SYMBOL'); + } +} + +$default_commands_conversion{'indicateurl'} = \&_convert_indicateurl_command; + + +sub _convert_titlefont_command($$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + + my $text = $args->[0]->{'normal'}; + if (!defined($text)) { + # happens with bogus @-commands without argument, like @strong something + return ''; + } + return &{$self->formatting_function('format_heading_text')}($self, $cmdname, + [$cmdname], $text, 0); +} +$default_commands_conversion{'titlefont'} = \&_convert_titlefont_command; + +sub _convert_U_command($$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + + my $arg; + $arg = $args->[0]->{'normal'} if ($args->[0]); + my $res; + if (defined($arg) and $arg ne '') { + # checks on the value already done in Parser, just output it here. + $res = "&#x$arg;"; + } else { + $res = ''; + } + return $res; +} +$default_commands_conversion{'U'} = \&_convert_U_command; + +sub _default_format_comment($$) { + my $self = shift; + my $text = shift; + return $self->xml_comment(' '.$text); +} + +# Note: has an XS override +sub _default_format_protect_text { + my $self = shift; + my $text = shift; + my $result = $self->xml_protect_text($text); + $result =~ s/\f/ /g; + return $result; +} + +sub _default_css_string_format_protect_text($$) { + my $self = shift; + my $text = shift; + $text =~ s/\\/\\\\/g; + $text =~ s/\'/\\'/g; + return $text; +} + +# can be called on root commands, tree units, special elements +# and title elements. $cmdname can be undef for special elements. +sub _default_format_heading_text($$$$$;$$$) +{ + my $self = shift; + my $cmdname = shift; + my $classes = shift; + my $text = shift; + my $level = shift; + my $id = shift; + my $element = shift; + my $target = shift; + + return '' if ($text !~ /\S/ and not defined($id)); + + # This should seldom happen. + if ($self->in_string()) { + $text .= "\n" unless (defined($cmdname) and $cmdname eq 'titlefont'); + return $text; + } + + if ($level < 1) { + $level = 1; + } elsif ($level > $self->get_conf('MAX_HEADER_LEVEL')) { + $level = $self->get_conf('MAX_HEADER_LEVEL'); + } + my $id_str = ''; + if (defined($id)) { + $id_str = " id=\"$id\""; + + # The ID of this heading is likely the point the user would prefer being + # linked to over the $target, since that's where they would be seeing a + # copiable anchor. + $target = $id; + } + my $inside = $text; + if (defined $target && $self->get_conf('COPIABLE_LINKS')) { + # Span-wrap this anchor, so that the existing span:hover a.copiable-link + # rule applies. + $inside = "$text"; + $inside .= $self->_get_copiable_anchor($target); + $inside .= ''; + } + my $result = $self->html_attribute_class("h$level", $classes) + ."${id_str}>$inside"; + # titlefont appears inline in text, so no end of line is + # added. The end of line should be added by the user if needed. + $result .= "\n" unless (defined($cmdname) and $cmdname eq 'titlefont'); + $result .= $self->get_conf('DEFAULT_RULE') . "\n" + if (defined($cmdname) and $cmdname eq 'part' + and defined($self->get_conf('DEFAULT_RULE')) + and $self->get_conf('DEFAULT_RULE') ne ''); + return $result; +} + +sub _default_format_separate_anchor($$;$) +{ + my $self = shift; + my $id = shift; + my $class = shift; + + # html_attribute_class would not work with span, so if span is + # used, html_attribute_class should not be used + return $self->html_attribute_class('a', [$class])." id=\"$id\">"; +} + +# Associated to a button. Return text to use for a link in button bar. +# Depending on USE_NODE_DIRECTIONS and xrefautomaticsectiontitle +# use section or node for link direction and string. +sub _default_panel_button_dynamic_direction($$;$$$) +{ + my $self = shift; + my $direction = shift; + my $source_command = shift; + my $omit_rel = shift; + my $use_first_element_in_file_directions = shift; + + my $result = undef; + + if ((defined($self->get_conf('USE_NODE_DIRECTIONS')) + and $self->get_conf('USE_NODE_DIRECTIONS')) + or (not defined($self->get_conf('USE_NODE_DIRECTIONS')) + and $self->get_conf('USE_NODES'))) { + $direction = 'Node'.$direction; + } + + if ($use_first_element_in_file_directions) { + $direction = 'FirstInFile'.$direction; + } + + my $href = $self->from_element_direction($direction, 'href', + undef, undef, $source_command); + my $node; + + if ($self->get_conf('xrefautomaticsectiontitle') eq 'on') { + $node = $self->from_element_direction($direction, 'section'); + } + + if (!defined($node)) { + $node = $self->from_element_direction($direction, 'node'); + } + + my $hyperlink; + if (defined($href) and $href ne '' and defined($node) and $node =~ /\S/) { + my $hyperlink_attributes = $omit_rel ? '' + : $self->_direction_href_attributes($direction); + $hyperlink = "$node"; + } elsif (defined($node) and $node =~ /\S/) { + $hyperlink = $node; + } + if (defined($hyperlink)) { + # i18n + $result = $self->direction_string($direction, 'text').": $hyperlink"; + } + # 1 to communicate that a delimiter is needed for that button + return ($result, 1); +} + +# Used for button bar at the foot of a node, with "rel" and "accesskey" +# attributes omitted. +sub _default_panel_button_dynamic_direction_node_footer($$$) +{ + my $self = shift; + my $direction = shift; + my $source_command = shift; + + return _default_panel_button_dynamic_direction($self, $direction, + $source_command, 1); +} + +# used for button bar at the foot of a section or chapter with +# directions of first element in file used instead of the last +# element directions. +sub _default_panel_button_dynamic_direction_section_footer($$$) { + my $self = shift; + my $direction = shift; + my $source_command = shift; + + return _default_panel_button_dynamic_direction($self, $direction, + $source_command, undef, 1); +} + +# Only used if ICONS is set and the button is active. +sub _default_format_button_icon_img($$$;$) +{ + my $self = shift; + my $button = shift; + my $icon = shift; + my $name = shift; + + return '' if (!defined($icon)); + $button = '' if (!defined ($button)); + $name = '' if (!defined($name)); + my $alt = ''; + if ($name ne '') { + if ($button ne '') { + $alt = "$button: $name"; + } else { + $alt = $name; + } + } else { + $alt = $button; + } + return $self->close_html_lone_element( + '\"$alt\"get_conf('USE_ACCESSKEY')) { + my $accesskey = $self->direction_string($direction, 'accesskey', 'string'); + if (defined($accesskey) and ($accesskey ne '')) { + $href_attributes = " accesskey=\"$accesskey\""; + } + } + if ($self->get_conf('USE_REL_REV')) { + my $button_rel = $self->direction_string($direction, 'rel', 'string'); + if (defined($button_rel) and ($button_rel ne '')) { + $href_attributes .= " rel=\"$button_rel\""; + } + } + return $href_attributes; +} + +my %html_default_node_directions; +foreach my $node_directions ('NodeNext', 'NodePrev', 'NodeUp') { + $html_default_node_directions{$node_directions} = 1; +} + +sub _default_format_button($$;$) +{ + my $self = shift; + my $button = shift; + my $source_command = shift; + + my ($active, $passive, $need_delimiter); + if (ref($button) eq 'CODE') { + ($active, $need_delimiter) = &$button($self); + } elsif (ref($button) eq 'SCALAR') { + $active = "$$button" if defined($$button); + $need_delimiter = 1; + } elsif (ref($button) eq 'ARRAY' and scalar(@$button == 2)) { + my $text = $button->[1]; + my $direction = $button->[0]; + # $direction is simple text and $text is a reference + if (defined($direction) and ref($direction) eq '' + and defined($text) and (ref($text) eq 'SCALAR') and defined($$text)) { + # use given text + my $href = $self->from_element_direction($direction, 'href', + undef, undef, $source_command); + if ($href) { + my $anchor_attributes = $self->_direction_href_attributes($direction); + $active = "$$text"; + } else { + $passive = $$text; + } + $need_delimiter = 1; + # $direction is simple text and $text is a reference on code + } elsif (defined($direction) and ref($direction) eq '' + and defined($text) and (ref($text) eq 'CODE')) { + ($active, $need_delimiter) = &$text($self, $direction, $source_command); + # $direction is simple text and $text is also a simple text + } elsif (defined($direction) and ref($direction) eq '' + and defined($text) and ref($text) eq '') { + if ($text =~ s/^->\s*//) { + # this case is mostly for tests, to test the direction type $text + # with the direction $direction + $active = $self->from_element_direction($direction, $text, + undef, undef, $source_command); + } else { + my $href = $self->from_element_direction($direction, 'href', + undef, undef, $source_command); + my $text_formatted = $self->from_element_direction($direction, $text); + if ($href) { + my $anchor_attributes = $self->_direction_href_attributes($direction); + $active = "$text_formatted"; + } else { + $passive = $text_formatted; + } + } + $need_delimiter = 1; + } + } elsif ($button eq ' ') { + # handle space button + if ($self->get_conf('ICONS') and $self->get_conf('ACTIVE_ICONS') + and defined($self->get_conf('ACTIVE_ICONS')->{$button}) + and $self->get_conf('ACTIVE_ICONS')->{$button} ne '') { + my $button_name_string = $self->direction_string($button, + 'button', 'string'); + $active = &{$self->formatting_function('format_button_icon_img')}($self, + $button_name_string, $self->get_conf('ACTIVE_ICONS')->{' '}); + } else { + $active = $self->direction_string($button, 'text'); + } + $need_delimiter = 0; + } else { + my $href = $self->from_element_direction($button, 'href', + undef, undef, $source_command); + if ($href) { + # button is active + my $btitle = ''; + my $description = $self->direction_string($button, 'description', 'string'); + if (defined($description)) { + $btitle = ' title="' . $description . '"'; + } + if ($self->get_conf('USE_ACCESSKEY')) { + my $accesskey = $self->direction_string($button, 'accesskey', 'string'); + if (defined($accesskey) and $accesskey ne '') { + $btitle .= " accesskey=\"$accesskey\""; + } + } + if ($self->get_conf('USE_REL_REV')) { + my $button_rel = $self->direction_string($button, 'rel', 'string'); + if (defined($button_rel) and $button_rel ne '') { + $btitle .= " rel=\"$button_rel\""; + } + } + my $use_icon; + if ($self->get_conf('ICONS') and $self->get_conf('ACTIVE_ICONS')) { + # FIXME strip FirstInFile from $button to get $active_icon? + my $active_icon = $self->get_conf('ACTIVE_ICONS')->{$button}; + my $button_name_string = $self->direction_string($button, + 'button', 'string'); + if (defined($active_icon) and $active_icon ne '') { + # use icon + $active = "". + &{$self->formatting_function('format_button_icon_img')}($self, + $button_name_string, $active_icon, + $self->from_element_direction($button, 'string')) .""; + $use_icon = 1; + } + } + if (!$use_icon) { + # use text + $active = '[' . "". + $self->direction_string($button, 'text')."" . ']'; + } + } else { + # button is passive + my $use_icon; + if ($self->get_conf('ICONS') and $self->get_conf('PASSIVE_ICONS')) { + # FIXME strip FirstInFile from $button to get $passive_icon? + my $passive_icon = $self->get_conf('PASSIVE_ICONS')->{$button}; + my $button_name_string = $self->direction_string($button, + 'button', 'string'); + if ($passive_icon and $passive_icon ne '') { + $passive = &{$self->formatting_function('format_button_icon_img')}( + $self, $button_name_string, $passive_icon, + $self->from_element_direction($button, 'string')); + $use_icon = 1; + } + } + if (!$use_icon) { + $passive = '[' . $self->direction_string($button, 'text') . ']'; + } + } + $need_delimiter = 0; + } + # FIXME chose another option among those proposed in comments below? + if (not defined($need_delimiter)) { + # option 1: be forgiving if $need_delimiter is not set + # if ($html_default_node_directions{$button}) { + # $need_delimiter = 1; + # } else { + # $need_delimiter = 0; + # } + # option 2: be somewhat forgiving but show a backtrace + #cluck ("need_delimiter not defined"); + # $need_delimiter = 0; + # option3: no pity + confess ("need_delimiter not defined"); + } + return ($active, $passive, $need_delimiter); +} + +# called for special elements and tree units +sub _default_format_navigation_panel($$$$;$) +{ + my $self = shift; + my $buttons = shift; + my $cmdname = shift; + my $source_command = shift; + my $vertical = shift; + + # if VERTICAL_HEAD_NAVIGATION, the buttons are in a vertical table which + # is itself in the first column of a table opened in header_navigation + #my $vertical = $self->get_conf('VERTICAL_HEAD_NAVIGATION'); + + my $result = ''; + if ($self->get_conf('HEADER_IN_TABLE')) { + $result .= $self->html_attribute_class('table', ['nav-panel']) + .' cellpadding="1" cellspacing="1" border="0">'."\n"; + $result .= "" unless $vertical; + } else { + $result .= $self->html_attribute_class('div', ['nav-panel']).">\n

\n"; + } + + my $first_button = 1; + foreach my $button (@$buttons) { + if ($self->get_conf('HEADER_IN_TABLE')) { + $result .= ''."\n" if $vertical; + $result .= ''; + } + my $direction; + if (ref($button) eq 'ARRAY' + and defined($button->[0]) and ref($button->[0]) eq '') { + $direction = $button->[0]; + } elsif (defined($button) and ref($button) eq '') { + $direction = $button; + } + + my ($active, $passive, $need_delimiter) + # API info: using the API to allow for customization would be: + # = &{$self->formatting_function('format_button')}($self, $button, + # $source_command); + = &{$self->{'formatting_function'}->{'format_button'}}($self, $button, + $source_command); + if ($self->get_conf('HEADER_IN_TABLE')) { + if (defined($active)) { + $result .= $active; + } elsif (defined($passive)) { + $result .= $passive; + } + $result .= "\n"; + $result .= "\n" if $vertical; + $first_button = 0 if ($first_button); + } elsif (defined($active)) { + # only active buttons are print out when not in table + if ($need_delimiter and !$first_button) { + $active = ', ' .$active; + } + $result .= $active; + $first_button = 0 if ($first_button); + } + } + if ($self->get_conf('HEADER_IN_TABLE')) { + $result .= "" unless $vertical; + $result .= "\n"; + } else { + $result .= "

\n\n"; + } + return $result; +} + +sub _default_format_navigation_header($$$$) +{ + my $self = shift; + my $buttons = shift; + my $cmdname = shift; + my $element = shift; + + my $result = ''; + if ($self->get_conf('VERTICAL_HEAD_NAVIGATION')) { + $result .= ' + + +
+'; + } + $result .= &{$self->formatting_function('format_navigation_panel')}($self, + $buttons, $cmdname, $element, + $self->get_conf('VERTICAL_HEAD_NAVIGATION')); + if ($self->get_conf('VERTICAL_HEAD_NAVIGATION')) { + $result .= ' +'; + } elsif ($self->get_conf('SPLIT') eq 'node') { + $result .= $self->get_conf('DEFAULT_RULE')."\n"; + } + return $result; +} + +# this can only be called on root commands and associated tree units +sub _default_format_element_header($$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $tree_unit = shift; + + my $result = ''; + + print STDERR "FORMAT elt header " + # uncomment to get perl object names + #."$tree_unit (@{$tree_unit->{'contents'}}) ". + . "(".join('|', map{Texinfo::Common::debug_print_element($_)} + @{$tree_unit->{'contents'}}) . ") ". + Texinfo::Structuring::root_or_external_element_cmd_texi($tree_unit) ."\n" + if ($self->get_conf('DEBUG')); + + # Do the heading if the command is the first command in the element + if (($tree_unit->{'contents'}->[0] eq $command + or (!$tree_unit->{'contents'}->[0]->{'cmdname'} + and $tree_unit->{'contents'}->[1] eq $command)) + # and there is more than one element + and ($tree_unit->{'structure'} + and ($tree_unit->{'structure'}->{'unit_next'} + or $tree_unit->{'structure'}->{'unit_prev'}))) { + my $is_top = $self->element_is_tree_unit_top($tree_unit); + my $first_in_page = (defined($tree_unit->{'structure'}->{'unit_filename'}) + and $self->count_elements_in_filename('current', + $tree_unit->{'structure'}->{'unit_filename'}) == 1); + my $previous_is_top = 0; + $previous_is_top = 1 + if ($tree_unit->{'structure'}->{'unit_prev'} + and $self->element_is_tree_unit_top($tree_unit->{'structure'} + ->{'unit_prev'})); + + print STDERR "Header ($previous_is_top, $is_top, $first_in_page): " + .Texinfo::Convert::Texinfo::root_heading_command_to_texinfo($command)."\n" + if ($self->get_conf('DEBUG')); + + if ($is_top) { + # use TOP_BUTTONS for top. + $result .= + &{$self->formatting_function('format_navigation_header')}($self, + $self->get_conf('TOP_BUTTONS'), $cmdname, $command) + if ($self->get_conf('SPLIT') or $self->get_conf('HEADERS')); + } else { + if ($first_in_page and !$self->get_conf('HEADERS')) { + if ($self->get_conf('SPLIT') eq 'chapter') { + $result + .= &{$self->formatting_function('format_navigation_header')}($self, + $self->get_conf('CHAPTER_BUTTONS'), $cmdname, $command); + + $result .= $self->get_conf('DEFAULT_RULE') ."\n" + if (defined($self->get_conf('DEFAULT_RULE')) + and !$self->get_conf('VERTICAL_HEAD_NAVIGATION')); + } elsif ($self->get_conf('SPLIT') eq 'section') { + $result + .= &{$self->formatting_function('format_navigation_header')}($self, + $self->get_conf('SECTION_BUTTONS'), $cmdname, $command); + } + } + if (($first_in_page or $previous_is_top) + and $self->get_conf('HEADERS')) { + $result + .= &{$self->formatting_function('format_navigation_header')}($self, + $self->get_conf('SECTION_BUTTONS'), $cmdname, $command); + } elsif($self->get_conf('HEADERS') or $self->get_conf('SPLIT') eq 'node') { + # got to do this here, as it isn't done otherwise since + # navigation_header is not called + $result + .= &{$self->formatting_function('format_navigation_panel')}($self, + $self->get_conf('SECTION_BUTTONS'), $cmdname, $command); + } + } + } + return $result; +} + +sub register_opened_section_level($$$) +{ + my $self = shift; + my $level = shift; + my $close = shift; + while (@{$self->{'pending_closes'}} < $level) { + push(@{$self->{'pending_closes'}}, ""); + } + push(@{$self->{'pending_closes'}}, $close); +} + +sub close_registered_sections_level($$) +{ + my $self = shift; + my $level = shift; + if (not defined($level)) { + cluck 'close_registered_sections_level $level not defined'; + } + my @closed_elements; + my $result = ''; + while (@{$self->{'pending_closes'}} > $level) { + my $close = pop @{$self->{'pending_closes'}}; + push(@closed_elements, $close) + if ($close); + } + return @closed_elements; +} + +sub _convert_heading_command($$$$$) +{ + my $self = shift; + my $cmdname = shift; + my $element = shift; + my $args = shift; + my $content = shift; + + my $result = ''; + + # No situation where this could happen + if ($self->in_string()) { + $result .= $self->command_text($element, 'string') ."\n" + if ($cmdname ne 'node'); + $result .= $content if (defined($content)); + return $result; + } + + my $element_id = $self->command_id($element); + + print STDERR "CONVERT elt heading " + # uncomment next line for the perl object name + #."$element " + .Texinfo::Convert::Texinfo::root_heading_command_to_texinfo($element)."\n" + if ($self->get_conf('DEBUG')); + my $tree_unit; + if ($Texinfo::Commands::root_commands{$element->{'cmdname'}} + and $element->{'structure'}->{'associated_unit'}) { + $tree_unit = $element->{'structure'}->{'associated_unit'}; + } + my $element_header = ''; + if ($tree_unit) { + $element_header = &{$self->formatting_function('format_element_header')}( + $self, $cmdname, $element, $tree_unit); + } + + my $tables_of_contents = ''; + my $structuring = $self->get_info('structuring'); + if ($self->get_conf('CONTENTS_OUTPUT_LOCATION') eq 'after_top' + and $cmdname eq 'top' + and $structuring and $structuring->{'sectioning_root'} + and scalar(@{$structuring->{'sections_list'}}) > 1) { + foreach my $content_command_name ('shortcontents', 'contents') { + if ($self->get_conf($content_command_name)) { + my $contents_text + = $self->_contents_inline_element($content_command_name, undef); + if ($contents_text ne '') { + $tables_of_contents .= $contents_text; + } + } + } + } + + my $mini_toc = ''; + if ($tables_of_contents eq '' + and $self->get_conf('FORMAT_MENU') eq 'sectiontoc' + and $sectioning_heading_commands{$cmdname}) { + $mini_toc = _mini_toc($self, $element); + } + + if ($self->get_conf('NO_TOP_NODE_OUTPUT') + and $Texinfo::Commands::root_commands{$cmdname}) { + my $in_skipped_node_top + = $self->shared_conversion_state('in_skipped_node_top', 0); + my $node_element; + if ($cmdname eq 'node') { + $node_element = $element; + } elsif ($cmdname eq 'part' and $element->{'extra'} + and $element->{'extra'}->{'part_following_node'}) { + $node_element = $element->{'extra'}->{'part_following_node'}; + } + if ($node_element or $cmdname eq 'part') { + if ($node_element and $node_element->{'extra'} + and $node_element->{'extra'}->{'normalized'} + and $node_element->{'extra'}->{'normalized'} eq 'Top') { + $$in_skipped_node_top = 1; + } elsif ($$in_skipped_node_top == 1) { + $$in_skipped_node_top = -1; + } + } + if ($$in_skipped_node_top == 1) { + my $id_class = $cmdname; + $result .= &{$self->formatting_function('format_separate_anchor')}($self, + $element_id, $id_class); + $result .= $element_header; + $result .= $tables_of_contents; + $result .= $mini_toc; + return $result; + } + } + + my @heading_classes; + my $level_corrected_cmdname = $cmdname; + if ($element->{'structure'} + and defined $element->{'structure'}->{'section_level'}) { + # if the level was changed, use a consistent command name + $level_corrected_cmdname + = Texinfo::Structuring::section_level_adjusted_command_name($element); + if ($level_corrected_cmdname ne $cmdname) { + push @heading_classes, + "${cmdname}-level-set-${level_corrected_cmdname}"; + } + } + + # find the section starting here, can be through the associated node + # preceding the section, or the section itself + my $opening_section; + my $level_corrected_opening_section_cmdname; + if ($cmdname eq 'node' + and $element->{'extra'} + and $element->{'extra'}->{'associated_section'}) { + $opening_section = $element->{'extra'}->{'associated_section'}; + $level_corrected_opening_section_cmdname + = Texinfo::Structuring::section_level_adjusted_command_name( + $opening_section); + } elsif ($cmdname ne 'node' + # if there is an associated node, it is not a section opening + # the section was opened before when the node was encountered + and (not $element->{'extra'} + or not $element->{'extra'}->{'associated_node'}) + # to avoid *heading* @-commands + and $Texinfo::Commands::root_commands{$cmdname}) { + $opening_section = $element; + $level_corrected_opening_section_cmdname = $level_corrected_cmdname; + } + + # $heading not defined may happen if the command is a @node, for example + # if there is an error in the node. + my $heading = $self->command_text($element); + my $heading_level; + # node is used as heading if there is nothing else. + if ($cmdname eq 'node') { + # FIXME what to do if the $tree_unit extra does not contain any + # unit_command, but tree_unit is defined (it can contain only + # 'first_in_page') + if ((!$tree_unit # or !$tree_unit->{'extra'} + # or !$tree_unit->{'extra'}->{'unit_command'} + or ($tree_unit->{'extra'}->{'unit_command'} + and $tree_unit->{'extra'}->{'unit_command'} eq $element + and (not $element->{'extra'} + or not $element->{'extra'}->{'associated_section'}))) + and defined($element->{'extra'}) + and defined($element->{'extra'}->{'normalized'})) { + if ($element->{'extra'}->{'normalized'} eq 'Top') { + $heading_level = 0; + } else { + $heading_level = 3; + } + } + } elsif ($element->{'structure'} + and defined($element->{'structure'}->{'section_level'})) { + $heading_level = $element->{'structure'}->{'section_level'}; + } else { + # for *heading* @-commands which do not have a level + # in the document as they are not associated with the + # sectioning tree, but still have a $heading_level + $heading_level = Texinfo::Common::section_level($element); + } + + my $do_heading = (defined($heading) and $heading ne '' + and defined($heading_level)); + + # if set, the id is associated to the heading text + my $heading_id; + if ($opening_section) { + my $level = $opening_section->{'structure'}->{'section_level'}; + $result .= join('', $self->close_registered_sections_level($level)); + $self->register_opened_section_level($level, "\n"); + + # use a specific class name to mark that this is the start of + # the section extent. It is not necessary where the section is. + $result .= $self->html_attribute_class('div', + ["${level_corrected_opening_section_cmdname}-level-extent"]); + $result .= " id=\"$element_id\"" + if (defined($element_id) and $element_id ne ''); + $result .= ">\n"; + } elsif (defined($element_id) and $element_id ne '') { + if ($element_header ne '') { + # case of a @node without sectioning command and with a header. + # put the node element anchor before the header. + # Set the class name to the command name if there is no heading, + # else the class will be with the heading element. + my $id_class = $cmdname; + if ($do_heading) { + $id_class = "${cmdname}-id"; + } + $result .= &{$self->formatting_function('format_separate_anchor')}($self, + $element_id, $id_class); + } else { + $heading_id = $element_id; + } + } + + $result .= $element_header; + + if ($do_heading) { + if ($self->get_conf('TOC_LINKS') + and $Texinfo::Commands::root_commands{$cmdname} + and $sectioning_heading_commands{$cmdname}) { + my $content_href = $self->command_contents_href($element, 'contents'); + if ($content_href ne '') { + $heading = "$heading"; + } + } + + my $heading_class = $level_corrected_cmdname; + unshift @heading_classes, $heading_class; + if ($self->in_preformatted()) { + my $id_str = ''; + if (defined($heading_id)) { + $id_str = " id=\"$heading_id\""; + } + $result .= $self->html_attribute_class('strong', \@heading_classes) + ."${id_str}>".$heading.''."\n"; + } else { + $result .= &{$self->formatting_function('format_heading_text')}($self, + $level_corrected_cmdname, \@heading_classes, $heading, + $heading_level +$self->get_conf('CHAPTER_HEADER_LEVEL') -1, + $heading_id, $element, $element_id); + } + } elsif (defined($heading_id)) { + # case of a lone node and no header, and case of an empty @top + $result .= &{$self->formatting_function('format_separate_anchor')}($self, + $heading_id, $cmdname); + } + $result .= $content if (defined($content)); + + $result .= $tables_of_contents; + $result .= $mini_toc; + return $result; +} + +foreach my $command (keys(%sectioning_heading_commands), 'node') { + $default_commands_conversion{$command} = \&_convert_heading_command; +} + +sub _convert_raw_command($$$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + my $content = shift; + + if ($cmdname eq 'html') { + return $content; + } + $self->_noticed_line_warn(sprintf(__("raw format %s is not converted"), + $cmdname), $command->{'source_info'}); + return &{$self->formatting_function('format_protect_text')}($self, $content); +} + +foreach my $command (keys(%format_raw_commands)) { + $default_commands_conversion{$command} = \&_convert_raw_command; +} + +sub _convert_inline_command($$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + + my $format_arg = shift @$args; + + my $format; + if (defined($format_arg)) { + $format = $format_arg->{'monospacetext'}; + } + return '' if (!defined($format) or $format eq ''); + + my $arg_index = undef; + if ($inline_format_commands{$cmdname}) { + if ($cmdname eq 'inlinefmtifelse' and !$self->is_format_expanded($format)) { + $arg_index = 1; + } elsif ($self->is_format_expanded($format)) { + $arg_index = 0; + } + } elsif (defined($command->{'extra'}) + and defined($command->{'extra'}->{'expand_index'})) { + $arg_index = 0; + } + if (defined($arg_index) and $arg_index < scalar(@$args)) { + my $text_arg = $args->[$arg_index]; + if ($text_arg) { + if ($text_arg->{'normal'}) { + return $text_arg->{'normal'}; + } elsif ($text_arg->{'raw'}) { + return $text_arg->{'raw'}; + } + } + } + return ''; +} + +foreach my $command (grep {$brace_commands{$_} eq 'inline'} + keys(%brace_commands)) { + $default_commands_conversion{$command} = \&_convert_inline_command; +} + +sub _indent_with_table($$$;$) +{ + my $self = shift; + my $cmdname = shift; + my $content = shift; + my $extra_classes = shift; + + my @classes; + @classes = @$extra_classes if (defined($extra_classes)); + unshift @classes, $cmdname; + return $self->html_attribute_class('table', \@classes) + .'>
'.$self->get_info('non_breaking_space').''.$content + ."
\n"; +} + +sub _convert_preformatted_command($$$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + my $content = shift; + + my @classes; + + # this is mainly for classes as there are purprosely no classes + # for small* + my $main_cmdname; + if ($small_block_associated_command{$cmdname}) { + $main_cmdname = $small_block_associated_command{$cmdname}; + push @classes, $cmdname; + } else { + $main_cmdname = $cmdname; + } + + if ($cmdname eq 'example') { + if ($command->{'args'}) { + for my $example_arg (@{$command->{'args'}}) { + # convert or remove all @-commands, using simple ascii and unicode + # characters + my $converted_arg + = Texinfo::Convert::NodeNameNormalization::convert_to_normalized( + $example_arg); + if ($converted_arg ne '') { + push @classes, 'user-' . $converted_arg; + } + } + } + } elsif ($main_cmdname eq 'lisp') { + push @classes, $main_cmdname; + $main_cmdname = 'example'; + } + + if ($content ne '' and !$self->in_string()) { + if ($self->get_conf('COMPLEX_FORMAT_IN_TABLE') + and $indented_preformatted_commands{$cmdname}) { + return _indent_with_table($self, $cmdname, $content, \@classes); + } else { + unshift @classes, $main_cmdname; + return $self->html_attribute_class('div', \@classes) + .">\n".$content.''."\n"; + } + } else { + return $content; + } +} + +foreach my $preformatted_command (keys(%preformatted_commands)) { + $default_commands_conversion{$preformatted_command} + = \&_convert_preformatted_command; +} + +sub _convert_indented_command($$$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + my $content = shift; + + my @classes; + + my $main_cmdname; + if ($small_block_associated_command{$cmdname}) { + push @classes, $cmdname; + $main_cmdname = $small_block_associated_command{$cmdname}; + } else { + $main_cmdname = $cmdname; + } + if ($content ne '' and !$self->in_string()) { + if ($self->get_conf('COMPLEX_FORMAT_IN_TABLE')) { + return _indent_with_table($self, $main_cmdname, $content, \@classes); + } else { + unshift @classes, $main_cmdname; + return $self->html_attribute_class('blockquote', \@classes).">\n" + . $content . ''."\n"; + } + } else { + return $content; + } +} + +$default_commands_conversion{'indentedblock'} = \&_convert_indented_command; + +sub _convert_verbatim_command($$$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + my $content = shift; + + if (!$self->in_string()) { + return $self->html_attribute_class('pre', [$cmdname]).'>' + .$content . ''; + } else { + return $content; + } +} + +$default_commands_conversion{'verbatim'} = \&_convert_verbatim_command; + +sub _convert_displaymath_command($$$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + my $content = shift; + + if ($self->in_string()) { + return $content; + } + + my $result = ''; + $result .= $self->html_attribute_class('div', [$cmdname]).'>'; + if ($self->get_conf('HTML_MATH') + and $self->get_conf('HTML_MATH') eq 'mathjax') { + $self->register_file_information('mathjax', 1); + $result .= $self->html_attribute_class('em', ['tex2jax_process']).'>' + ."\\[$content\\]".''; + } else { + $result .= $self->html_attribute_class('em').'>'."$content".''; + } + $result .= ''; + return $result; +} + +$default_commands_conversion{'displaymath'} = \&_convert_displaymath_command; + +sub _convert_verbatiminclude_command($$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + + my $verbatim_include_verbatim + = Texinfo::Convert::Utils::expand_verbatiminclude($self, $self, $command); + if (defined($verbatim_include_verbatim)) { + return $self->convert_tree($verbatim_include_verbatim, + 'convert verbatiminclude'); + } else { + return ''; + } +} + +$default_commands_conversion{'verbatiminclude'} + = \&_convert_verbatiminclude_command; + +sub _convert_command_simple_block($$$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + my $content = shift; + + return $self->html_attribute_class('div', [$cmdname]).'>' + .$content.''; +} + +$default_commands_conversion{'raggedright'} = \&_convert_command_simple_block; +$default_commands_conversion{'flushleft'} = \&_convert_command_simple_block; +$default_commands_conversion{'flushright'} = \&_convert_command_simple_block; +$default_commands_conversion{'group'} = \&_convert_command_simple_block; + +sub _convert_sp_command($$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + + if (defined($command->{'extra'}) + and defined($command->{'extra'}->{'misc_args'}->[0])) { + my $sp_nr = $command->{'extra'}->{'misc_args'}->[0]; + if ($self->in_preformatted() or $self->in_string()) { + return "\n" x $sp_nr; + } else { + return ($self->get_info('line_break_element')."\n") x $sp_nr; + } + } +} + +$default_commands_conversion{'sp'} = \&_convert_sp_command; + +sub _convert_exdent_command($$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + + + my $arg = $self->get_pending_formatted_inline_content().$args->[0]->{'normal'}; + + if ($self->in_string()) { + return $arg ."\n"; + } + + # FIXME do something with CSS? Currently nothing is defined for exdent + + if ($self->in_preformatted()) { + return $self->html_attribute_class('pre', [$cmdname]).'>'.$arg ."\n"; + } else { + return $self->html_attribute_class('p', [$cmdname]).'>'.$arg ."\n

"; + } +} + +$default_commands_conversion{'exdent'} = \&_convert_exdent_command; + +sub _convert_center_command($$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + + if ($self->in_string()) { + return $args->[0]->{'normal'}."\n"; + } else { + return $self->html_attribute_class('div', [$cmdname]).">" + .$args->[0]->{'normal'}."\n"; + } +} + +$default_commands_conversion{'center'} = \&_convert_center_command; + +sub _convert_author_command($$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + + return '' if (!$args->[0] or !$command->{'extra'} + or !$command->{'extra'}->{'titlepage'}); + if (!$self->in_string()) { + return $self->html_attribute_class('strong', [$cmdname]) + .">$args->[0]->{'normal'}" + .$self->get_info('line_break_element')."\n"; + } else { + return $args->[0]->{'normal'} . "\n"; + } +} + +$default_commands_conversion{'author'} = \&_convert_author_command; + +sub _convert_title_command($$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + return '' if (!$args->[0]); + if (!$self->in_string()) { + return $self->html_attribute_class('h1', [$cmdname]) + .">$args->[0]->{'normal'}\n"; + } else { + return $args->[0]->{'normal'}; + } +} +$default_commands_conversion{'title'} = \&_convert_title_command; + +sub _convert_subtitle_command($$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + + return '' if (!$args->[0]); + if (!$self->in_string()) { + return $self->html_attribute_class('h3', [$cmdname]) + .">$args->[0]->{'normal'}\n"; + } else { + return $args->[0]->{'normal'}; + } +} +$default_commands_conversion{'subtitle'} = \&_convert_subtitle_command; + +sub _convert_insertcopying_command($$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + + my $global_commands = $self->get_info('global_commands'); + if ($global_commands and $global_commands->{'copying'}) { + return $self->convert_tree({'contents' + => $global_commands->{'copying'}->{'contents'}}, + 'convert insertcopying'); + } + return ''; +} +$default_commands_conversion{'insertcopying'} + = \&_convert_insertcopying_command; + +sub _convert_listoffloats_command($$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + + # should probably never happen + return '' if ($self->in_string()); + + my $floats = $self->get_info('floats'); + my $listoffloats_name = $command->{'extra'}->{'float_type'}; + if ($floats and $floats->{$listoffloats_name} + and scalar(@{$floats->{$listoffloats_name}})) { + my $result = $self->html_attribute_class('dl', [$cmdname]).">\n" ; + foreach my $float (@{$floats->{$listoffloats_name}}) { + my $float_href = $self->command_href($float); + next if (!$float_href); + $result .= '
'; + my $float_text = $self->command_text($float); + if (defined($float_text) and $float_text ne '') { + if ($float_href) { + $result .= "$float_text"; + } else { + $result .= $float_text; + } + } + $result .= '
'; + my $caption; + my $caption_cmdname; + if ($float->{'extra'} and $float->{'extra'}->{'shortcaption'}) { + $caption = $float->{'extra'}->{'shortcaption'}; + $caption_cmdname = 'shortcaption'; + } elsif ($float->{'extra'} and $float->{'extra'}->{'caption'}) { + $caption = $float->{'extra'}->{'caption'}; + $caption_cmdname = 'caption'; + } + + my $caption_text; + my @caption_classes; + if ($caption) { + $caption_text = $self->convert_tree_new_formatting_context( + $caption->{'args'}->[0], $cmdname, 'listoffloats'); + push @caption_classes, "${caption_cmdname}-in-${cmdname}"; + } else { + $caption_text = ''; + } + $result .= $self->html_attribute_class('dd', \@caption_classes).'>' + .$caption_text.''."\n"; + } + return $result . "\n"; + } else { + return ''; + } +} +$default_commands_conversion{'listoffloats'} = \&_convert_listoffloats_command; + +sub _in_preformatted_in_menu($) +{ + my $self = shift; + return 1 if ($self->get_conf('SIMPLE_MENU')); + my @pre_classes = $self->preformatted_classes_stack(); + foreach my $pre_class (@pre_classes) { + return 1 if ($preformatted_commands{$pre_class}); + } + return 0; +} + +sub _convert_menu_command($$$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + my $content = shift; + + return $content if ($cmdname eq 'detailmenu'); + + my $html_menu_entry_index + = $self->shared_conversion_state('html_menu_entry_index', 0); + $$html_menu_entry_index = 0; + + if ($content !~ /\S/) { + return ''; + } + # This can probably only happen with incorrect input, + # for instance menu in copying + # FIXME check? + if ($self->in_string()) { + return $content; + } + + if ($self->get_conf('SIMPLE_MENU')) { + return $self->html_attribute_class('div', [$cmdname]).'>' + .$content ."\n"; + } + my $begin_row = ''; + my $end_row = ''; + if ($self->_in_preformatted_in_menu()) { + $begin_row = ''; + $end_row = ''; + } + return $self->html_attribute_class('table', [$cmdname]) + ." border=\"0\" cellspacing=\"0\">${begin_row}\n" + . $content . "${end_row}\n"; +} +$default_commands_conversion{'menu'} = \&_convert_menu_command; +$default_commands_conversion{'detailmenu'} = \&_convert_menu_command; + +sub _convert_float_command($$$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + my $content = shift; + + my ($caption, $prepended) + = Texinfo::Convert::Converter::float_name_caption($self, $command); + my $caption_command_name; + if (defined($caption)) { + $caption_command_name = $caption->{'cmdname'}; + } + if ($self->in_string()) { + my $prepended_text; + if ($prepended) { + $prepended_text = $self->convert_tree_new_formatting_context( + $prepended, 'float prepended'); + } else { + $prepended_text = ''; + } + my $caption_text = ''; + if ($caption and $caption->{'args'}->[0] + and $caption->{'args'}->[0]->{'contents'}) { + $caption_text = $self->convert_tree_new_formatting_context( + {'contents' => $caption->{'args'}->[0]->{'contents'}}, + 'float caption'); + } + return $prepended.$content.$caption_text; + } + + my $id = $self->command_id($command); + my $id_str = '';; + if (defined($id) and $id ne '') { + $id_str = " id=\"$id\""; + } + + my $prepended_text; + my $caption_text = ''; + if ($prepended) { + # FIXME add a span with a class name for the prependend information + # if not empty? + $prepended_text = $self->convert_tree_new_formatting_context( + {'cmdname' => 'strong', + 'args' => [{'type' => 'brace_command_arg', + 'contents' => [$prepended]}]}, + 'float number type'); + if ($caption) { + # register the converted prepended tree to be prepended to + # the first paragraph in caption formatting + $self->register_pending_formatted_inline_content($caption_command_name, + $prepended_text); + $caption_text = $self->convert_tree_new_formatting_context( + $caption->{'args'}->[0], 'float caption'); + my $cancelled_prepended + = $self->cancel_pending_formatted_inline_content($caption_command_name); + $prepended_text = '' if (not defined($cancelled_prepended)); + } + if ($prepended_text ne '') { + $prepended_text = '

'.$prepended_text.'

'; + } + } else { + $caption_text = $self->convert_tree_new_formatting_context( + $caption->{'args'}->[0], 'float caption') + if (defined($caption)); + } + + my $float_type_number_caption = ''; + if ($caption_text ne '') { + $float_type_number_caption + = $self->html_attribute_class('div', [$caption_command_name]). '>' + .$caption_text.''; + } elsif (defined($prepended) and $prepended_text ne '') { + $float_type_number_caption + = $self->html_attribute_class('div', ['type-number-float']). '>' + . $prepended_text .''; + } + return $self->html_attribute_class('div', [$cmdname]). "${id_str}>\n" + . $content . $float_type_number_caption . ''; +} +$default_commands_conversion{'float'} = \&_convert_float_command; + +sub _convert_quotation_command($$$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + my $content = shift; + + $self->cancel_pending_formatted_inline_content($cmdname); + + my @classes; + + my $main_cmdname; + if ($small_block_associated_command{$cmdname}) { + push @classes, $cmdname; + $main_cmdname = $small_block_associated_command{$cmdname}; + } else { + $main_cmdname = $cmdname; + } + unshift @classes, $main_cmdname; + + my $attribution = ''; + if ($command->{'extra'} and $command->{'extra'}->{'authors'}) { + # FIXME there is no easy way to mark with a class the @author + # @-command. Add a span or a div (@center is in a div)? + foreach my $author (@{$command->{'extra'}->{'authors'}}) { + if ($author->{'args'}->[0] + and $author->{'args'}->[0]->{'contents'}) { + # TRANSLATORS: quotation author + my $centered_author = $self->gdt("\@center --- \@emph{{author}}", + {'author' => $author->{'args'}->[0]->{'contents'}}); + $centered_author->{'parent'} = $command; + $attribution .= $self->convert_tree($centered_author, + 'convert quotation author'); + } + } + } + + if (!$self->in_string()) { + return $self->html_attribute_class('blockquote', \@classes).">\n" + . $content . "\n" . $attribution; + } else { + return $content.$attribution; + } +} +$default_commands_conversion{'quotation'} = \&_convert_quotation_command; + +sub _convert_cartouche_command($$$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + my $content = shift; + + return $content if ($self->in_string()); + + my $title_content = ''; + if ($args->[0] and $args->[0]->{'normal'} ne '') { + $title_content = "\n". $args->[0]->{'normal'} .""; + } + my $cartouche_content = ''; + if ($content =~ /\S/) { + $cartouche_content = "\n". $content .""; + } + if ($cartouche_content ne '' or $title_content ne '') { + return $self->html_attribute_class('table', [$cmdname]) + . " border=\"1\">${title_content}${cartouche_content}" + . "\n"; + } + return $content; +} + +$default_commands_conversion{'cartouche'} = \&_convert_cartouche_command; + +sub _convert_itemize_command($$$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + my $content = shift; + + if ($self->in_string()) { + return $content; + } + my $command_as_argument_name; + my $mark_class_name; + if (defined($command->{'extra'}) + and defined($command->{'extra'}->{'command_as_argument'})) { + my $command_as_argument = $command->{'extra'}->{'command_as_argument'}; + if ($command_as_argument->{'cmdname'} eq 'click' + and $command_as_argument->{'extra'}->{'clickstyle'}) { + $command_as_argument_name = $command_as_argument->{'extra'}->{'clickstyle'}; + } else { + $command_as_argument_name = $command_as_argument->{'cmdname'}; + } + + if ($command_as_argument_name eq 'w') { + $mark_class_name = 'none'; + } else { + $mark_class_name = $command_as_argument_name; + } + } + + if (defined($mark_class_name) + and defined($self->css_get_info('style', 'ul.mark-'.$mark_class_name))) { + return $self->html_attribute_class('ul', [$cmdname, + 'mark-'.$mark_class_name]) + .">\n" . $content. "\n"; + } elsif ($self->get_conf('NO_CSS')) { + return $self->html_attribute_class('ul', [$cmdname]) + .">\n" . $content. "\n"; + } else { + my $css_string + = $self->html_convert_css_string_for_list_mark($command->{'args'}->[0], + 'itemize arg'); + if ($css_string ne '') { + return $self->html_attribute_class('ul', [$cmdname]) + ." style=\"list-style-type: '". + &{$self->formatting_function('format_protect_text')}($self, + $css_string) + . "'\">\n" . $content. "\n"; + } else { + return $self->html_attribute_class('ul', [$cmdname]) + .">\n" . $content. "\n"; + } + } +} + +$default_commands_conversion{'itemize'} = \&_convert_itemize_command; + +sub _convert_enumerate_command($$$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + my $content = shift; + + if ($self->in_string()) { + return $content; + } + if ($content eq '') { + return ''; + } + my $type_attribute = ''; + my $start_attribute = ''; + my $specification = $command->{'extra'}->{'enumerate_specification'}; + if (defined $specification) { + my ($start, $type); + if ($specification =~ /^\d*$/ and $specification ne '1') { + $start = $specification; + } elsif ($specification =~ /^[A-Z]$/) { + $start = 1 + ord($specification) - ord('A'); + $type = 'A'; + } elsif ($specification =~ /^[a-z]$/) { + $start = 1 + ord($specification) - ord('a'); + $type = 'a'; + } + $type_attribute = " type=\"$type\"" if (defined($type)); + $start_attribute = " start=\"$start\"" if (defined($start)); + } + return $self->html_attribute_class('ol', [$cmdname]).$type_attribute + .$start_attribute.">\n" . $content . "\n"; +} + +$default_commands_conversion{'enumerate'} = \&_convert_enumerate_command; + +sub _convert_multitable_command($$$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + my $content = shift; + + if ($self->in_string()) { + return $content; + } + if ($content =~ /\S/) { + return $self->html_attribute_class('table', [$cmdname]).">\n" + . $content . "\n"; + } else { + return ''; + } +} + +$default_commands_conversion{'multitable'} = \&_convert_multitable_command; + +sub _convert_xtable_command($$$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + my $content = shift; + + if ($self->in_string()) { + return $content; + } + if ($content ne '') { + return $self->html_attribute_class('dl', [$cmdname]).">\n" + . $content . "\n"; + } else { + return ''; + } +} +$default_commands_conversion{'table'} = \&_convert_xtable_command; +$default_commands_conversion{'ftable'} = \&_convert_xtable_command; +$default_commands_conversion{'vtable'} = \&_convert_xtable_command; + +sub _convert_item_command($$$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + my $content = shift; + + if ($self->in_string()) { + return $content; + } + if ($command->{'parent'}->{'cmdname'} + and $command->{'parent'}->{'cmdname'} eq 'itemize') { + if ($content =~ /\S/) { + return '
  • ' . $content . '
  • '; + } else { + return ''; + } + } elsif ($command->{'parent'}->{'cmdname'} + and $command->{'parent'}->{'cmdname'} eq 'enumerate') { + if ($content =~ /\S/) { + return '
  • ' . ' ' . $content . '
  • '; + } else { + return ''; + } + } elsif ($command->{'parent'}->{'type'} + and $command->{'parent'}->{'type'} eq 'table_term') { + if ($args->[0]) { + my $table_item_tree = $self->table_item_content_tree($command, + [$args->[0]->{'tree'}]); + my $result = $self->convert_tree($table_item_tree, + 'convert table_item_tree'); + if ($self->in_preformatted()) { + my @pre_classes = $self->preformatted_classes_stack(); + foreach my $pre_class (@pre_classes) { + if ($preformatted_code_commands{$pre_class}) { + $result = $self->html_attribute_class('code', + ['table-term-preformatted-code']).'>' + . $result . ''; + last; + } + } + } + my $open_tag = ($cmdname eq 'item') ? '' : '
    '; + my $index_id = $self->command_id($command); + my $anchor; + my $anchor_span_open = ''; + my $anchor_span_close = ''; + if (defined($index_id)) { + $anchor = $self->_get_copiable_anchor($index_id); + $index_id = ""; + if ($anchor ne '') { + $anchor_span_open = ''; + $anchor_span_close = ''; + } + } else { + $anchor = ''; + $index_id = ''; + } + return "$open_tag$index_id$anchor_span_open$result$anchor$anchor_span_close
    \n"; + } else { + return ''; + } + } elsif ($command->{'parent'}->{'type'} + and $command->{'parent'}->{'type'} eq 'row') { + return &{$self->command_conversion('tab')}($self, $cmdname, $command, + $args, $content); + } + return ''; +} +$default_commands_conversion{'item'} = \&_convert_item_command; +$default_commands_conversion{'headitem'} = \&_convert_item_command; +$default_commands_conversion{'itemx'} = \&_convert_item_command; + +sub _convert_tab_command($$$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + my $content = shift; + + my $cell_nr = $command->{'extra'}->{'cell_number'}; + my $row = $command->{'parent'}; + my $row_cmdname = $row->{'contents'}->[0]->{'cmdname'}; + my $multitable = $row->{'parent'}->{'parent'}; + + my $fractions = ''; + my $cf = $multitable->{'extra'}->{'columnfractions'}; + if ($cf) { + if (exists($cf->{'extra'}->{'misc_args'}->[$cell_nr-1])) { + my $fraction = sprintf('%d', + 100*$cf->{'extra'}->{'misc_args'}->[$cell_nr-1]); + $fractions = " width=\"$fraction%\""; + } + } + + $content =~ s/^\s*//; + $content =~ s/\s*$//; + + if ($self->in_string()) { + return $content; + } + if ($row_cmdname eq 'headitem') { + return "" . $content . ''; + } else { + return "" . $content . ''; + } +} +$default_commands_conversion{'tab'} = \&_convert_tab_command; + +sub _convert_xref_commands($$$$) +{ + my $self = shift; + my $cmdname = shift; + my $root = shift; + my $args = shift; + + my $tree; + my $name; + if ($cmdname ne 'link' and $cmdname ne 'inforef' + and $args->[2] + and defined($args->[2]->{'normal'}) and $args->[2]->{'normal'} ne '') { + $name = $args->[2]->{'normal'}; + } elsif ($args->[1] + and defined($args->[1]->{'normal'}) and $args->[1]->{'normal'} ne '') { + $name = $args->[1]->{'normal'} + } + + if ($cmdname eq 'link' or $cmdname eq 'inforef') { + $args->[3] = $args->[2]; + $args->[2] = undef; + } + + my $file_arg_tree; + my $file = ''; + if ($args->[3] + and defined($args->[3]->{'filenametext'}) + and $args->[3]->{'filenametext'} ne '') { + $file_arg_tree = $args->[3]->{'tree'}; + $file = $args->[3]->{'filenametext'}; + } + + my $book = ''; + $book = $args->[4]->{'normal'} + if ($args->[4] and defined($args->[4]->{'normal'})); + + my $node_arg = $root->{'args'}->[0]; + + # internal reference + if ($cmdname ne 'inforef' and $book eq '' and $file eq '' + and $node_arg and $node_arg->{'extra'} + and defined($node_arg->{'extra'}->{'normalized'}) + and !$node_arg->{'extra'}->{'manual_content'} + and $self->label_command($node_arg->{'extra'}->{'normalized'})) { + my $node + = $self->label_command($node_arg->{'extra'}->{'normalized'}); + # This is the node if USE_NODES, otherwise this may be the sectioning + # command (if the sectioning command is really associated to the node) + my $command = $self->command_root_element_command($node); + $command = $node if (!$node->{'extra'}->{'associated_section'} + or $node->{'extra'}->{'associated_section'} ne $command); + + my $href = $self->command_href($command, undef, $root); + + if (!defined($name)) { + if ($self->get_conf('xrefautomaticsectiontitle') eq 'on' + and $node->{'extra'} + and $node->{'extra'}->{'associated_section'} + # this condition avoids infinite recursions, indeed in that case + # the node will be used and not the section. There should not be + # @*ref in nodes, and even if there are, it does not seems to be + # possible to construct an infinite recursion with nodes only + # as the node must both be a reference target and refer to a specific + # target at the same time, which is not possible. + and not grep {$_ eq $node->{'extra'}->{'associated_section'}} + @{$self->{'referred_command_stack'}}) { + $command = $node->{'extra'}->{'associated_section'}; + $name = $self->command_text($command, 'text_nonumber'); + } elsif ($node->{'cmdname'} eq 'float') { + if (!$self->get_conf('XREF_USE_FLOAT_LABEL')) { + $name = $self->command_text($command); + } + if (!defined($name) or $name eq '') { + if (defined($args->[0]->{'monospace'})) { + $name = $args->[0]->{'monospace'}; + } else { + $name = ''; + } + } + } elsif (!$self->get_conf('XREF_USE_NODE_NAME_ARG') + and (defined($self->get_conf('XREF_USE_NODE_NAME_ARG')) + or !$self->in_preformatted())) { + $name = $self->command_text($command, 'text_nonumber'); + #die "$command $command->{'normalized'}" if (!defined($name)); + } elsif (defined($args->[0]->{'monospace'})) { + $name = $args->[0]->{'monospace'}; + } else { + $name = ''; + } + } + my $reference = $name; + $reference = $self->html_attribute_class('a', [$cmdname]) + ." href=\"$href\">$name" if ($href ne '' + and !$self->in_string()); + + my $is_section = ($command->{'cmdname'} ne 'node' + and $command->{'cmdname'} ne 'anchor' + and $command->{'cmdname'} ne 'float'); + if ($cmdname eq 'pxref') { + $tree = $self->gdt('see {reference_name}', + { 'reference_name' => {'type' => '_converted', 'text' => $reference} }); + } elsif ($cmdname eq 'xref') { + $tree = $self->gdt('See {reference_name}', + { 'reference_name' => {'type' => '_converted', 'text' => $reference} }); + } elsif ($cmdname eq 'ref' or $cmdname eq 'link') { + $tree = $self->gdt('{reference_name}', + { 'reference_name' => {'type' => '_converted', 'text' => $reference} }); + } + } else { + # external reference + + # We setup a label_info based on the node argument and not directly the + # node argument to be able to use the $file argument + my $label_info = {}; + if ($node_arg->{'extra'}) { + $label_info->{'node_content'} = $node_arg->{'extra'}->{'node_content'} + if ($node_arg->{'extra'}->{'node_content'}); + $label_info->{'normalized'} = $node_arg->{'extra'}->{'normalized'} + if (exists($node_arg->{'extra'}->{'normalized'})); + } + # file argument takes precedence over the file in the node (file)node entry + if (defined($file_arg_tree) and $file ne '') { + $label_info->{'manual_content'} = $file_arg_tree->{'contents'}; + } elsif ($node_arg and $node_arg->{'extra'} + and $node_arg->{'extra'}->{'manual_content'}) { + $label_info->{'manual_content'} + = $node_arg->{'extra'}->{'manual_content'}; + my $file_with_node_tree = {'type' => '_code', + 'contents' => [@{$label_info->{'manual_content'}}]}; + $file = $self->convert_tree($file_with_node_tree, 'node file in ref'); + } + my $href = $self->command_href($label_info, undef, $root); + + if ($book eq '') { + if (!defined($name)) { + my $node_name = $self->command_text($label_info); + $name = $node_name; + } + } elsif (!defined($name) and $label_info->{'node_content'}) { + my $node_no_file_tree = {'type' => '_code', + 'contents' => [@{$label_info->{'node_content'}}]}; + my $node_name = $self->convert_tree($node_no_file_tree, 'node in ref'); + if (defined($node_name) and $node_name ne 'Top') { + $name = $node_name; + } + } + + # not exactly sure when it happens. Something like @ref{(file),,,Manual}? + $name = $args->[0]->{'monospace'} + if (!defined($name) + # FIXME could it really be Top? + and $args->[0]->{'monospace'} ne 'Top'); + $name = '' if (!defined($name)); + + my $reference = $name; + my $book_reference = ''; + if (!$self->in_string() and $href ne '') { + # attribute to distiguish links to Texinfo manuals from other links + # and to provide manual name of target + my $manual_name_attribute = ''; + if ($file) { + if (not $self->get_conf('NO_CUSTOM_HTML_ATTRIBUTE')) { + $manual_name_attribute = "data-manual=\"". + &{$self->formatting_function('format_protect_text')}($self, $file)."\" "; + } + } + if ($name ne '') { + $reference = "$name"; + } elsif ($book ne '') { + $book_reference = "$book"; + } + } + if ($cmdname eq 'pxref') { + if (($book ne '') and ($href ne '') and ($reference ne '')) { + $tree = $self->gdt('see {reference} in @cite{{book}}', + { 'reference' => {'type' => '_converted', 'text' => $reference}, + 'book' => {'type' => '_converted', 'text' => $book }}); + } elsif ($book_reference ne '') { + $tree = $self->gdt('see @cite{{book_reference}}', + { 'book_reference' => {'type' => '_converted', + 'text' => $book_reference }}); + } elsif (($book ne '') and ($reference ne '')) { + $tree = $self->gdt('see `{section}\' in @cite{{book}}', + { 'section' => {'type' => '_converted', 'text' => $reference}, + 'book' => {'type' => '_converted', 'text' => $book }}); + } elsif ($book ne '') { # should seldom or even never happen + $tree = $self->gdt('see @cite{{book}}', + {'book' => {'type' => '_converted', 'text' => $book }}); + } elsif ($href ne '') { + $tree = $self->gdt('see {reference}', + { 'reference' => {'type' => '_converted', 'text' => $reference} }); + } elsif ($reference ne '') { + $tree = $self->gdt('see `{section}\'', { + 'section' => {'type' => '_converted', 'text' => $reference} }); + } + } elsif ($cmdname eq 'xref' or $cmdname eq 'inforef') { + if (($book ne '') and ($href ne '') and ($reference ne '')) { + $tree = $self->gdt('See {reference} in @cite{{book}}', + { 'reference' => {'type' => '_converted', 'text' => $reference}, + 'book' => {'type' => '_converted', 'text' => $book }}); + } elsif ($book_reference ne '') { + $tree = $self->gdt('See @cite{{book_reference}}', + { 'book_reference' => {'type' => '_converted', + 'text' => $book_reference }}); + } elsif (($book ne '') and ($reference ne '')) { + $tree = $self->gdt('See `{section}\' in @cite{{book}}', + { 'section' => {'type' => '_converted', 'text' => $reference}, + 'book' => {'type' => '_converted', 'text' => $book }}); + } elsif ($book ne '') { # should seldom or even never happen + $tree = $self->gdt('See @cite{{book}}', + {'book' => {'type' => '_converted', 'text' => $book }}); + } elsif ($href ne '') { + $tree = $self->gdt('See {reference}', + { 'reference' => {'type' => '_converted', 'text' => $reference} }); + } elsif ($reference ne '') { + $tree = $self->gdt('See `{section}\'', { + 'section' => {'type' => '_converted', 'text' => $reference} }); + } + } else { # @ref + if (($book ne '') and ($href ne '') and ($reference ne '')) { + $tree = $self->gdt('{reference} in @cite{{book}}', + { 'reference' => {'type' => '_converted', 'text' => $reference}, + 'book' => {'type' => '_converted', 'text' => $book }}); + } elsif ($book_reference ne '') { + $tree = $self->gdt('@cite{{book_reference}}', + { 'book_reference' => {'type' => '_converted', + 'text' => $book_reference }}); + } elsif (($book ne '') and ($reference ne '')) { + $tree = $self->gdt('`{section}\' in @cite{{book}}', + { 'section' => {'type' => '_converted', 'text' => $reference}, + 'book' => {'type' => '_converted', 'text' => $book }}); + } elsif ($book ne '') { # should seldom or even never happen + $tree = $self->gdt('@cite{{book}}', + {'book' => {'type' => '_converted', 'text' => $book }}); + } elsif ($href ne '') { + $tree = $self->gdt('{reference}', + { 'reference' => {'type' => '_converted', 'text' => $reference} }); + } elsif ($reference ne '') { + $tree = $self->gdt('`{section}\'', { + 'section' => {'type' => '_converted', 'text' => $reference} }); + } + } + if (!defined($tree)) { + # May happen if there is no argument + #die "external: $cmdname, ($args), '$name' '$file' '$book' '$href' '$reference'. tree undef"; + return ''; + } + } + return $self->convert_tree($tree, "convert xref $cmdname"); +} +foreach my $command(keys(%ref_commands)) { + $default_commands_conversion{$command} = \&_convert_xref_commands; +} + +sub _convert_printindex_command($$$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + + my $index_name; + if ($command->{'extra'} and $command->{'extra'}->{'misc_args'} + and defined($command->{'extra'}->{'misc_args'}->[0])) { + $index_name = $command->{'extra'}->{'misc_args'}->[0]; + } else { + return ''; + } + my $index_entries_by_letter = $self->get_info('index_entries_by_letter'); + if (!defined($index_entries_by_letter) + or !$index_entries_by_letter->{$index_name} + or !@{$index_entries_by_letter->{$index_name}}) { + return ''; + } + + #foreach my $letter_entry (@{$index_entries_by_letter->{$index_name}}) { + # print STDERR "IIIIIII $letter_entry->{'letter'}\n"; + # foreach my $index_entry (@{$letter_entry->{'entries'}}) { + # print STDERR " ".join('|', keys(%$index_entry))."||| $index_entry->{'key'}\n"; + # } + #} + return '' if ($self->in_string()); + + my %letter_id; + my %letter_is_symbol; + # First collect the links that are used in entries and in letter summaries + my $symbol_idx = 0; + foreach my $letter_entry (@{$index_entries_by_letter->{$index_name}}) { + my $letter = $letter_entry->{'letter'}; + my $index_element_id = $self->from_element_direction('This', 'target'); + if (!defined($index_element_id)) { + my ($root_element, $root_command) + = $self->get_element_root_command_element($command); + if ($root_command) { + $index_element_id = $self->command_id($root_command); + } + if (not defined($index_element_id)) { + # to avoid duplicate names, use a prefix that cannot happen in anchors + my $target_prefix = 't_i'; + $index_element_id = $target_prefix; + } + } + my $is_symbol = $letter !~ /^\p{Alpha}/; + $letter_is_symbol{$letter} = $is_symbol; + my $identifier; + if ($is_symbol) { + $symbol_idx++; + $identifier = $index_element_id . "_${index_name}_symbol-$symbol_idx"; + } else { + $identifier = $index_element_id . "_${index_name}_letter-${letter}"; + } + $letter_id{$letter} = $identifier; + } + + $self->_new_document_context($cmdname); + + # Next do the entries to determine the letters that are not empty + my @letter_entries; + my $result_index_entries = ''; + my $formatted_index_entries + = $self->shared_conversion_state('formatted_index_entries', {}); + foreach my $letter_entry (@{$index_entries_by_letter->{$index_name}}) { + my $letter = $letter_entry->{'letter'}; + my $entries_text = ''; + my $entry_nr = -1; + # since we normalize, a different formatting will not trigger a new + # formatting of the main entry or a subentry level. This is the + # same for Texinfo TeX + my $normalized_entry_levels = []; + foreach my $index_entry_ref (@{$letter_entry->{'entries'}}) { + $entry_nr++; + my $main_entry_element = $index_entry_ref->{'entry_element'}; + next if ($self->get_conf('NO_TOP_NODE_OUTPUT') + and defined($main_entry_element->{'extra'}->{'element_node'}) + and $main_entry_element->{'extra'}->{'element_node'}->{'extra'} + and $main_entry_element->{'extra'}->{'element_node'} + ->{'extra'}->{'normalized'} + and $main_entry_element->{'extra'}->{'element_node'} + ->{'extra'}->{'normalized'} eq 'Top'); + + # to avoid double error messages, call convert_tree_new_formatting_context + # below with a multiple_pass argument if an entry was already formatted once, + # for example if there are multiple printindex. + if (!$formatted_index_entries->{$index_entry_ref}) { + $formatted_index_entries->{$index_entry_ref} = 1; + } else { + $formatted_index_entries->{$index_entry_ref}++; + } + + my $entry_content_element + = Texinfo::Common::index_content_element($main_entry_element); + + my $in_code = 0; + my $indices_information = $self->get_info('indices_information'); + $in_code = 1 + if ($indices_information->{$index_entry_ref->{'index_name'}}->{'in_code'}); + my $entry_ref_tree = {'contents' => [$entry_content_element]}; + $entry_ref_tree->{'type'} = '_code' if ($in_code); + + # index entry with @seeentry or @seealso + if ($main_entry_element->{'extra'} + and ($main_entry_element->{'extra'}->{'seeentry'} + or $main_entry_element->{'extra'}->{'seealso'})) { + my $referred_entry; + my $seenentry = 1; + if ($main_entry_element->{'extra'}->{'seeentry'}) { + $referred_entry = $main_entry_element->{'extra'}->{'seeentry'}; + } else { + $referred_entry = $main_entry_element->{'extra'}->{'seealso'}; + $seenentry = 0; + } + my @referred_contents; + if ($referred_entry->{'args'} and $referred_entry->{'args'}->[0] + and $referred_entry->{'args'}->[0]->{'contents'}) { + @referred_contents + = @{$referred_entry->{'args'}->[0]->{'contents'}}; + } + my $referred_tree = {'contents' => \@referred_contents}; + $referred_tree->{'type'} = '_code' if ($in_code); + my $entry; + # for @seealso, to appear where chapter/node ususally appear + my $reference = ''; + my $delimiter = ''; + my $entry_class; + my $section_class; + if ($seenentry) { + my $result_tree; + if ($in_code) { + $result_tree + # TRANSLATORS: redirect to another index entry + = $self->gdt('@code{{main_index_entry}}, @emph{See} @code{{seenentry}}', + {'main_index_entry' => $entry_ref_tree, + 'seenentry' => $referred_tree}); + } else { + $result_tree + # TRANSLATORS: redirect to another index entry + = $self->gdt('{main_index_entry}, @emph{See} {seenentry}', + {'main_index_entry' => $entry_ref_tree, + 'seenentry' => $referred_tree}); + } + if ($formatted_index_entries->{$index_entry_ref} > 1) { + # call with multiple_pass argument + $entry = $self->convert_tree_new_formatting_context($result_tree, + "index $index_name l $letter index entry $entry_nr seenentry", + "index formatted $formatted_index_entries->{$index_entry_ref}") + } else { + $entry = $self->convert_tree($result_tree, + "index $index_name l $letter index entry $entry_nr seenentry"); + } + $entry_class = "$cmdname-index-see-entry"; + $section_class = "$cmdname-index-see-entry-section"; + } else { + # TRANSLATORS: refer to another index entry + my $reference_tree = $self->gdt('@emph{See also} {see_also_entry}', + {'see_also_entry' => $referred_tree}); + if ($formatted_index_entries->{$index_entry_ref} > 1) { + # call with multiple_pass argument + $entry = $self->convert_tree_new_formatting_context($entry_ref_tree, + "index $index_name l $letter index entry $entry_nr (with seealso)", + "index formatted $formatted_index_entries->{$index_entry_ref}"); + $reference + = $self->convert_tree_new_formatting_context($reference_tree, + "index $index_name l $letter index entry $entry_nr seealso", + "index formatted $formatted_index_entries->{$index_entry_ref}"); + } else { + $entry = $self->convert_tree($entry_ref_tree, + "index $index_name l $letter index entry $entry_nr (with seealso)"); + $reference + = $self->convert_tree_new_formatting_context($reference_tree, + "index $index_name l $letter index entry $entry_nr seealso"); + } + $entry = '' .$entry .'' if ($in_code); + $delimiter = $self->get_conf('INDEX_ENTRY_COLON'); + # TODO add the information that it is associated with see also? + $entry_class = "$cmdname-index-entry"; + $section_class = "$cmdname-index-see-also"; + } + + $entries_text .= '' + .$self->html_attribute_class('td', [$entry_class]).'>' + . $entry . + $delimiter . '' + .$self->html_attribute_class('td', [$section_class]).'>'; + $entries_text .= $reference; + $entries_text .= "\n"; + + $normalized_entry_levels = []; + next; + } + + # determine the trees and normalized main entry and subentries, to be + # compared with the previous line normalized entries to determine + # what is already formatted as part of the previous lines and + # what levels should be added. The last level is always formatted. + my @new_normalized_entry_levels; + my @entry_trees; + $new_normalized_entry_levels[0] + = uc(Texinfo::Convert::NodeNameNormalization::convert_to_normalized( + $entry_ref_tree)); + $entry_trees[0] = $entry_ref_tree; + my $subentry = $index_entry_ref->{'entry_element'}; + my $subentry_level = 1; + my $subentries_max_level = 2; + while ($subentry->{'extra'} and $subentry->{'extra'}->{'subentry'} + and $subentry_level <= $subentries_max_level) { + $subentry = $subentry->{'extra'}->{'subentry'}; + my @subentry_contents; + if ($subentry->{'args'} and $subentry->{'args'}->[0] + and $subentry->{'args'}->[0]->{'contents'}) { + @subentry_contents = @{$subentry->{'args'}->[0]->{'contents'}}; + } + my $subentry_tree = {'contents' => \@subentry_contents}; + $subentry_tree->{'type'} = '_code' if ($in_code); + if ($subentry_level >= $subentries_max_level) { + # at the max, concatenate the remaining subentries + my $other_subentries_tree = $self->comma_index_subentries_tree($subentry); + push @{$subentry_tree->{'contents'}}, + @{$other_subentries_tree->{'contents'}} + if defined($other_subentries_tree); + } else { + push @new_normalized_entry_levels, + uc(Texinfo::Convert::NodeNameNormalization::convert_to_normalized( + $subentry_tree)); + } + push @entry_trees, $subentry_tree; + $subentry_level ++; + } + #print STDERR join('|', @new_normalized_entry_levels)."\n"; + # last entry, always converted, associated to chapter/node and + # with an hyperlinh + my $entry_tree = pop @entry_trees; + # indentation level of the last entry + my $entry_level = 0; + + # format the leading entries when there are subentries. + # Each on a line with increasing indentation, no hyperlink. + if (scalar(@entry_trees) > 0) { + # find the level not already formatted as part of the previous lines + my $starting_subentry_level = 0; + foreach my $subentry_tree (@entry_trees) { + if ((scalar(@$normalized_entry_levels) > $starting_subentry_level) + and $normalized_entry_levels->[$starting_subentry_level] + eq $new_normalized_entry_levels[$starting_subentry_level]) { + } else { + last; + } + $starting_subentry_level ++; + } + $entry_level = $starting_subentry_level; + foreach my $level ($starting_subentry_level .. scalar(@entry_trees)-1) { + my $entry; + if ($formatted_index_entries->{$index_entry_ref} > 1) { + # call with multiple_pass argument + $entry = $self->convert_tree_new_formatting_context($entry_trees[$level], + "index $index_name l $letter index entry $entry_nr subentry $level", + "index formatted $formatted_index_entries->{$index_entry_ref}") + } else { + $entry = $self->convert_tree($entry_trees[$level], + "index $index_name l $letter index entry $entry_nr subentry $level"); + } + $entry = '' .$entry .'' if ($in_code); + my @td_entry_classes = ("$cmdname-index-entry"); + # indent + if ($level > 0) { + push @td_entry_classes, "index-entry-level-$level"; + } + $entries_text .= '' + # FIXME same class used for leading element of the entry and + # last element of the entry. Could be different. + .$self->html_attribute_class('td', \@td_entry_classes).'>' + . $entry . '' + # empty cell, no section for this line + . "\n"; + + $entry_level = $level+1; + } + } + + my $entry; + if ($formatted_index_entries->{$index_entry_ref} > 1) { + # call with multiple_pass argument + $entry = $self->convert_tree_new_formatting_context($entry_tree, + "index $index_name l $letter index entry $entry_nr", + "index formatted $formatted_index_entries->{$index_entry_ref}") + } else { + $entry = $self->convert_tree($entry_tree, + "index $index_name l $letter index entry $entry_nr"); + } + + next if ($entry !~ /\S/ and $entry_level == 0); + + $normalized_entry_levels = [@new_normalized_entry_levels]; + $entry = '' .$entry .'' if ($in_code); + my $target_element = $index_entry_ref->{'entry_element'}; + $target_element = $index_entry_ref->{'entry_associated_element'} + if ($index_entry_ref->{'entry_associated_element'}); + my $entry_href = $self->command_href($target_element); + my $formatted_entry = "$entry"; + my @td_entry_classes = ("$cmdname-index-entry"); + # subentry + if ($entry_level > 0) { + push @td_entry_classes, "index-entry-level-$entry_level"; + } + + my $associated_command; + if ($self->get_conf('NODE_NAME_IN_INDEX')) { + $associated_command = $main_entry_element->{'extra'}->{'element_node'}; + if (!defined($associated_command)) { + $associated_command + = $self->command_node($target_element); + } + if (!defined($associated_command) + # do not warn if the entry is in a special region, like titlepage + and not $main_entry_element->{'extra'}->{'element_region'} + and $formatted_index_entries->{$index_entry_ref} == 1) { + # NOTE _noticed_line_warn is not used as printindex should not + # happen in multiple tree parsing that lead to ignore_notice being set, + # but the error message is printed only for the first entry formatting. + $self->line_warn($self, + sprintf( + __("entry for index `%s' for \@printindex %s outside of any node"), + $index_entry_ref->{'index_name'}, + $index_name), + $main_entry_element->{'source_info'}); + } + } + if (!$associated_command) { + $associated_command + = $self->command_root_element_command($target_element); + if (!$associated_command) { + # Use Top if not associated command found + $associated_command + = $self->tree_unit_element_command( + $self->global_direction_element('Top')); + # NOTE the warning here catches the most relevant cases of + # index entry that is not associated to the right command, which + # are very few in the test suite. There is also a warning in the + # parser with a much broader scope with possible overlap, but the + # overlap is not a problem. + # NODE_NAME_IN_INDEX may be undef even with USE_NODES set if the + # converter is called as convert() as in the test suite + if (defined($self->get_conf('NODE_NAME_IN_INDEX')) + and not $self->get_conf('NODE_NAME_IN_INDEX') + # do not warn if the entry is in a special region, like titlepage + and not $main_entry_element->{'extra'}->{'element_region'} + and $formatted_index_entries->{$index_entry_ref} == 1) { + # NOTE _noticed_line_warn is not used as printindex should not + # happen in multiple tree parsing that lead to ignore_notice being set, + # but the error message is printed only for the first entry formatting. + # NOTE the index entry may be associated to a node in that case. + $self->line_warn($self, + sprintf( + __("entry for index `%s' for \@printindex %s outside of any section"), + $index_entry_ref->{'index_name'}, + $index_name), + $main_entry_element->{'source_info'}); + } + } + } + my ($associated_command_href, $associated_command_text); + if ($associated_command) { + $associated_command_href = $self->command_href($associated_command); + $associated_command_text = $self->command_text($associated_command); + } + + $entries_text .= '' + .$self->html_attribute_class('td', \@td_entry_classes).'>' + . $formatted_entry . $self->get_conf('INDEX_ENTRY_COLON') . '' + .$self->html_attribute_class('td', ["$cmdname-index-section"]).'>'; + $entries_text + .= "$associated_command_text" + if ($associated_command_href); + $entries_text .= "\n"; + } + # a letter and associated indice entries + if ($entries_text ne '') { + $result_index_entries .= '' . + "". + &{$self->formatting_function('format_protect_text')}($self, $letter) + . "\n" . $entries_text + . "".$self->get_conf('DEFAULT_RULE')."\n"; + push @letter_entries, $letter_entry; + } + } + + # Do the summary letters linking to the letters done above + my @non_alpha = (); + my @alpha = (); + foreach my $letter_entry (@letter_entries) { + my $letter = $letter_entry->{'letter'}; + my $summary_letter_link + = $self->html_attribute_class('a',["summary-letter-$cmdname"]) + ." href=\"#$letter_id{$letter}\">". + &{$self->formatting_function('format_protect_text')}($self, $letter) + .''; + if ($letter_is_symbol{$letter}) { + push @non_alpha, $summary_letter_link; + } else { + push @alpha, $summary_letter_link; + } + } + + if (scalar(@non_alpha) + scalar(@alpha) == 0) { + $self->_pop_document_context(); + return ''; + } + + my $non_breaking_space = $self->get_info('non_breaking_space'); + + # Format the summary letters + my $join = ''; + my $non_alpha_text = ''; + my $alpha_text = ''; + if (scalar(@non_alpha) + scalar(@alpha) > 1) { + $join = " $non_breaking_space \n".$self->get_info('line_break_element')."\n" + if (scalar(@non_alpha) and scalar(@alpha)); + if (scalar(@non_alpha)) { + $non_alpha_text = join("\n $non_breaking_space \n", @non_alpha) . "\n"; + } + if (scalar(@alpha)) { + $alpha_text = join("\n $non_breaking_space \n", @alpha) + . "\n $non_breaking_space \n"; + } + } + my $result = $self->html_attribute_class('div', + [$cmdname, "$index_name-$cmdname"]).">\n"; + # format the summary + if (scalar(@non_alpha) + scalar(@alpha) > 1) { + my $summary_header = $self->html_attribute_class('table', + ["$index_name-letters-header-$cmdname"]).'>' + # TRANSLATORS: before list of letters and symbols grouping index entries + . $self->convert_tree($self->gdt('Jump to')) .": $non_breaking_space " . + $non_alpha_text . $join . $alpha_text . "\n"; + + $result .= $summary_header; + } + + # now format the index entries + $result + .= $self->html_attribute_class('table', ["$index_name-entries-$cmdname"]) + ." border=\"0\">\n" . '' + . $self->html_attribute_class('th', ["entries-header-$cmdname"]).'>' + # TRANSLATORS: index entries column header in index formatting + . $self->convert_tree($self->gdt('Index Entry')) .'' + . $self->html_attribute_class('th', ["sections-header-$cmdname"]).'>' + # TRANSLATORS: section of index entry column header in index formatting + . $self->convert_tree($self->gdt('Section')) . "\n" + . "".$self->get_conf('DEFAULT_RULE') + ."\n"; + $result .= $result_index_entries; + $result .= "\n"; + + $self->_pop_document_context(); + + if (scalar(@non_alpha) + scalar(@alpha) > 1) { + my $summary_footer = $self->html_attribute_class('table', + ["$index_name-letters-footer-$cmdname"]).'>' + # TRANSLATORS: before list of letters and symbols grouping index entries + . $self->convert_tree($self->gdt('Jump to')) + . ": $non_breaking_space " + . $non_alpha_text . $join . $alpha_text . "\n"; + $result .= $summary_footer + } + return $result . "\n"; +} +$default_commands_conversion{'printindex'} = \&_convert_printindex_command; + +sub _contents_inline_element($$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + + print STDERR "CONTENTS_INLINE $cmdname\n" if ($self->get_conf('DEBUG')); + my $content = &{$self->formatting_function('format_contents')}($self, + $cmdname, $command); + if ($content) { + my ($special_element_variety, $special_element, $class_base, + $special_element_direction) + = $self->command_name_special_element_information($cmdname); + # FIXME is element- the best prefix? + my $result = $self->html_attribute_class('div', ["element-${class_base}"]); + my $heading; + if ($special_element) { + my $id = $self->command_id($special_element); + if (defined($id) and $id ne '') { + $result .= " id=\"$id\""; + } + $heading = $self->command_text($special_element); + } else { + # happens when called as convert() and not output() + #cluck "$cmdname special element not defined"; + my $heading_tree = $self->special_element_info('heading_tree', + $special_element_variety); + if (defined($heading_tree)) { + $heading = $self->convert_tree($heading_tree, + "convert $cmdname special heading"); + } else { + $heading = ''; + } + } + $result .= ">\n"; + $result .= &{$self->formatting_function('format_heading_text')}($self, + $cmdname, [$class_base.'-heading'], $heading, + $self->get_conf('CHAPTER_HEADER_LEVEL'))."\n"; + $result .= $content . "\n"; + return $result; + } + return ''; +} + +sub _convert_informative_command($$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + + return '' if ($self->in_string()); + + Texinfo::Common::set_informative_command_value($self, $command); + + return ''; +} + +foreach my $informative_command (@informative_global_commands) { + $default_commands_conversion{$informative_command} + = \&_convert_informative_command; +} + +sub _convert_contents_command($$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + + return '' if ($self->in_string()); + $cmdname = 'shortcontents' if ($cmdname eq 'summarycontents'); + + Texinfo::Common::set_informative_command_value($self, $command); + + my $structuring = $self->get_info('structuring'); + if ($self->get_conf('CONTENTS_OUTPUT_LOCATION') eq 'inline' + and ($cmdname eq 'contents' or $cmdname eq 'shortcontents') + and $self->get_conf($cmdname) + and $structuring and $structuring->{'sectioning_root'} + and scalar(@{$structuring->{'sections_list'}}) > 1) { + return $self->_contents_inline_element($cmdname, $command); + } + return ''; +} + +foreach my $contents_comand (@contents_commands) { + $default_commands_conversion{$contents_comand} = \&_convert_contents_command; +} + +# associate same formatting function for @small* command +# as for the associated @-command +foreach my $small_command (keys(%small_block_associated_command)) { + $default_commands_conversion{$small_command} + = $default_commands_conversion{$small_block_associated_command{$small_command}}; +} + +sub _open_quotation_command($$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + + my $formatted_quotation_arg_to_prepend; + if ($command->{'args'} and $command->{'args'}->[0] + and $command->{'args'}->[0]->{'contents'} + and @{$command->{'args'}->[0]->{'contents'}}) { + $formatted_quotation_arg_to_prepend + = $self->convert_tree($self->gdt('@b{{quotation_arg}:} ', + {'quotation_arg' => $command->{'args'}->[0]->{'contents'}}), + "open $cmdname prepended arg"); + } + $self->register_pending_formatted_inline_content($cmdname, + $formatted_quotation_arg_to_prepend); + return ''; +} + +$default_commands_open{'quotation'} = \&_open_quotation_command; + +# associate same opening function for @small* command +# as for the associated @-command +foreach my $small_command (keys(%small_block_associated_command)) { + if (exists($default_commands_open{$small_block_associated_command{$small_command}})) { + $default_commands_open{$small_command} + = $default_commands_open{$small_block_associated_command{$small_command}}; + } +} + +# Keys are tree element types, values are function references to convert +# elements of that type. Can be overridden accessing +# Texinfo::Config::GNUT_get_types_conversion, setup by +# Texinfo::Config::texinfo_register_type_formatting() +my %default_types_conversion; + +sub default_type_conversion($$) +{ + my $self = shift; + my $type = shift; + return $default_types_conversion{$type}; +} + +sub type_conversion($$) +{ + my $self = shift; + my $type = shift; + return $self->{'types_conversion'}->{$type}; +} + +my %default_types_open; + +sub default_type_open($$) +{ + my $self = shift; + my $type = shift; + return $default_types_open{$type}; +} + + +# Ignored commands +foreach my $type ('ignorable_spaces_after_command', 'postamble_after_end', + 'preamble_before_beginning', + 'preamble_before_setfilename', + 'spaces_at_end', + 'spaces_before_paragraph', + 'spaces_after_close_brace') { + $default_types_conversion{$type} = undef; +} + +sub _convert_paragraph_type($$$$) +{ + my $self = shift; + my $type = shift; + my $element = shift; + my $content = shift; + + $content = $self->get_associated_formatted_inline_content($element).$content; + + if ($self->paragraph_number() == 1) { + my $in_format = $self->top_block_command(); + if ($in_format) { + # no first paragraph in those environment to avoid extra spacing + if ($in_format eq 'itemize' + or $in_format eq 'enumerate' + or $in_format eq 'multitable') { + return $content; + } + } + } + return $content if ($self->in_string()); + + if ($content =~ /\S/) { + my $align = $self->in_align(); + if ($align and $HTML_align_commands{$align}) { + return $self->html_attribute_class('p', [$align.'-paragraph']).">" + .$content."

    "; + } else { + return "

    ".$content."

    "; + } + } else { + return ''; + } +} + +$default_types_conversion{'paragraph'} = \&_convert_paragraph_type; + + +sub _open_inline_container_type($$$) +{ + my $self = shift; + my $type = shift; + my $element = shift; + + my $pending_formatted = $self->get_pending_formatted_inline_content(); + + if (defined($pending_formatted)) { + $self->associate_pending_formatted_inline_content($element, $pending_formatted); + } + return ''; +} + +$default_types_open{'paragraph'} = \&_open_inline_container_type; +$default_types_open{'preformatted'} = \&_open_inline_container_type; + + +sub _preformatted_class() +{ + my $self = shift; + my $pre_class; + my @pre_classes = $self->preformatted_classes_stack(); + foreach my $class (@pre_classes) { + # FIXME maybe add or $pre_class eq 'menu' to override + # 'menu' with 'menu-comment'? + $pre_class = $class unless ($pre_class + and $preformatted_code_commands{$pre_class} + and !($preformatted_code_commands{$class} + or $class eq 'menu')); + } + return $pre_class.'-preformatted'; +} + +sub _convert_preformatted_type($$$$) +{ + my $self = shift; + my $type = shift; + my $element = shift; + my $content = shift; + + if (!defined($content)) { + cluck "content undef in _convert_preformatted_type " + .Texinfo::Common::debug_print_element($element, 1); + } + + $content = $self->get_associated_formatted_inline_content($element).$content; + + return '' if ($content eq ''); + + my $pre_class = $self->_preformatted_class(); + + if ($self->top_block_command() eq 'multitable') { + $content =~ s/^\s*//; + $content =~ s/\s*$//; + } + + # menu_entry_description is always in a preformatted container + # in the tree, as the whole menu is meant to be an + # environment where spaces and newlines are preserved. + if ($element->{'parent'}->{'type'} + and $element->{'parent'}->{'type'} eq 'menu_entry_description') { + if (!$self->_in_preformatted_in_menu()) { + # If not in preformatted block command (nor in SIMPLE_MENU), + # we don't preserve spaces and newlines in menu_entry_description, + # instead the whole menu_entry is in a table, so no
     in that situation
    +      return $content;
    +    } else {
    +      # if directly in description, we want to avoid the linebreak that
    +      # comes with pre being a block level element, so set a special class
    +      $pre_class = 'menu-entry-description-preformatted';
    +    }
    +  }
    +
    +  if ($self->in_string()) {
    +    return $content;
    +  }
    +  $content =~ s/^\n/\n\n/; # a newline immediately after a 
     is ignored.
    +  my $result = $self->html_attribute_class('pre', [$pre_class]).'>'
    +                                                   . $content . '
    '; + + # this may happen with lines without textual content + # between a def* and def*x. + if ($element->{'parent'}->{'cmdname'} + and $element->{'parent'}->{'cmdname'} =~ /^def/) { + $result = '
    '.$result.'
    '; + } + return $result; +} + +$default_types_conversion{'preformatted'} = \&_convert_preformatted_type; + +sub _convert_balanced_braces_type($$$$) { + my $self = shift; + my $type = shift; + my $element = shift; + my $content = shift; + + return $content; +} + +$default_types_conversion{'balanced_braces'} = \&_convert_balanced_braces_type; + +# use the type and not the index commands names, as they are diverse and +# can be dynamically added, so it is difficult to use as selector for output +# formatting. The command name can be obtained here as $element->{'cmdname'}. +sub _convert_index_entry_command_type($$$$) +{ + my $self = shift; + my $type = shift; + my $element = shift; + my $content = shift; + + my $index_id = $self->command_id($element); + if (defined($index_id) and $index_id ne '' + and !$self->in_multi_expanded() + and !$self->in_string()) { + my $result = &{$self->formatting_function('format_separate_anchor')}($self, + $index_id, 'index-entry-id'); + $result .= "\n" unless ($self->in_preformatted()); + return $result; + } + return ''; +} +$default_types_conversion{'index_entry_command'} = \&_convert_index_entry_command_type; + +sub _convert_definfoenclose_type($$$$) { + my $self = shift; + my $type = shift; + my $element = shift; + my $content = shift; + + # FIXME add a span to mark the original command as a class? + return &{$self->formatting_function('format_protect_text')}($self, + $element->{'extra'}->{'begin'}) + . $content . + &{$self->formatting_function('format_protect_text')}($self, + $element->{'extra'}->{'end'}); +} + +$default_types_conversion{'definfoenclose_command'} + = \&_convert_definfoenclose_type; + +# Note: has an XS override +sub _entity_text +{ + my $text = shift; + + $text =~ s/---/\&mdash\;/g; + $text =~ s/--/\&ndash\;/g; + $text =~ s/``/\&ldquo\;/g; + $text =~ s/''/\&rdquo\;/g; + $text =~ s/'/\&rsquo\;/g; + $text =~ s/`/\&lsquo\;/g; + + return $text; +} + +sub _convert_text($$$) +{ + my $self = shift; + my $type = shift; + my $element = shift; + my $text = shift; + + my $context = $self->{'document_context'}->[-1]; + + # API info: in_verbatim() API code conforming would be: + #if ($self->in_verbatim()) { + if ($context->{'verbatim'}) { # inline these calls for speed + # API info: using the API to allow for customization would be: + #return &{$self->formatting_function('format_protect_text')}($self, $text); + return $self->_default_format_protect_text($text); + } + return $text if $context->{'raw'}; + # API info: in_raw() API code conforming would be: + #return $text if ($self->in_raw()); + + my $formatting_context = $context->{'formatting_context'}->[-1]; + $text = uc($text) if $formatting_context->{'upper_case'}; + # API info: in_upper_case() API code conforming would be: + #$text = uc($text) if ($self->in_upper_case()); + + # API info: using the API to allow for customization would be: + #$text = &{$self->formatting_function('format_protect_text')}($self, $text); + $text = _default_format_protect_text($self, $text); + + # API info: get_conf() API code conforming would be: + #if ($self->get_conf('OUTPUT_CHARACTERS') + # and $self->get_conf('OUTPUT_ENCODING_NAME') + # and $self->get_conf('OUTPUT_ENCODING_NAME') eq 'utf-8') { + if ($self->{'conf'}->{'OUTPUT_CHARACTERS'} + and $self->{'conf'}->{'OUTPUT_ENCODING_NAME'} + and $self->{'conf'}->{'OUTPUT_ENCODING_NAME'} eq 'utf-8') { + $text = Texinfo::Convert::Unicode::unicode_text($text, + (in_code($self) or in_math($self))); + # API info: in_code() API code conforming and + # API info: in_math() API code conforming would be: + #} elsif (!$self->in_code() and !$self->in_math()) { + } elsif (!$context->{'monospace'}->[-1] and !$context->{'math'}) { + # API info: get_conf() API code conforming would be: + #if ($self->get_conf('USE_NUMERIC_ENTITY')) { + if ($self->{'conf'}->{'USE_NUMERIC_ENTITY'}) { + $text = $self->xml_format_text_with_numeric_entities($text); + # API info: get_conf() API code conforming would be: + #} elsif ($self->get_conf('USE_ISO')) { + } elsif ($self->{'conf'}->{'USE_ISO'}) { + $text = _entity_text($text); + } else { + $text =~ s/``/"/g; + $text =~ s/''/"/g; + $text =~ s/---/\x{1F}/g; + $text =~ s/--/-/g; + $text =~ s/\x{1F}/--/g; + } + } + + return $text if (in_preformatted($self)); + + # API info: in_non_breakable_space() API code conforming would be: + #if ($self->in_non_breakable_space()) { + if ($formatting_context->{'no_break'}) { + my $non_breaking_space = $self->get_info('non_breaking_space'); + $text =~ s/\n/ /g; + $text =~ s/ +/$non_breaking_space/g; + # API info: in_space_protected() API code conforming would be: + #} elsif ($self->in_space_protected()) { + } elsif ($formatting_context->{'space_protected'}) { + if (chomp($text)) { + # API info: API code conforming would be: + # $self->get_info('line_break_element') + my $line_break_element = $self->{'line_break_element'}; + # protect spaces in line_break_element formatting. + # Note that this case is theoretical right now, as it is not possible + # to redefine $self->{'line_break_element'} and there are no spaces + # in the possible values. However this is a deficiency of the API, + # it would be better to be able to redefine $self->{'line_break_element'} + $line_break_element =~ s/ /\x{1F}/g; + $text .= $line_break_element; + } + # Protect spaces within text + my $non_breaking_space = $self->get_info('non_breaking_space'); + $text =~ s/ /$non_breaking_space/g; + # Revert protected spaces in leading html attribute + $text =~ s/\x{1F}/ /g; + } + return $text; +} + +$default_types_conversion{'text'} = \&_convert_text; + +sub _css_string_convert_text($$$) +{ + my $self = shift; + my $type = shift; + my $element = shift; + my $text = shift; + + $text = uc($text) if ($self->in_upper_case()); + + # need to hide \ otherwise it is protected in protect_text + if (!$self->in_code() and !$self->in_math()) { + $text =~ s/---/\x{1F}2014 /g; + $text =~ s/--/\x{1F}2013 /g; + $text =~ s/``/\x{1F}201C /g; + $text =~ s/''/\x{1F}201D /g; + $text =~ s/'/\x{1F}2019 /g; + $text =~ s/`/\x{1F}2018 /g; + } + + $text + = &{$self->formatting_function('format_protect_text')}($self, $text); + $text =~ s/\x{1F}/\\/g; + + return $text; +} +$default_css_string_types_conversion{'text'} = \&_css_string_convert_text; + +sub _simplify_text_for_comparison($) +{ + my $text = shift; + $text =~ s/[^\p{Word}]//g; + return $text; +} + +sub _convert_row_type($$$$) { + my $self = shift; + my $type = shift; + my $element = shift; + my $content = shift; + + return $content if ($self->in_string()); + if ($content =~ /\S/) { + my $result = '' . $content . ''; + if ($element->{'contents'} + and scalar(@{$element->{'contents'}}) + and $element->{'contents'}->[0]->{'cmdname'} ne 'headitem') { + # if headitem, end of line added in _convert_multitable_head_type + $result .= "\n"; + } + return $result; + } else { + return ''; + } +} +$default_types_conversion{'row'} = \&_convert_row_type; + +sub _convert_multitable_head_type($$$$) { + my $self = shift; + my $type = shift; + my $element = shift; + my $content = shift; + + return $content if ($self->in_string()); + if ($content =~ /\S/) { + return '' . $content . '' . "\n"; + } else { + return ''; + } +} + +$default_types_conversion{'multitable_head'} = \&_convert_multitable_head_type; + +sub _convert_multitable_body_type($$$$) { + my $self = shift; + my $type = shift; + my $element = shift; + my $content = shift; + + return $content if ($self->in_string()); + if ($content =~ /\S/) { + return '' . $content . '' . "\n"; + } else { + return ''; + } +} + +$default_types_conversion{'multitable_body'} = \&_convert_multitable_body_type; + +sub _convert_menu_entry_type($$$) +{ + my $self = shift; + my $type = shift; + my $element = shift; + + my $name_entry; + my $menu_description; + my $menu_entry_node; + my $menu_entry_leading_text; + my @menu_entry_separators; + + foreach my $arg (@{$element->{'contents'}}) { + if ($arg->{'type'} eq 'menu_entry_leading_text') { + $menu_entry_leading_text = $arg; + } elsif ($arg->{'type'} eq 'menu_entry_name') { + $name_entry = $arg; + } elsif ($arg->{'type'} eq 'menu_entry_description') { + $menu_description = $arg; + } elsif ($arg->{'type'} eq 'menu_entry_separator') { + push @menu_entry_separators, $arg; + } elsif ($arg->{'type'} eq 'menu_entry_node') { + $menu_entry_node = $arg; + } + } + + my $href = ''; + my $rel = ''; + my $section; + my $label_info = $menu_entry_node->{'extra'}; + + # external node + my $external_node; + if ($label_info and $label_info->{'manual_content'}) { + $href = $self->command_href($label_info, undef, $element); + $external_node = 1; + # may not be defined in case of menu entry node consisting only of spaces + } elsif ($label_info and defined($label_info->{'normalized'})) { + my $node = $self->label_command($label_info->{'normalized'}); + if ($node) { + # if !NODE_NAME_IN_MENU, we pick the associated section, except if + # the node is the element command + if ($node->{'extra'} + and $node->{'extra'}->{'associated_section'} + and !$self->get_conf('NODE_NAME_IN_MENU') + and !($self->command_root_element_command($node) eq $node)) { + $section = $node->{'extra'}->{'associated_section'}; + $href = $self->command_href($section, undef, $element); + } else { + $href = $self->command_href($node, undef, $element); + } + if ($node->{'extra'} and $node->{'extra'}->{'isindex'}) { + # Mark the target as an index. See + # http://microformats.org/wiki/existing-rel-values#HTML5_link_type_extensions + $rel = ' rel="index"'; + } + } + } + + my $html_menu_entry_index + = $self->shared_conversion_state('html_menu_entry_index', 0); + ${$html_menu_entry_index}++; + my $accesskey = ''; + $accesskey = " accesskey=\"$$html_menu_entry_index\"" + if ($self->get_conf('USE_ACCESSKEY') and $$html_menu_entry_index < 10); + + my $MENU_SYMBOL = $self->get_conf('MENU_SYMBOL'); + my $MENU_ENTRY_COLON = $self->get_conf('MENU_ENTRY_COLON'); + + my $in_string = $self->in_string(); + if ($self->_in_preformatted_in_menu() or $in_string) { + my $leading_text = $menu_entry_leading_text->{'text'}; + $leading_text =~ s/\*/$MENU_SYMBOL/; + my $result_name_node = $leading_text; + + if ($name_entry) { + $result_name_node + .= $self->convert_tree($name_entry, + "menu_arg menu_entry_name preformatted"); + my $name_separator = shift @menu_entry_separators; + $result_name_node + .= $self->convert_tree($name_separator, + "menu_arg name separator preformatted"); + } + + if ($menu_entry_node) { + # 'contents' seems to always be defined. If it is + # not the case, it should not be an issue as an undefined + # 'contents' is ignored. + my $name = $self->convert_tree( + {'type' => '_code', + 'contents' => $menu_entry_node->{'contents'}}, + "menu_arg menu_entry_node preformatted"); + if ($href ne '' and !$in_string) { + $result_name_node .= "".$name.""; + } else { + $result_name_node .= $name; + } + } + if (scalar(@menu_entry_separators)) { + my $node_separator = shift @menu_entry_separators; + $result_name_node + .= $self->convert_tree($node_separator, + "menu_arg node separator preformatted"); + } + + if (!$self->get_conf('SIMPLE_MENU') and not $in_string) { + my $pre_class = $self->_preformatted_class(); + $result_name_node = $self->html_attribute_class('pre', [$pre_class]).'>' + . $result_name_node . '
    '; + } + + my $description = ''; + if ($menu_description) { + $description .= $self->convert_tree($menu_description, + "menu_arg description preformatted"); + } + + return $result_name_node . $description; + } + + my $name; + my $name_no_number; + if ($section) { + $name = $self->command_text($section); + $name_no_number = $self->command_text($section, 'text_nonumber'); + if ($href ne '' and $name ne '') { + $name = "".$name.""; + } + } + if (!defined($name) or $name eq '') { + if ($name_entry) { + $name = $self->convert_tree($name_entry, 'convert menu_entry_name'); + } + if (!defined($name) or $name eq '') { + if ($label_info and $label_info->{'manual_content'}) { + $name = $self->command_text($label_info); + } elsif ($label_info) { + $name = $self->convert_tree({'type' => '_code', + 'contents' => $label_info->{'node_content'}}, + 'menu_arg name'); + } else { + $name = ''; + } + } + $name =~ s/^\s*//; + $name_no_number = $name; + if ($href ne '') { + $name = "".$name.""; + } + $name = "$MENU_SYMBOL ".$name; + } + my $description = ''; + if ($menu_description) { + $description = $self->convert_tree($menu_description, + 'menu_arg description'); + if ($self->get_conf('AVOID_MENU_REDUNDANCY')) { + $description = '' if (_simplify_text_for_comparison($name_no_number) + eq _simplify_text_for_comparison($description)); + } + } + my $non_breaking_space = $self->get_info('non_breaking_space'); + return '' + .$self->html_attribute_class('td', ['menu-entry-destination']).'>' + ."$name$MENU_ENTRY_COLON" + ."${non_breaking_space}${non_breaking_space}" + .$self->html_attribute_class('td', ['menu-entry-description']).'>' + ."$description\n"; +} + +$default_types_conversion{'menu_entry'} = \&_convert_menu_entry_type; + +sub _convert_menu_comment_type($$$$) +{ + my $self = shift; + my $type = shift; + my $element = shift; + my $content = shift; + + if ($self->_in_preformatted_in_menu() or $self->in_string()) { + return $content; + } else { + return ''.$self->html_attribute_class('th', ['menu-comment']) + . ' colspan="3">'.$content .''; + } +} + +$default_types_conversion{'menu_comment'} = \&_convert_menu_comment_type; + +sub _convert_before_item_type($$$$) +{ + my $self = shift; + my $type = shift; + my $element = shift; + my $content = shift; + + return '' if ($content !~ /\S/); + return $content if ($self->in_string()); + my $top_block_command = $self->top_block_command(); + if ($top_block_command eq 'itemize' or $top_block_command eq 'enumerate') { + return '
  • '. $content .'
  • '; + } elsif ($top_block_command eq 'table' or $top_block_command eq 'vtable' + or $top_block_command eq 'ftable') { + return '
    '. $content .'
    '."\n"; + } elsif ($top_block_command eq 'multitable') { + $content =~ s/^\s*//; + $content =~ s/\s*$//; + + return ''.$content.''."\n"; + } +} + +$default_types_conversion{'before_item'} = \&_convert_before_item_type; + +sub _convert_table_term_type($$$$) +{ + my $self = shift; + my $type = shift; + my $element = shift; + my $content = shift; + + return '
    '.$content; +} + +$default_types_conversion{'table_term'} = \&_convert_table_term_type; + +sub _convert_def_line_type($$$$) +{ + my $self = shift; + my $type = shift; + my $element = shift; + my $content = shift; + + if ($self->in_string()) { + # should probably never happen + return &{$self->formatting_function('format_protect_text')}($self, + Texinfo::Convert::Text::convert_to_text( + $element, Texinfo::Convert::Text::copy_options_for_convert_text($self))); + } + + my $index_label = ''; + my $index_id = $self->command_id($element); + if (defined($index_id) and $index_id ne '' and !$self->in_multi_expanded()) { + $index_label = " id=\"$index_id\""; + } + my ($category_element, $class_element, + $type_element, $name_element, $arguments) + = Texinfo::Convert::Utils::definition_arguments_content($element); + + my @classes = (); + my $command_name; + if ($Texinfo::Common::def_aliases{$element->{'extra'}->{'def_command'}}) { + $command_name + = $Texinfo::Common::def_aliases{$element->{'extra'}->{'def_command'}}; + } else { + $command_name = $element->{'extra'}->{'def_command'}; + } + my $original_command_name; + if ($Texinfo::Common::def_aliases{$element->{'extra'}->{'original_def_cmdname'}}) { + my $original_def_cmdname = $element->{'extra'}->{'original_def_cmdname'}; + $original_command_name = $Texinfo::Common::def_aliases{$original_def_cmdname}; + push @classes, "$original_def_cmdname-alias-$original_command_name"; + } else { + $original_command_name = $element->{'extra'}->{'original_def_cmdname'}; + } + if ($command_name ne $original_command_name) { + push @classes, "def-cmd-$command_name"; + } + unshift @classes, $original_command_name; + + my $result_type = ''; + if ($type_element) { + my $type_text = $self->_convert({'type' => '_code', + 'contents' => [$type_element]}); + if ($type_text ne '') { + $result_type = $self->html_attribute_class('code', ['def-type']).'>'. + $type_text .''; + } + if ($self->get_conf('deftypefnnewline') eq 'on' + and ($command_name eq 'deftypefn' or $command_name eq 'deftypeop')) { + $result_type .= $self->get_info('line_break_element'); + } + } + + my $result_name = ''; + if ($name_element) { + $result_name = $self->html_attribute_class('strong', ['def-name']).'>'. + $self->_convert({'type' => '_code', 'contents' => [$name_element]}) + .''; + } + + my $def_space = ' '; + if ($element->{'extra'}->{'omit_def_name_space'}) { + $def_space = ''; + } + + my $result_arguments = ''; + if ($arguments) { + # arguments not only metasyntactic variables + # (deftypefn, deftypevr, deftypeop, deftypecv) + if ($Texinfo::Common::def_no_var_arg_commands{$command_name}) { + my $arguments_formatted = $self->_convert({'type' => '_code', + 'contents' => $arguments}); + $result_arguments = $self->html_attribute_class('code', + ['def-code-arguments']).'>' + . $arguments_formatted.'' + if ($arguments_formatted =~ /\S/); + } else { + # only metasyntactic variable arguments (deffn, defvr, deftp, defop, defcv) + push @{$self->{'document_context'}->[-1]->{'monospace'}}, 0; + my $arguments_formatted = $self->_convert({'contents' => $arguments}); + pop @{$self->{'document_context'}->[-1]->{'monospace'}}; + if ($arguments_formatted =~ /\S/) { + $result_arguments = $self->html_attribute_class('var', + ['def-var-arguments']).'>' + . $arguments_formatted .''; + } + } + } + + my $def_call = ''; + $def_call .= $result_type . ' ' if ($result_type ne ''); + $def_call .= $result_name; + $def_call .= $def_space . $result_arguments if ($result_arguments ne ''); + + if ($self->get_conf('DEF_TABLE')) { + my $category_result = ''; + my $definition_category_tree + = Texinfo::Convert::Utils::definition_category_tree($self, $element); + $category_result + = $self->convert_tree({'contents' => [$definition_category_tree]}) + if (defined($definition_category_tree)); + + return $self->html_attribute_class('tr', \@classes) + . "$index_label>".$self->html_attribute_class('td', ['call-def']).'>' + . $def_call . ''.$self->html_attribute_class('td', ['category-def']) + . '>' . '[' . $category_result . ']' . "\n"; + } + + my $category_result = ''; + my $category_tree; + if ($category_element) { + if ($class_element) { + if ($command_name eq 'deftypeop' + and $type_element + and $self->get_conf('deftypefnnewline') eq 'on') { + $category_tree = $self->gdt('{category} on @code{{class}}:@* ', + {'category' => $category_element, + 'class' => $class_element}); + } elsif ($command_name eq 'defop' or $command_name eq 'deftypeop') { + $category_tree = $self->gdt('{category} on @code{{class}}: ', + {'category' => $category_element, + 'class' => $class_element}); + } elsif ($command_name eq 'defcv' or $command_name eq 'deftypecv') { + $category_tree = $self->gdt('{category} of @code{{class}}: ', + {'category' => $category_element, + 'class' => $class_element}); + } + } elsif ($type_element + and ($command_name eq 'deftypefn' or $command_name eq 'deftypeop') + and $self->get_conf('deftypefnnewline') eq 'on') { + # FIXME if in @def* in @example and with @deftypefnnewline + # on there is no effect of @deftypefnnewline on, as @* in + # preformatted environment becomes an end of line, but the def* + # line is not in a preformatted environment. There should be + # an explicit
    in that case. Probably requires changing + # the conversion of @* in a @def* line in preformatted, nothing + # really specific of @deftypefnnewline on. + $category_tree = $self->gdt('{category}:@* ', + {'category' => $category_element}); + } else { + $category_tree = $self->gdt('{category}: ', {'category' => $category_element}); + } + $category_result = $self->convert_tree($category_tree); + } + + if ($category_result ne '') { + my $open = $self->html_attribute_class('span', ['category-def']); + if ($open ne '') { + $category_result = $open.'>'.$category_result.''; + } + } + my $anchor_span_open = ''; + my $anchor_span_close = ''; + my $anchor = $self->_get_copiable_anchor($index_id); + if ($anchor ne '') { + $anchor_span_open = ''; + $anchor_span_close = ''; + } + return $self->html_attribute_class('dt', \@classes) + . "$index_label>" . $category_result . $anchor_span_open + . $def_call + . "$anchor$anchor_span_close
    \n"; +} + +sub _get_copiable_anchor { + my ($self, $id) = @_; + my $result = ''; + if ($id and $self->get_conf('COPIABLE_LINKS')) { + my $paragraph_symbol = $self->get_info('paragraph_symbol'); + $result = $self->html_attribute_class('a', ['copiable-link']) + ." href=\"#$id\"> $paragraph_symbol"; + } + return $result; +} + +$default_types_conversion{'def_line'} = \&_convert_def_line_type; + +sub _convert_def_item_type($$$$) +{ + my $self = shift; + my $type = shift; + my $element = shift; + my $content = shift; + + return $content if ($self->in_string()); + if ($content =~ /\S/) { + if (! $self->get_conf('DEF_TABLE')) { + return '
    ' . $content . '
    '; + } else { + return '' . $content . ''; + } + } +} + +$default_types_conversion{'def_item'} = \&_convert_def_item_type; +$default_types_conversion{'inter_def_item'} = \&_convert_def_item_type; + +sub _convert_def_command($$$$$) { + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $args = shift; + my $content = shift; + + return $content if ($self->in_string()); + + my @classes; + my $command_name; + if ($Texinfo::Common::def_aliases{$cmdname}) { + $command_name = $Texinfo::Common::def_aliases{$cmdname}; + push @classes, "first-$cmdname-alias-first-$command_name"; + } else { + $command_name = $cmdname; + } + unshift @classes, "first-$command_name"; + + if (!$self->get_conf('DEF_TABLE')) { + return $self->html_attribute_class('dl', \@classes).">\n" + . $content ."\n"; + } else { + return $self->html_attribute_class('table', \@classes)." width=\"100%\">\n" + . $content . "\n"; + } +} + +foreach my $command (keys(%def_commands), 'defblock') { + $default_commands_conversion{$command} = \&_convert_def_command; +} + +sub _convert_table_definition_type($$$$) +{ + my $self = shift; + my $type = shift; + my $element = shift; + my $content = shift; + + return $content if ($self->in_string()); + if ($content =~ /\S/) { + return '
    ' . $content . '
    '."\n"; + } +} + +$default_types_conversion{'table_definition'} + = \&_convert_table_definition_type; +$default_types_conversion{'inter_item'} + = \&_convert_table_definition_type; + +sub _contents_shortcontents_in_title($) +{ + my $self = shift; + + my $result = ''; + + my $structuring = $self->get_info('structuring'); + if ($structuring and $structuring->{'sectioning_root'} + and scalar(@{$structuring->{'sections_list'}}) > 1 + and $self->get_conf('CONTENTS_OUTPUT_LOCATION') eq 'after_title') { + foreach my $cmdname ('shortcontents', 'contents') { + if ($self->get_conf($cmdname)) { + my $contents_text = $self->_contents_inline_element($cmdname, undef); + if ($contents_text ne '') { + $result .= $contents_text . $self->get_conf('DEFAULT_RULE')."\n"; + } + } + } + } + return $result; +} + +# Convert @titlepage. Falls back to simpletitle. +sub _default_format_titlepage($) +{ + my $self = shift; + + my $titlepage_text; + my $global_commands = $self->get_info('global_commands'); + if ($global_commands->{'titlepage'}) { + $titlepage_text = $self->convert_tree({'contents' + => $global_commands->{'titlepage'}->{'contents'}}, + 'convert titlepage'); + } else { + my $simpletitle_tree = $self->get_info('simpletitle_tree'); + if ($simpletitle_tree) { + my $simpletitle_command_name = $self->get_info('simpletitle_command_name'); + my $title_text = $self->convert_tree_new_formatting_context( + $simpletitle_tree, "$simpletitle_command_name simpletitle"); + $titlepage_text = &{$self->formatting_function('format_heading_text')}($self, + $simpletitle_command_name, + [$simpletitle_command_name], $title_text, 0); + } + } + my $result = ''; + $result .= $titlepage_text.$self->get_conf('DEFAULT_RULE')."\n" + if (defined($titlepage_text)); + $result .= $self->_contents_shortcontents_in_title(); + return $result; +} + +sub _default_format_title_titlepage($) +{ + my $self = shift; + + my $result = ''; + if ($self->get_conf('SHOW_TITLE')) { + if ($self->get_conf('USE_TITLEPAGE_FOR_TITLE')) { + $result .= &{$self->formatting_function('format_titlepage')}($self); + } else { + my $simpletitle_tree = $self->get_info('simpletitle_tree'); + if ($simpletitle_tree) { + my $simpletitle_command_name = $self->get_info('simpletitle_command_name'); + my $title_text = $self->convert_tree_new_formatting_context( + $simpletitle_tree, "$simpletitle_command_name simpletitle"); + $result .= &{$self->formatting_function('format_heading_text')}($self, + $simpletitle_command_name, + [$simpletitle_command_name], $title_text, 0); + } + $result .= $self->_contents_shortcontents_in_title(); + } + } + return $result; +} + +# Function for converting special elements +sub _convert_special_element_type($$$$) +{ + my $self = shift; + my $type = shift; + my $element = shift; + my $content = shift; + + if ($self->in_string()) { + return ''; + } + + my $result = ''; + + my $special_element_variety = $element->{'extra'}->{'special_element_variety'}; + $result .= join('', $self->close_registered_sections_level(0)); + + my $special_element_body + .= &{$self->special_element_body_formatting($special_element_variety)}($self, + $special_element_variety, $element); + + # This may happen with footnotes in regions that are not expanded, + # like @copying or @titlepage + if ($special_element_body eq '') { + return ''; + } + + my $id = $self->command_id($element); + my $class_base + = $self->special_element_info('class', $special_element_variety); + $result .= $self->html_attribute_class('div', ["element-${class_base}"]); + if ($id ne '') { + $result .= " id=\"$id\""; + } + $result .= ">\n"; + if ($self->get_conf('HEADERS') + # first in page + or $self->count_elements_in_filename('current', + $element->{'structure'}->{'unit_filename'}) == 1) { + $result .= &{$self->formatting_function('format_navigation_header')}($self, + $self->get_conf('MISC_BUTTONS'), undef, $element); + } + my $heading = $self->command_text($element); + my $level = $self->get_conf('CHAPTER_HEADER_LEVEL'); + if ($special_element_variety eq 'footnotes') { + $level = $self->get_conf('FOOTNOTE_SEPARATE_HEADER_LEVEL'); + } + $result .= &{$self->formatting_function('format_heading_text')}($self, + undef, [$class_base.'-heading'], $heading, $level)."\n"; + + + $result .= $special_element_body . ''; + $result .= &{$self->formatting_function('format_element_footer')}($self, $type, + $element, $content); + return $result; +} + +$default_types_conversion{'special_element'} = \&_convert_special_element_type; + +# Function for converting the top-level elements in the conversion corresponding to +# a section or a node. The node and associated section appear together in +# the tree unit top-level element. $ELEMENT was created in this module (in +# _prepare_conversion_tree_units), with type 'unit' (it's not a tree element created +# by the parser). $CONTENT is the contents of the node/section, already converted. +sub _convert_tree_unit_type($$$$) +{ + my $self = shift; + my $type = shift; + my $element = shift; + my $content = shift; + + if ($self->in_string()) { + if (defined($content)) { + return $content; + } else { + return ''; + } + } + my $result = ''; + my $tree_unit = $element; + if (not $tree_unit->{'structure'} + or not $tree_unit->{'structure'}->{'unit_prev'}) { + $result .= $self->get_info('title_titlepage'); + if (not $tree_unit->{'structure'} + or not $tree_unit->{'structure'}->{'unit_next'}) { + # only one unit, use simplified formatting + $result .= $content; + # if there is one unit it also means that there is no formatting + # of footnotes in a separate unit. And if footnotestyle is end + # the footnotes won't be done in format_element_footer either. + $result .= &{$self->formatting_function('format_footnotes_segment')}($self); + $result .= $self->get_conf('DEFAULT_RULE') ."\n" + if ($self->get_conf('PROGRAM_NAME_IN_FOOTER') + and defined($self->get_conf('DEFAULT_RULE'))); + # do it here, as it is won't be done at end of page in format_element_footer + $result .= join('', $self->close_registered_sections_level(0)); + return $result; + } + } + $result .= $content; + my $command; + if ($element->{'extra'} and $element->{'extra'}->{'unit_command'}) { + $command = $element->{'extra'}->{'unit_command'}; + } + $result .= &{$self->formatting_function('format_element_footer')}($self, $type, + $element, $content, $command); + + return $result; +} + +$default_types_conversion{'unit'} = \&_convert_tree_unit_type; + +# for tree unit elements and special elements +sub _default_format_element_footer($$$$;$) +{ + my $self = shift; + my $type = shift; + my $element = shift; + my $content = shift; + my $command = shift; + + my $result = ''; + my $is_top = $self->element_is_tree_unit_top($element); + my $next_is_top = ($element->{'structure'}->{'unit_next'} + and $self->element_is_tree_unit_top($element->{'structure'}->{'unit_next'})); + my $next_is_special = (defined($element->{'structure'}->{'unit_next'}) + and defined($element->{'structure'}->{'unit_next'}->{'type'}) + and $element->{'structure'}->{'unit_next'}->{'type'} eq 'special_element'); + + my $end_page = (!$element->{'structure'}->{'unit_next'} + or (defined($element->{'structure'}->{'unit_filename'}) + and $element->{'structure'}->{'unit_filename'} + ne $element->{'structure'}->{'unit_next'}->{'structure'}->{'unit_filename'} + and $self->count_elements_in_filename('remaining', + $element->{'structure'}->{'unit_filename'}) == 1)); + + my $is_special = (defined($element->{'type'}) + and $element->{'type'} eq 'special_element'); + + if (($end_page or $next_is_top or $next_is_special or $is_top) + and $self->get_conf('VERTICAL_HEAD_NAVIGATION') + and ($self->get_conf('SPLIT') ne 'node' + or $self->get_conf('HEADERS') or $is_special or $is_top)) { + $result .= " + +"."\n"; + } + + my $rule = ''; + my $buttons; + + if ($end_page) { + $result .= join('', $self->close_registered_sections_level(0)); + + # setup buttons for navigation footer + if (($is_top or $is_special) + and ($self->get_conf('SPLIT') or !$self->get_conf('MONOLITHIC')) + and (($self->get_conf('HEADERS') + or ($self->get_conf('SPLIT') + and $self->get_conf('SPLIT') ne 'node')))) { + if ($is_top) { + $buttons = $self->get_conf('TOP_BUTTONS'); + } else { + $buttons = $self->get_conf('MISC_BUTTONS'); + } + } elsif ($self->get_conf('SPLIT') eq 'section') { + $buttons = $self->get_conf('SECTION_FOOTER_BUTTONS'); + } elsif ($self->get_conf('SPLIT') eq 'chapter') { + $buttons = $self->get_conf('CHAPTER_FOOTER_BUTTONS'); + } elsif ($self->get_conf('SPLIT') eq 'node') { + if ($self->get_conf('HEADERS')) { + my $no_footer_word_count; + if ($self->get_conf('WORDS_IN_PAGE')) { + # FIXME it seems that NO-BREAK SPACE and NEXT LINE (NEL) may + # not be in \h and \v in some case, but not sure which case it is + my @cnt = split(/\P{Word}*[\h\v]+\P{Word}*/, $content); + if (scalar(@cnt) < $self->get_conf('WORDS_IN_PAGE')) { + $no_footer_word_count = 1; + } + } + $buttons = $self->get_conf('NODE_FOOTER_BUTTONS') + unless ($no_footer_word_count); + } + } + } + # FIXME the following condition is almost a duplication of the + # condition appearing in end_page except that the file counter + # needs not to be 1 + if ((!$element->{'structure'}->{'unit_next'} + or (defined($element->{'structure'}->{'unit_filename'}) + and $element->{'structure'}->{'unit_filename'} + ne $element->{'structure'}->{'unit_next'}->{'structure'}->{'unit_filename'})) + and $self->get_conf('footnotestyle') eq 'end') { + $result .= &{$self->formatting_function('format_footnotes_segment')}($self); + } + + if (!$buttons or $is_top or $is_special + or ($end_page and ($self->get_conf('SPLIT') eq 'chapter' + or $self->get_conf('SPLIT') eq 'section')) + or ($self->get_conf('SPLIT') eq 'node' and $self->get_conf('HEADERS'))) { + $rule = $self->get_conf('DEFAULT_RULE'); + } + + if (!$end_page and ($is_top or $next_is_top or ($next_is_special + and !$is_special))) { + $rule = $self->get_conf('BIG_RULE'); + } + + if ($buttons or !$end_page or $self->get_conf('PROGRAM_NAME_IN_FOOTER')) { + $result .= "$rule\n" if ($rule); + } + if ($buttons) { + my $cmdname; + $cmdname = $command->{'cmdname'} if ($command and $command->{'cmdname'}); + $result .= &{$self->formatting_function('format_navigation_panel')}($self, + $buttons, $cmdname, $command); + } + return $result; +} + +# if $document_global_context is set, it means that the formatting +# is not done within the document formatting flow, but the formatted +# output may still end up in the document. In particular for +# command_text() which caches its computations. +sub _new_document_context($;$$) +{ + my $self = shift; + my $context = shift; + my $document_global_context = shift; + + push @{$self->{'document_context'}}, + {'context' => $context, + 'formatting_context' => [{'context_name' => $context}], + 'composition_context' => [''], + 'formats' => [], + 'monospace' => [0], + 'document_global_context' => $document_global_context, + 'block_commands' => [], + }; + if (defined($document_global_context)) { + $self->{'document_global_context'}++; + } +} + +sub _pop_document_context($) +{ + my $self = shift; + + my $context = pop @{$self->{'document_context'}}; + if (defined($context->{'document_global_context'})) { + $self->{'document_global_context'}--; + } +} + +# can be set through Texinfo::Config::texinfo_register_file_id_setting_function +my %customizable_file_id_setting_references; +foreach my $customized_reference ('label_target_name', 'node_file_name', + 'sectioning_command_target_name', 'tree_unit_file_name', + 'special_element_target_file_name') { + $customizable_file_id_setting_references{$customized_reference} = 1; +} + +# Functions accessed with e.g. 'format_heading_text'. +# used in Texinfo::Config +%default_formatting_references = ( + 'format_begin_file' => \&_default_format_begin_file, + 'format_button' => \&_default_format_button, + 'format_button_icon_img' => \&_default_format_button_icon_img, + 'format_css_lines' => \&_default_format_css_lines, + 'format_comment' => \&_default_format_comment, + 'format_contents' => \&_default_format_contents, + 'format_element_header' => \&_default_format_element_header, + 'format_element_footer' => \&_default_format_element_footer, + 'format_end_file' => \&_default_format_end_file, + 'format_frame_files' => \&_default_format_frame_files, + 'format_footnotes_segment' => \&_default_format_footnotes_segment, + 'format_footnotes_sequence' => \&_default_format_footnotes_sequence, + 'format_heading_text' => \&_default_format_heading_text, + 'format_navigation_header' => \&_default_format_navigation_header, + 'format_navigation_panel' => \&_default_format_navigation_panel, + 'format_node_redirection_page' => \&_default_format_node_redirection_page, + 'format_program_string' => \&_default_format_program_string, + 'format_protect_text' => \&_default_format_protect_text, + 'format_separate_anchor' => \&_default_format_separate_anchor, + 'format_titlepage' => \&_default_format_titlepage, + 'format_title_titlepage' => \&_default_format_title_titlepage, + 'format_translate_string' => undef, +); + +# not up for customization +%default_css_string_formatting_references = ( + 'format_protect_text' => \&_default_css_string_format_protect_text, +); + +%defaults_format_special_body_contents = ( + 'contents' => \&_default_format_special_body_contents, + 'about' => \&_default_format_special_body_about, + 'footnotes' => \&_default_format_special_body_footnotes, + 'shortcontents' => \&_default_format_special_body_shortcontents, +); + +sub _reset_unset_no_arg_commands_formatting_context($$$$;$) +{ + my $self = shift; + my $cmdname = shift; + my $reset_context = shift; + my $ref_context = shift; + my $translate = shift; + + # should never happen as unset is set at configuration + if (!defined ($self->{'no_arg_commands_formatting'}->{$reset_context}->{$cmdname})) { + $self->{'no_arg_commands_formatting'}->{$reset_context}->{$cmdname}->{'unset'} = 1; + } + my $no_arg_command_context + = $self->{'no_arg_commands_formatting'}->{$reset_context}->{$cmdname}; + if (defined($ref_context)) { + if ($no_arg_command_context->{'unset'}) { + foreach my $key (keys(%{$self->{'no_arg_commands_formatting'}->{$ref_context}->{$cmdname}})) { + # both 'translated_converted' and (possibly translated) 'text' are + # reused + $no_arg_command_context->{$key} + = $self->{'no_arg_commands_formatting'}->{$ref_context}->{$cmdname}->{$key} + } + } + } + if ($translate + and $no_arg_command_context->{'tree'} + and not defined($no_arg_command_context->{'translated_converted'})) { + my $translated_tree + = $no_arg_command_context->{'tree'}; + my $translation_result; + if ($reset_context eq 'normal') { + $translation_result + = $self->convert_tree($translated_tree, "no arg $cmdname translated"); + } elsif ($reset_context eq 'preformatted') { + # there does not seems to be anything simpler... + my $preformatted_command_name = 'example'; + $self->_new_document_context(); + push @{$self->{'document_context'}->[-1]->{'composition_context'}}, + $preformatted_command_name; + # should not be needed for at commands no brace translation strings + push @{$self->{'document_context'}->[-1]->{'preformatted_classes'}}, + $pre_class_commands{$preformatted_command_name}; + $translation_result + = $self->convert_tree($translated_tree, "no arg $cmdname translated"); + # only pop the main context + $self->_pop_document_context(); + } elsif ($reset_context eq 'string') { + $translation_result = $self->convert_tree_new_formatting_context({'type' => '_string', + 'contents' => [$translated_tree]}, + 'translated_string', "string no arg $cmdname translated"); + } elsif ($reset_context eq 'css_string') { + $translation_result = $self->html_convert_css_string($translated_tree); + } + $no_arg_command_context->{'text'} + = $translation_result; + } +} +sub _complete_no_arg_commands_formatting($$;$) +{ + my $self = shift; + my $cmdname = shift; + my $translate = shift; + + _reset_unset_no_arg_commands_formatting_context($self, $cmdname, + 'normal', undef, $translate); + _reset_unset_no_arg_commands_formatting_context($self, $cmdname, + 'preformatted', 'normal', $translate); + _reset_unset_no_arg_commands_formatting_context($self, $cmdname, + 'string', 'preformatted', $translate); + _reset_unset_no_arg_commands_formatting_context($self, $cmdname, + 'css_string', 'string', $translate); +} + +sub _set_non_breaking_space($$) +{ + my $self = shift; + my $non_breaking_space = shift; + $self->{'non_breaking_space'} = $non_breaking_space; +} + +# transform
    to
    +sub _xhtml_re_close_lone_element($) +{ + my $element = shift; + $element =~ s/^(<[a-zA-Z]+[^>]*)>$/$1\/>/; + return $element; +} + +my %htmlxref_entries = ( + 'node' => [ 'node', 'section', 'chapter', 'mono' ], + 'section' => [ 'section', 'chapter','node', 'mono' ], + 'chapter' => [ 'chapter', 'section', 'node', 'mono' ], + 'mono' => [ 'mono', 'chapter', 'section', 'node' ], +); + +# $FILES is an array reference of file names binary strings. +sub _parse_htmlxref_files($$) +{ + my $self = shift; + my $files = shift; + my $htmlxref = {}; + + foreach my $file (@$files) { + my $fname = $file; + if ($self->get_conf('TEST')) { + my ($volume, $directories); + # strip directories for out-of-source builds reproducible file names + ($volume, $directories, $fname) = File::Spec->splitpath($file); + } + print STDERR "html refs config file: $file\n" if ($self->get_conf('DEBUG')); + unless (open(HTMLXREF, $file)) { + my $htmlxref_file_name = $file; + my $encoding = $self->get_conf('COMMAND_LINE_ENCODING'); + if (defined($encoding)) { + $htmlxref_file_name = decode($encoding, $htmlxref_file_name); + } + $self->document_warn($self, + sprintf(__("could not open html refs config file %s: %s"), + $htmlxref_file_name, $!)); + next; + } + my $line_nr = 0; + my %variables; + while (my $hline = ) { + my $line = $hline; + $line_nr++; + next if $hline =~ /^\s*#/; + #$hline =~ s/[#]\s.*//; + $hline =~ s/^\s*//; + next if $hline =~ /^\s*$/; + chomp ($hline); + if ($hline =~ s/^\s*(\w+)\s*=\s*//) { + # handle variables + my $var = $1; + my $re = join '|', map { quotemeta $_ } keys %variables; + $hline =~ s/\$\{($re)\}/defined $variables{$1} ? $variables{$1} + : "\${$1}"/ge; + $variables{$var} = $hline; + next; + } + my @htmlxref = split /\s+/, $hline; + my $manual = shift @htmlxref; + my $split_or_mono = shift @htmlxref; + #print STDERR "$split_or_mono $Texi2HTML::Config::htmlxref_entries{$split_or_mono} $line_nr\n"; + if (!defined($split_or_mono)) { + $self->line_warn($self, __("missing type"), + {'file_name' => $fname, 'line_nr' => $line_nr}); + next; + } elsif (!defined($htmlxref_entries{$split_or_mono})) { + $self->line_warn($self, sprintf(__("unrecognized type: %s"), + $split_or_mono), + {'file_name' => $fname, 'line_nr' => $line_nr}); + next; + } + my $href = shift @htmlxref; + next if ($htmlxref->{$manual} + and exists($htmlxref->{$manual}->{$split_or_mono})); + + if (defined($href)) { # substitute 'variables' + my $re = join '|', map { quotemeta $_ } keys %variables; + $href =~ s/\$\{($re)\}/defined $variables{$1} ? $variables{$1} + : "\${$1}"/ge; + $href =~ s/\/*$// if ($split_or_mono ne 'mono'); + } + $htmlxref->{$manual} = {} if (!$htmlxref->{$manual}); + $htmlxref->{$manual}->{$split_or_mono} = $href; + } + if (!close (HTMLXREF)) { + $self->document_warn($self, sprintf(__( + "error on closing html refs config file %s: %s"), + $file, $!)); + } + } + return $htmlxref; +} + +sub _load_htmlxref_files { + my ($self) = @_; + + my @htmlxref_files; + my $htmlxref_mode = $self->get_conf('HTMLXREF_MODE'); + return if (defined($htmlxref_mode) and $htmlxref_mode eq 'none'); + my $htmlxref_file_name = 'htmlxref.cnf'; + if (defined($htmlxref_mode) and $htmlxref_mode eq 'file') { + if (defined($self->get_conf('HTMLXREF_FILE'))) { + $htmlxref_file_name = $self->get_conf('HTMLXREF_FILE'); + } + my ($encoded_htmlxref_file_name, $htmlxref_file_encoding) + = $self->encoded_output_file_name($htmlxref_file_name); + if (-e $encoded_htmlxref_file_name and -r $encoded_htmlxref_file_name) { + @htmlxref_files = ($encoded_htmlxref_file_name); + } else { + $self->document_warn($self, + sprintf(__("could not find html refs config file %s"), + $htmlxref_file_name)); + } + } else { + my @htmlxref_dirs = (); + if ($self->get_conf('TEST')) { + my $curdir = File::Spec->curdir(); + # to have reproducible tests, do not use system or user + # directories if TEST is set. + @htmlxref_dirs = File::Spec->catdir($curdir, '.texinfo'); + + if (defined($self->{'parser_info'}) + and defined($self->{'parser_info'}->{'input_directory'})) { + my $input_directory = $self->{'parser_info'}->{'input_directory'}; + if ($input_directory ne '.' and $input_directory ne '') { + unshift @htmlxref_dirs, $input_directory; + } + } + } elsif ($self->{'language_config_dirs'} + and @{$self->{'language_config_dirs'}}) { + @htmlxref_dirs = @{$self->{'language_config_dirs'}}; + } + unshift @htmlxref_dirs, '.'; + + # no htmlxref for tests, unless explicitly specified + if ($self->get_conf('TEST')) { + if (defined($self->get_conf('HTMLXREF_FILE'))) { + $htmlxref_file_name = $self->get_conf('HTMLXREF_FILE'); + } else { + $htmlxref_file_name = undef; + } + } elsif (defined($self->get_conf('HTMLXREF_FILE'))) { + $htmlxref_file_name = $self->get_conf('HTMLXREF_FILE'); + } + + if (defined($htmlxref_file_name)) { + my ($encoded_htmlxref_file_name, $htmlxref_file_encoding) + = $self->encoded_output_file_name($htmlxref_file_name); + @htmlxref_files + = Texinfo::Common::locate_init_file($encoded_htmlxref_file_name, + \@htmlxref_dirs, 1); + } + } + + $self->{'htmlxref'} = {}; + if (scalar(@htmlxref_files)) { + $self->{'htmlxref'} = _parse_htmlxref_files($self, + \@htmlxref_files); + } +} + +# converter state +# +# output_init_conf +# +# API exists +# shared_conversion_state +# Set through the shared_conversion_state API (among others): +# explained_commands # used only in an @-command conversion function +# element_explanation_contents # same as above +# +# API exists +# current_filename +# document_name +# destination_directory +# paragraph_symbol +# line_break_element +# non_breaking_space +# simpletitle_tree +# simpletitle_command_name +# title_string +# title_tree +# documentdescription_string +# copying_comment +# index_entries +# index_entries_by_letter +# jslicenses +# +# API exists +# css_element_class_styles +# css_import_lines +# css_rule_lines +# +# API exists +# file_id_setting +# commands_conversion +# commands_open +# types_conversion +# types_open +# no_arg_commands_formatting +# style_commands_formatting +# code_types +# pre_class_types +# +# API exists +# document_context +# +# API exists +# pending_closes +# +# API exists +# pending_footnotes +# +# API exists +# pending_inline_content +# associated_inline_content +# +# API exists +# targets for directions. Keys are elements references, values are +# target information hash references described above before +# the API functions used to access this information. +# special_targets +# special_elements_targets +# special_elements_directions +# global_target_elements_directions +# +# API exists +# directions_strings +# translated_direction_strings +# +# API exists +# special_element_info +# translated_special_element_info +# +# API exists +# elements_in_file_count # the number of tree unit elements in file +# file_counters # begin at elements_in_file_count decrease +# # each time the tree unit element is closed +# +# API exists +# document_global_context_css +# file_css +# +# API exists +# files_information +# +# No API, converter internals +# tree_units +# out_filepaths (partially common with Texinfo::Converter) +# current_root_element +# seen_ids +# ignore_notice +# options_latex_math +# htmlxref +# check_htmlxref_already_warned +# referred_command_stack +# +# from Converter +# labels + +my %special_characters = ( + 'paragraph_symbol' => ['¶', '00B6'], + 'left_quote' => ['‘', '2018'], + 'right_quote' => ['’', '2019'], + 'bullet' => ['•', '2022'], + 'non_breaking_space' => [undef, '00A0'], +); + +sub converter_initialize($) +{ + my $self = shift; + + %{$self->{'css_element_class_styles'}} = %css_element_class_styles; + + _load_htmlxref_files($self); + + # duplicate such as not to modify the defaults + my $conf_default_no_arg_commands_formatting_normal + = Storable::dclone($default_no_arg_commands_formatting{'normal'}); + + my %special_characters_set; + + my $output_encoding = $self->get_conf('OUTPUT_ENCODING_NAME'); + + foreach my $special_character (keys(%special_characters)) { + my ($default_entity, $unicode_point) = @{$special_characters{$special_character}}; + if ($self->get_conf('OUTPUT_CHARACTERS') + and Texinfo::Convert::Unicode::unicode_point_decoded_in_encoding( + $output_encoding, $unicode_point)) { + $special_characters_set{$special_character} = chr(hex($unicode_point)); + } elsif ($self->get_conf('USE_NUMERIC_ENTITY')) { + $special_characters_set{$special_character} = '&#'.hex($unicode_point).';'; + } else { + $special_characters_set{$special_character} = $default_entity; + } + } + + if (defined($special_characters_set{'non_breaking_space'})) { + my $non_breaking_space = $special_characters_set{'non_breaking_space'}; + $self->_set_non_breaking_space($non_breaking_space); + foreach my $space_command (' ', "\t", "\n") { + $conf_default_no_arg_commands_formatting_normal->{$space_command}->{'text'} + = $self->{'non_breaking_space'}; + } + $conf_default_no_arg_commands_formatting_normal->{'tie'}->{'text'} + = $self->substitute_html_non_breaking_space( + $default_no_arg_commands_formatting{'normal'}->{'tie'}->{'text'}); + } else { + $self->_set_non_breaking_space($xml_named_entity_nbsp); + } + $self->{'paragraph_symbol'} = $special_characters_set{'paragraph_symbol'}; + + if (not defined($self->get_conf('OPEN_QUOTE_SYMBOL'))) { + $self->set_conf('OPEN_QUOTE_SYMBOL', $special_characters_set{'left_quote'}); + } + if (not defined($self->get_conf('CLOSE_QUOTE_SYMBOL'))) { + $self->set_conf('CLOSE_QUOTE_SYMBOL', $special_characters_set{'right_quote'}); + } + if (not defined($self->get_conf('MENU_SYMBOL'))) { + $self->set_conf('MENU_SYMBOL', $special_characters_set{'bullet'}); + } + + if ($self->get_conf('USE_NUMERIC_ENTITY')) { + foreach my $command (keys(%Texinfo::Convert::Unicode::unicode_entities)) { + $conf_default_no_arg_commands_formatting_normal->{$command}->{'text'} + = $Texinfo::Convert::Unicode::unicode_entities{$command}; + } + } + + if ($self->get_conf('USE_XML_SYNTAX')) { + foreach my $customization_variable ('BIG_RULE', 'DEFAULT_RULE') { + my $variable_value = $self->get_conf($customization_variable); + if (defined($variable_value)) { + my $closed_lone_element = _xhtml_re_close_lone_element($variable_value); + if ($closed_lone_element ne $variable_value) { + $self->force_conf($customization_variable, $closed_lone_element); + } + } + } + $self->{'line_break_element'} = '
    '; + } else { + $self->{'line_break_element'} = '
    '; + } + $conf_default_no_arg_commands_formatting_normal->{'*'}->{'text'} + = $self->{'line_break_element'}; + + # three types of direction strings: + # * strings not translated, already converted + # * strings translated + # - strings already converted + # - strings not already converted + $self->{'directions_strings'} = {}; + + my $customized_direction_strings + = Texinfo::Config::GNUT_get_direction_string_info(); + foreach my $string_type (keys(%default_converted_directions_strings)) { + $self->{'directions_strings'}->{$string_type} = {}; + foreach my $direction + (keys(%{$default_converted_directions_strings{$string_type}})) { + $self->{'directions_strings'}->{$string_type}->{$direction} = {}; + my $string_contexts; + if ($customized_direction_strings->{$string_type} + and $customized_direction_strings->{$string_type}->{$direction}) { + if (defined($customized_direction_strings->{$string_type} + ->{$direction}->{'converted'})) { + $string_contexts + = $customized_direction_strings->{$string_type} + ->{$direction}->{'converted'}; + } + } else { + my $string + = $default_converted_directions_strings{$string_type}->{$direction}; + $string_contexts + = {'normal' => $string}; + } + $string_contexts->{'string'} = $string_contexts->{'normal'} + if (not defined($string_contexts->{'string'})); + foreach my $context (keys(%$string_contexts)) { + $self->{'directions_strings'}->{$string_type}->{$direction}->{$context} + = $self->substitute_html_non_breaking_space( + $string_contexts->{$context}); + } + } + } + $self->{'translated_direction_strings'} = {}; + foreach my $string_type (keys(%default_translated_directions_strings)) { + $self->{'translated_direction_strings'}->{$string_type} = {}; + foreach my $direction + (keys(%{$default_translated_directions_strings{$string_type}})) { + if ($customized_direction_strings->{$string_type} + and $customized_direction_strings->{$string_type}->{$direction}) { + $self->{'translated_direction_strings'}->{$string_type}->{$direction} + = $customized_direction_strings->{$string_type}->{$direction}; + } else { + if ($default_translated_directions_strings{$string_type}->{$direction} + ->{'converted'}) { + $self->{'translated_direction_strings'}->{$string_type} + ->{$direction} = {'converted' => {}}; + foreach my $context ('normal', 'string') { + $self->{'translated_direction_strings'}->{$string_type} + ->{$direction}->{'converted'}->{$context} + = $default_translated_directions_strings{$string_type} + ->{$direction}->{'converted'}; + } + } else { + $self->{'translated_direction_strings'}->{$string_type}->{$direction} + = $default_translated_directions_strings{$string_type}->{$direction}; + } + } + } + } + + $self->{'types_conversion'} = {}; + my $customized_types_conversion = Texinfo::Config::GNUT_get_types_conversion(); + foreach my $type (keys(%default_types_conversion)) { + if (exists($customized_types_conversion->{$type})) { + $self->{'types_conversion'}->{$type} + = $customized_types_conversion->{$type}; + } else { + $self->{'types_conversion'}->{$type} + = $default_types_conversion{$type}; + } + } + + $self->{'types_open'} = {}; + my $customized_types_open + = Texinfo::Config::GNUT_get_types_open(); + foreach my $type (keys(%default_types_conversion)) { + if (exists($customized_types_open->{$type})) { + $self->{'types_open'}->{$type} + = $customized_types_open->{$type}; + } elsif (exists($default_types_open{$type})) { + $self->{'types_open'}->{$type} + = $default_types_open{$type}; + } + } + + $self->{'code_types'} = {}; + foreach my $type (keys(%default_code_types)) { + $self->{'code_types'}->{$type} = $default_code_types{$type}; + } + $self->{'pre_class_types'} = {}; + foreach my $type (keys(%default_pre_class_types)) { + $self->{'pre_class_types'}->{$type} = $default_pre_class_types{$type}; + } + my $customized_type_formatting + = Texinfo::Config::GNUT_get_types_formatting_info(); + foreach my $type (keys(%$customized_type_formatting)) { + # Used in cvs.init. + $self->{'code_types'}->{$type} + = $customized_type_formatting->{$type}->{'code'}; + $self->{'pre_class_types'}->{$type} + = $customized_type_formatting->{$type}->{'pre_class'}; + } + + $self->{'commands_conversion'} = {}; + # FIXME put value in a category in Texinfo::Common? + my $customized_commands_conversion + = Texinfo::Config::GNUT_get_commands_conversion(); + foreach my $command (keys(%line_commands), keys(%brace_commands), + keys (%block_commands), keys(%nobrace_commands)) { + if (exists($customized_commands_conversion->{$command})) { + $self->{'commands_conversion'}->{$command} + = $customized_commands_conversion->{$command}; + } else { + if ($self->get_conf('FORMAT_MENU') ne 'menu' + and ($command eq 'menu' or $command eq 'detailmenu')) { + $self->{'commands_conversion'}->{$command} = undef; + } elsif ($format_raw_commands{$command} + and !$self->{'expanded_formats_hash'}->{$command}) { + } elsif (exists($default_commands_conversion{$command})) { + $self->{'commands_conversion'}->{$command} + = $default_commands_conversion{$command}; + } + } + } + + $self->{'commands_open'} = {}; + my $customized_commands_open + = Texinfo::Config::GNUT_get_commands_open(); + foreach my $command (keys(%line_commands), keys(%brace_commands), + keys (%block_commands), keys(%nobrace_commands)) { + if (exists($customized_commands_open->{$command})) { + $self->{'commands_open'}->{$command} + = $customized_commands_open->{$command}; + } elsif (exists($default_commands_open{$command})) { + $self->{'commands_open'}->{$command} + = $default_commands_open{$command}; + } + } + + $self->{'no_arg_commands_formatting'} = {}; + foreach my $context ('normal', 'preformatted', 'string', 'css_string') { + $self->{'no_arg_commands_formatting'}->{$context} = {}; + foreach my $command (keys(%{$default_no_arg_commands_formatting{'normal'}})) { + my $no_arg_command_customized_formatting + = Texinfo::Config::GNUT_get_no_arg_command_formatting($command, $context); + if (defined($no_arg_command_customized_formatting)) { + $self->{'no_arg_commands_formatting'}->{$context}->{$command} + = $no_arg_command_customized_formatting; + } else { + my $context_default_default_no_arg_commands_formatting + = $default_no_arg_commands_formatting{$context}; + if ($context eq 'normal') { + $context_default_default_no_arg_commands_formatting + = $conf_default_no_arg_commands_formatting_normal; + } + if (defined($context_default_default_no_arg_commands_formatting->{$command})) { + if ($self->get_conf('OUTPUT_CHARACTERS') + and Texinfo::Convert::Unicode::brace_no_arg_command( + $command, $self->get_conf('OUTPUT_ENCODING_NAME'))) { + $self->{'no_arg_commands_formatting'}->{$context}->{$command} + = { 'text' => Texinfo::Convert::Unicode::brace_no_arg_command( + $command, $self->get_conf('OUTPUT_ENCODING_NAME'))}; + # reset CSS for itemize command arguments + if ($context eq 'css_string' + and exists($brace_commands{$command}) + and $command ne 'bullet' and $command ne 'w' + and not $special_list_mark_css_string_no_arg_command{$command}) { + my $css_string + = $self->{'no_arg_commands_formatting'} + ->{$context}->{$command}->{'text'}; + $css_string = '"'.$css_string.'"'; + $self->{'css_element_class_styles'}->{"ul.mark-$command"} + = "list-style-type: $css_string"; + } + } else { + $self->{'no_arg_commands_formatting'}->{$context}->{$command} + = $context_default_default_no_arg_commands_formatting->{$command}; + } + } else { + $self->{'no_arg_commands_formatting'}->{$context}->{$command} + = {'unset' => 1}; + } + } + } + } + + # set sane defaults in case there is none and the default formatting + # function is used + foreach my $command (keys(%{$default_no_arg_commands_formatting{'normal'}})) { + if ($self->{'commands_conversion'}->{$command} + and $self->{'commands_conversion'}->{$command} + eq $default_commands_conversion{$command}) { + $self->_complete_no_arg_commands_formatting($command); + } + } + + $self->{'style_commands_formatting'} = {}; + foreach my $context (keys(%style_commands_formatting)) { + $self->{'style_commands_formatting'}->{$context} = {}; + foreach my $command (keys(%{$style_commands_formatting{$context}})) { + my $style_commands_formatting_info + = Texinfo::Config::GNUT_get_style_command_formatting($command, $context); + if (defined($style_commands_formatting_info)) { + $self->{'style_commands_formatting'}->{$context}->{$command} + = $style_commands_formatting_info; + } elsif (exists($style_commands_formatting{$context}->{$command})) { + $self->{'style_commands_formatting'}->{$context}->{$command} + = $style_commands_formatting{$context}->{$command}; + } + } + } + + $self->{'accent_entities'} = {}; + foreach my $accent_command + (keys(%Texinfo::Convert::Converter::xml_accent_entities)) { + $self->{'accent_entities'}->{$accent_command} = []; + my ($accent_command_entity, $accent_command_text_with_entities) + = Texinfo::Config::GNUT_get_accent_command_formatting($accent_command); + if (not defined($accent_command_entity) + and defined($Texinfo::Convert::Converter::xml_accent_text_with_entities{ + $accent_command})) { + $accent_command_entity + = $Texinfo::Convert::Converter::xml_accent_entities{$accent_command}; + } + if (not defined($accent_command_text_with_entities) + and defined($Texinfo::Convert::Converter::xml_accent_text_with_entities{ + $accent_command})) { + $accent_command_text_with_entities + = $Texinfo::Convert::Converter::xml_accent_text_with_entities{$accent_command}; + } + # an empty string means no formatting + if (defined($accent_command_entity)) { + $self->{'accent_entities'}->{$accent_command} = [$accent_command_entity, + $accent_command_text_with_entities]; + } + } + #print STDERR Data::Dumper->Dump([$self->{'accent_entities'}]); + + $self->{'file_id_setting'} = {}; + my $customized_file_id_setting_references + = Texinfo::Config::GNUT_get_file_id_setting_references(); + # first check the validity of the names + foreach my $customized_file_id_setting_ref + (sort(keys(%{$customized_file_id_setting_references}))) { + if (!$customizable_file_id_setting_references{$customized_file_id_setting_ref}) { + $self->document_warn($self, + sprintf(__("Unknown file and id setting function: %s"), + $customized_file_id_setting_ref)); + } else { + $self->{'file_id_setting'}->{$customized_file_id_setting_ref} + = $customized_file_id_setting_references->{$customized_file_id_setting_ref}; + } + } + + my $customized_formatting_references + = Texinfo::Config::GNUT_get_formatting_references(); + # first check that all the customized_formatting_references + # are in default_formatting_references + foreach my $customized_formatting_reference + (sort(keys(%{$customized_formatting_references}))) { + if (!exists($default_formatting_references{$customized_formatting_reference})) { + $self->document_warn($self, sprintf(__("Unknown formatting function: %s"), + $customized_formatting_reference)); + } + } + + $self->{'formatting_function'} = {}; + foreach my $formatting_reference (keys(%default_formatting_references)) { + if (defined($customized_formatting_references->{$formatting_reference})) { + $self->{'formatting_function'}->{$formatting_reference} + = $customized_formatting_references->{$formatting_reference}; + } else { + $self->{'formatting_function'}->{$formatting_reference} + = $default_formatting_references{$formatting_reference}; + } + } + + my $customized_special_element_info + = Texinfo::Config::GNUT_get_special_element_info(); + + $self->{'special_element_info'} = {}; + foreach my $type (keys(%default_special_element_info)) { + $self->{'special_element_info'}->{$type} = {}; + foreach my $special_element_variety + (keys(%{$default_special_element_info{$type}})) { + if (exists($customized_special_element_info->{$type}) + and exists($customized_special_element_info + ->{$type}->{$special_element_variety})) { + $self->{'special_element_info'}->{$type}->{$special_element_variety} + = $customized_special_element_info->{$type}->{$special_element_variety}; + } else { + $self->{'special_element_info'}->{$type}->{$special_element_variety} + = $default_special_element_info{$type}->{$special_element_variety}; + } + } + } + + $self->{'translated_special_element_info'} = {}; + foreach my $type (keys(%default_translated_special_element_info)) { + $self->{'special_element_info'}->{$type} = {}; + $self->{'special_element_info'}->{$type.'_tree'} = {}; + $self->{'translated_special_element_info'}->{$type.'_tree'} = [$type, {}]; + foreach my $special_element_variety + (keys(%{$default_translated_special_element_info{$type}})) { + if (exists($customized_special_element_info->{$type}) + and exists($customized_special_element_info + ->{$type}->{$special_element_variety})) { + $self->{'translated_special_element_info'}->{$type.'_tree'} + ->[1]->{$special_element_variety} + = $customized_special_element_info->{$type}->{$special_element_variety}; + } else { + $self->{'translated_special_element_info'}->{$type.'_tree'} + ->[1]->{$special_element_variety} + = $default_translated_special_element_info{$type} + ->{$special_element_variety}; + } + } + } + + my $customized_special_element_body + = Texinfo::Config::GNUT_get_formatting_special_element_body_references(); + + $self->{'special_element_body'} = {}; + foreach my $special_element_variety (keys(%defaults_format_special_body_contents)) { + $self->{'special_element_body'}->{$special_element_variety} + = $defaults_format_special_body_contents{$special_element_variety}; + } + foreach my $special_element_variety (keys(%$customized_special_element_body)) { + $self->{'special_element_body'}->{$special_element_variety} + = $customized_special_element_body->{$special_element_variety}; + } + + $self->{'document_context'} = []; + $self->{'multiple_pass'} = []; + $self->{'pending_closes'} = []; + $self->_new_document_context('_toplevel_context'); + + if ($self->get_conf('SPLIT') and $self->get_conf('SPLIT') ne 'chapter' + and $self->get_conf('SPLIT') ne 'section' + and $self->get_conf('SPLIT') ne 'node') { + $self->force_conf('SPLIT', 'node'); + } + + return $self; +} + +# the entry point for _convert +sub convert_tree($$;$) +{ + my $self = shift; + my $tree = shift; + my $explanation = shift; + + # when formatting accents, goes through xml_accent without + # explanation, as explanation is not in the standard API, but + # otherwise the coverage of explanations should be pretty good + #cluck if (! defined($explanation)); + #print STDERR "CONVERT_TREE".(defined($explanation) ? " ".$explanation : '')."\n" + # if ($self->get_conf('DEBUG')); + return $self->_convert($tree, $explanation); +} + +# FIXME document as part of the API. Make it a mandatory called function? +# a format_* function? +# protect an url, in which characters with specific meaning in url are considered +# to have their specific meaning +sub url_protect_url_text($$) +{ + my $self = shift; + my $input_string = shift; + # percent encode character string. It is better use UTF-8 irrespective + # of the actual charset of the HTML output file, according to the tests done. + my $href = encode("UTF-8", $input_string); + # protect 'ligntly', do not protect unreserved and reserved characters + the % itself + $href =~ s/([^^A-Za-z0-9\-_.!~*'()\$&+,\/:;=\?@\[\]\#%])/ sprintf "%%%02x", ord $1 /eg; + return &{$self->formatting_function('format_protect_text')}($self, $href); +} + +# FIXME document as part of the API. Make it a mandatory called function? +# a format_* function? +# protect a file path used in an url. Characters appearing in file paths +# are not protected. All the other characters that can be percent +# protected are protected, including characters with specific meaning in url. +sub url_protect_file_text($$) +{ + my $self = shift; + my $input_string = shift; + # percent encode character string. It is better use UTF-8 irrespective + # of the actual charset of the HTML output file, according to the tests done. + my $href = encode("UTF-8", $input_string); + # protect everything that can be special in url except ~, / and : that could + # appear in file names and does not have much risk in being incorrectly + # interpreted (for :, the interpretation as a scheme delimiter may be possible). + $href =~ s/([^^A-Za-z0-9\-_.~\/:])/ sprintf "%%%02x", ord $1 /eg; + return &{$self->formatting_function('format_protect_text')}($self, $href); +} + +sub _normalized_to_id($) +{ + my $id = shift; + if (!defined($id)) { + cluck "_normalized_to_id id not defined"; + return ''; + } + $id =~ s/^([0-9_])/g_t$1/; + return $id; +} + +sub _default_format_css_lines($;$) +{ + my $self = shift; + my $filename = shift; + + return '' if ($self->get_conf('NO_CSS')); + + my $css_refs = $self->get_conf('CSS_REFS'); + my @css_element_classes = $self->html_get_css_elements_classes($filename); + my @css_import_lines = $self->css_get_info('imports'); + my @css_rule_lines = $self->css_get_info('rules'); + + return '' if !@css_import_lines and !@css_element_classes + and !@css_rule_lines + and (!defined($css_refs) or !@$css_refs); + + my $css_text = "\n"; + foreach my $ref (@$css_refs) { + $css_text .= $self->close_html_lone_element( + ') { + $line_nr++; + if ($line_nr == 1) { + # the rule is to assume utf-8. There could also be a BOM, and + # the Content-Type: HTTP header but it is not relevant here. + # https://developer.mozilla.org/en-US/docs/Web/CSS/@charset + my $charset = 'utf-8'; + my $charset_line; + # should always be the first line + if ($line =~ /^\@charset *"([^"]+)" *; *$/) { + $charset = $1; + $charset_line = 1; + } + my $Encode_encoding_object = find_encoding($charset); + if (defined($Encode_encoding_object)) { + my $input_perl_encoding = $Encode_encoding_object->name(); + if ($input_perl_encoding eq 'utf-8') { + binmode($fh, ":utf8"); + } else { + binmode($fh, ":encoding($input_perl_encoding)"); + } + } + next if ($charset_line); + } + #print STDERR "Line: $line"; + if ($in_rules) { + push @$rules, $line; + next; + } + my $text = ''; + while (1) { + #sleep 1; + #print STDERR "${text}!in_comment $in_comment in_rules $in_rules in_import $in_import in_string $in_string: $line"; + if ($in_comment) { + if ($line =~ s/^(.*?\*\/)//) { + $text .= $1; + $in_comment = 0; + } else { + push @$imports, $text . $line; + last; + } + } elsif (!$in_string and $line =~ s/^\///) { + if ($line =~ s/^\*//) { + $text .= '/*'; + $in_comment = 1; + } else { + push (@$imports, $text. "\n") if ($text ne ''); + push (@$rules, '/' . $line); + $in_rules = 1; + last; + } + } elsif (!$in_string and $in_import and $line =~ s/^([\"\'])//) { + # strings outside of import start rules + $text .= "$1"; + $in_string = quotemeta("$1"); + } elsif ($in_string and $line =~ s/^(\\$in_string)//) { + $text .= $1; + } elsif ($in_string and $line =~ s/^($in_string)//) { + $text .= $1; + $in_string = 0; + } elsif ((! $in_string and !$in_import) + and ($line =~ s/^([\\]?\@import)$// + or $line =~ s/^([\\]?\@import\s+)//)) { + $text .= $1; + $in_import = 1; + } elsif (!$in_string and $in_import and $line =~ s/^\;//) { + $text .= ';'; + $in_import = 0; + } elsif (($in_import or $in_string) and $line =~ s/^(.)//) { + $text .= $1; + } elsif (!$in_import and $line =~ s/^([^\s])//) { + push (@$imports, $text. "\n") if ($text ne ''); + push (@$rules, $1 . $line); + $in_rules = 1; + last; + } elsif ($line =~ s/^(\s)//) { + $text .= $1; + } elsif ($line eq '') { + push (@$imports, $text); + last; + } + } + } + $self->line_warn($self, __("string not closed in css file"), + {'file_name' => $file, 'line_nr' => $line_nr}) if ($in_string); + $self->line_warn($self, __("--css-include ended in comment"), + {'file_name' => $file, 'line_nr' => $line_nr}) if ($in_comment); + $self->line_warn($self, __("\@import not finished in css file"), + {'file_name' => $file, 'line_nr' => $line_nr}) + if ($in_import and !$in_comment and !$in_string); + return ($imports, $rules); +} + +sub _prepare_css($) +{ + my $self = shift; + + return if ($self->get_conf('NO_CSS')); + + my @css_import_lines; + my @css_rule_lines; + + my $css_files = $self->get_conf('CSS_FILES'); + foreach my $file (@$css_files) { + my $css_file_fh; + my $css_file; + if ($file eq '-') { + $css_file_fh = \*STDIN; + $css_file = '-'; + } else { + $css_file = $self->Texinfo::Common::locate_include_file($file); + unless (defined($css_file)) { + my $input_file_name = $file; + my $encoding = $self->get_conf('COMMAND_LINE_ENCODING'); + if (defined($encoding)) { + $input_file_name = decode($encoding, $input_file_name); + } + $self->document_warn($self, sprintf( + __("CSS file %s not found"), $input_file_name)); + next; + } + unless (open (CSSFILE, $css_file)) { + my $css_file_name = $css_file; + my $encoding = $self->get_conf('COMMAND_LINE_ENCODING'); + if (defined($encoding)) { + $css_file_name = decode($encoding, $css_file_name); + } + $self->document_warn($self, sprintf(__( + "could not open --include-file %s: %s"), + $css_file_name, $!)); + next; + } + $css_file_fh = \*CSSFILE; + } + my ($import_lines, $rules_lines); + ($import_lines, $rules_lines) + = $self->_process_css_file($css_file_fh, $css_file); + if (!close($css_file_fh)) { + my $css_file_name = $css_file; + my $encoding = $self->get_conf('COMMAND_LINE_ENCODING'); + if (defined($encoding)) { + $css_file_name = decode($encoding, $css_file_name); + } + $self->document_warn($self, + sprintf(__("error on closing CSS file %s: %s"), + $css_file_name, $!)); + } + push @css_import_lines, @$import_lines; + push @css_rule_lines, @$rules_lines; + + } + if ($self->get_conf('DEBUG')) { + if (@css_import_lines) { + print STDERR "# css import lines\n"; + foreach my $line (@css_import_lines) { + print STDERR "$line"; + } + } + if (@css_rule_lines) { + print STDERR "# css rule lines\n"; + foreach my $line (@css_rule_lines) { + print STDERR "$line"; + } + } + } + $self->{'css_import_lines'} = \@css_import_lines; + $self->{'css_rule_lines'} = \@css_rule_lines; +} + +# Get the name of a file containing a label, as well as the identifier within +# that file to link to that label. $normalized is the normalized label name +# and $node_contents is the label contents. Labels are typically associated +# to @node, @anchor or @float and to external nodes. +sub _normalized_label_id_file($$$) +{ + my $self = shift; + my $normalized = shift; + my $node_contents = shift; + + my $target; + if (!defined($normalized)) { + $normalized = Texinfo::Convert::NodeNameNormalization::normalize_node( + { 'contents' => $node_contents }); + } + + if (defined($normalized)) { + $target = _normalized_to_id($normalized); + } else { + $target = ''; + } + # to find out the Top node, one could check $normalized + if (defined($self->{'file_id_setting'}->{'label_target_name'})) { + $target = &{$self->{'file_id_setting'}->{'label_target_name'}}($self, + $normalized, $node_contents, $target); + } + + my $filename = $self->node_information_filename($normalized, + $node_contents); + + return ($filename, $target); +} + +sub _new_sectioning_command_target($$) +{ + my $self = shift; + my $command = shift; + + my ($normalized_name, $filename) + = $self->normalized_sectioning_command_filename($command); + + my $target_base = _normalized_to_id($normalized_name); + if ($target_base !~ /\S/ and $command->{'cmdname'} eq 'top') { + # @top is allowed to be empty. In that case it gets this target name + $target_base = 'SEC_Top'; + } + my $nr=1; + my $target = $target_base; + if ($target ne '') { + while ($self->{'seen_ids'}->{$target}) { + $target = $target_base.'-'.$nr; + $nr++; + # Avoid integer overflow + die if ($nr == 0); + } + } + + # These are undefined if the $target is set to ''. + my $target_contents; + my $target_shortcontents; + if ($sectioning_heading_commands{$command->{'cmdname'}}) { + if ($target ne '') { + my $target_base_contents = $target; + $target_base_contents =~ s/^g_t//; + $target_contents = 'toc-'.$target_base_contents; + my $toc_nr = $nr -1; + while ($self->{'seen_ids'}->{$target_contents}) { + $target_contents = 'toc-'.$target_base_contents.'-'.$toc_nr; + $toc_nr++; + # Avoid integer overflow + die if ($toc_nr == 0); + } + + $target_shortcontents = 'stoc-'.$target_base_contents; + my $target_base_shortcontents = $target_base; + $target_base_shortcontents =~ s/^g_t//; + my $stoc_nr = $nr -1; + while ($self->{'seen_ids'}->{$target_shortcontents}) { + $target_shortcontents = 'stoc-'.$target_base_shortcontents + .'-'.$stoc_nr; + $stoc_nr++; + # Avoid integer overflow + die if ($stoc_nr == 0); + } + } + } + + if (defined($self->{'file_id_setting'}->{'sectioning_command_target_name'})) { + ($target, $target_contents, + $target_shortcontents, $filename) + = &{$self->{'file_id_setting'}->{'sectioning_command_target_name'}}($self, + $command, $target, + $target_contents, + $target_shortcontents, + $filename); + } + if ($self->get_conf('DEBUG')) { + print STDERR "Register $command->{'cmdname'} $target\n"; + } + $self->{'targets'}->{$command} = { + 'target' => $target, + 'section_filename' => $filename, + }; + $self->{'seen_ids'}->{$target} = 1; + if (defined($target_contents)) { + $self->{'targets'}->{$command}->{'contents_target'} = $target_contents; + } else { + $self->{'targets'}->{$command}->{'contents_target'} = ''; + } + if (defined($target_shortcontents)) { + $self->{'targets'}->{$command}->{'shortcontents_target'} + = $target_shortcontents; + } else { + $self->{'targets'}->{$command}->{'shortcontents_target'} = ''; + } + return $self->{'targets'}->{$command}; +} + +# This set with two different codes +# * the target information, id and normalized filename of 'labels', +# ie everything that may be the target of a ref, @node, @float label, +# @anchor. +# * The target information of sectioning elements by going through tree units +# @node and section commands targets are therefore both set. +# +# conversion to HTML is done on-demand, upon call to command_text +# and similar functions. +# Note that 'node_filename', which is set here for Top target information +# too, is not used later for Top anchors or links, see the NOTE below +# associated with setting TOP_NODE_FILE_TARGET. +sub _set_root_commands_targets_node_files($$) +{ + my $self = shift; + my $tree_units = shift; + + my $no_unidecode; + $no_unidecode = 1 if (defined($self->get_conf('USE_UNIDECODE')) + and !$self->get_conf('USE_UNIDECODE')); + + my $extension = ''; + $extension = '.'.$self->get_conf('EXTENSION') + if (defined($self->get_conf('EXTENSION')) + and $self->get_conf('EXTENSION') ne ''); + if ($self->{'labels'}) { + foreach my $label (sort(keys(%{$self->{'labels'}}))) { + my $target_element = $self->{'labels'}->{$label}; + my $label_element = Texinfo::Common::get_label_element($target_element); + my ($node_filename, $target) + = $self->_normalized_label_id_file($target_element->{'extra'}->{'normalized'}, + $label_element->{'contents'}); + $node_filename .= $extension; + if (defined($self->{'file_id_setting'}->{'node_file_name'})) { + # a non defined filename is ok if called with convert, but not + # if output in files. We reset if undef, silently unless verbose + # in case called by convert. + my $user_node_filename + = &{$self->{'file_id_setting'}->{'node_file_name'}}( + $self, $target_element, $node_filename); + if (defined($user_node_filename)) { + $node_filename = $user_node_filename; + } elsif ($self->get_conf('VERBOSE')) { + $self->document_warn($self, sprintf(__( + "user-defined node file name not set for `%s'"), + $node_filename)); + + } elsif ($self->get_conf('DEBUG')) { + warn "user-defined node file name undef for `$node_filename'\n"; + } + } + if ($self->get_conf('DEBUG')) { + print STDERR 'Label' + # uncomment to get the perl object names + #."($target_element)" + ." \@$target_element->{'cmdname'} $target, $node_filename\n"; + } + $self->{'targets'}->{$target_element} = {'target' => $target, + 'node_filename' => $node_filename}; + $self->{'seen_ids'}->{$target} = 1; + } + } + + if ($tree_units) { + foreach my $tree_unit (@$tree_units) { + foreach my $root_element(@{$tree_unit->{'contents'}}) { + # this happens for types which would precede the root commands. + # The target may already be set for the top node tree unit. + next if (!defined($root_element->{'cmdname'}) + or $self->{'targets'}->{$root_element}); + if ($sectioning_heading_commands{$root_element->{'cmdname'}}) { + $self->_new_sectioning_command_target($root_element); + } + } + } + } +} + +sub _html_get_tree_root_element($$;$); + +# If $find_container is set, the element that holds the command output +# is found, otherwise the element that holds the command is found. This is +# mostly relevant for footnote only. +# If no known root element type is found, the returned root element is undef, +# and not set to the element at the tree root +sub _html_get_tree_root_element($$;$) +{ + my $self = shift; + my $command = shift; + my $find_container = shift; + + # can be used to debug/understand what is going on + #my $debug = 1; + + my $current = $command; + #print STDERR "START ".Texinfo::Common::debug_print_element($current)."\n" if ($debug); + + my ($root_element, $root_command); + while (1) { + if ($current->{'type'}) { + if ($current->{'type'} eq 'unit' + or $current->{'type'} eq 'special_element') { + #print STDERR "ROOT ELEMENT $current->{'type'}\n" if ($debug); + return ($current, $root_command); + } + } + if ($current->{'cmdname'}) { + if ($root_commands{$current->{'cmdname'}}) { + $root_command = $current; + #print STDERR "CMD ROOT $current->{'cmdname'}\n" if ($debug); + return ($root_element, $root_command) if defined($root_element); + } elsif ($block_commands{$current->{'cmdname'}} + and $block_commands{$current->{'cmdname'}} eq 'region') { + if ($current->{'cmdname'} eq 'copying' + and $self->{'global_commands'} + and $self->{'global_commands'}->{'insertcopying'}) { + foreach my $insertcopying(@{$self->{'global_commands'} + ->{'insertcopying'}}) { + #print STDERR "INSERTCOPYING\n" if ($debug); + my ($root_element, $root_command) + = $self->_html_get_tree_root_element($insertcopying, + $find_container); + return ($root_element, $root_command) + if (defined($root_element) or defined($root_command)); + } + } elsif ($current->{'cmdname'} eq 'titlepage' + and $self->get_conf('USE_TITLEPAGE_FOR_TITLE') + and $self->get_conf('SHOW_TITLE') + and $self->{'tree_units'}->[0]) { + #print STDERR "FOR titlepage tree_units [0]\n" if ($debug); + return ($self->{'tree_units'}->[0], + $self->{'tree_units'}->[0]->{'extra'}->{'unit_command'}); + } + die "Problem $root_element, $root_command" if (defined($root_element) + or defined($root_command)); + return (undef, undef); + } elsif ($find_container) { + # @footnote and possibly @*contents when a separate element is set + my ($special_element_variety, $special_element, $class_base, + $special_element_direction) + = $self->command_name_special_element_information($current->{'cmdname'}); + if ($special_element) { + #print STDERR "SPECIAL $current->{'cmdname'}: $special_element_variety ($special_element_direction)\n" if ($debug); + return ($special_element); + } + } + } + if ($current->{'structure'} + and $current->{'structure'}->{'associated_unit'}) { + #print STDERR "ASSOCIATED_UNIT ".Texinfo::Common::debug_print_element($current->{'structure'}->{'associated_unit'})."\n" if ($debug); + $current = $current->{'structure'}->{'associated_unit'}; + } elsif ($current->{'parent'}) { + #print STDERR "PARENT ".Texinfo::Common::debug_print_element($current->{'parent'})."\n" if ($debug); + $current = $current->{'parent'}; + } else { + #print STDERR "UNKNOWN ROOT ".Texinfo::Common::debug_print_element($current)."\n" if ($debug); + return (undef, $root_command); + } + } +} + +sub _html_set_pages_files($$$$$$$$) +{ + my $self = shift; + my $tree_units = shift; + my $special_elements = shift; + my $output_file = shift; + my $destination_directory = shift; + my $output_filename = shift; + my $document_name = shift; + + # Ensure that the document has pages + return undef if (!defined($tree_units) or !@$tree_units); + + $self->initialize_tree_units_files(); + + my $extension = ''; + $extension = '.'.$self->get_conf('EXTENSION') + if (defined($self->get_conf('EXTENSION')) + and $self->get_conf('EXTENSION') ne ''); + + my %filenames_paths; + my %unit_file_name_paths; + # associate a file to the source information leading to set the file + # name. Use the first element source information associated to a file + # The source information can be either a tree element associated to + # the 'file_info_element' key, with a 'file_info_type' 'node' or + # 'section'... or a specific source associated to the 'file_info_name' + # key with 'file_info_type' 'special_file', or a source set if + # nothing was found, with 'file_info_type' 'stand_in_file' and a + # 'file_info_name'. Redirection files are added in the output() + # function. + my %files_source_info = (); + if (!$self->get_conf('SPLIT')) { + $filenames_paths{$output_filename} = $output_file; + foreach my $tree_unit (@$tree_units) { + $unit_file_name_paths{$tree_unit} = $output_filename; + } + $files_source_info{$output_filename} + = {'file_info_type' => 'special_file', + 'file_info_name' => 'non_split'}; + } else { + my $node_top; + $node_top = $self->{'labels'}->{'Top'} if ($self->{'labels'}); + + my $top_node_filename = $self->top_node_filename($document_name); + my $node_top_tree_unit; + # first determine the top node file name. + if ($node_top and defined($top_node_filename)) { + my ($node_top_tree_unit) = $self->_html_get_tree_root_element($node_top); + die "BUG: No element for top node" if (!defined($node_top_tree_unit)); + $filenames_paths{$top_node_filename} = undef; + $unit_file_name_paths{$node_top_tree_unit} = $top_node_filename; + $files_source_info{$top_node_filename} + = {'file_info_type' => 'special_file', + 'file_info_name' => 'Top'}; + } + my $file_nr = 0; + my $previous_page; + foreach my $tree_unit (@$tree_units) { + # For Top node. + next if (exists($unit_file_name_paths{$tree_unit})); + my $file_tree_unit = $tree_unit->{'extra'}->{'first_in_page'}; + if (!$file_tree_unit) { + cluck ("No first_in_page for $tree_unit\n"); + } + if (not exists($unit_file_name_paths{$file_tree_unit})) { + foreach my $root_command (@{$file_tree_unit->{'contents'}}) { + if ($root_command->{'cmdname'} + and $root_command->{'cmdname'} eq 'node') { + my $node_filename; + # double node are not normalized, they are handled here + if (!defined($root_command->{'extra'}->{'normalized'}) + or !defined($self->{'labels'}->{ + $root_command->{'extra'}->{'normalized'}})) { + $node_filename = 'unknown_node'; + $node_filename .= $extension; + my $file_source_info = {'file_info_type' => 'stand_in_file', + 'file_info_name' => 'unknown_node'}; + $files_source_info{$node_filename} = $file_source_info + unless ($files_source_info{$node_filename}); + } else { + # Nodes with {'extra'}->{'normalized'} should always be in + # 'labels', and thus in targets. It is a bug otherwise. + $node_filename + = $self->{'targets'}->{$root_command}->{'node_filename'}; + my $file_source_info = {'file_info_type' => 'node', + 'file_info_element' => $root_command}; + $files_source_info{$node_filename} = $file_source_info + unless ($files_source_info{$node_filename} + and $files_source_info{$node_filename} + ->{'file_info_type'} ne 'stand_in_file'); + } + $filenames_paths{$node_filename} = undef; + $unit_file_name_paths{$file_tree_unit} = $node_filename; + last; + } + } + if (not defined($unit_file_name_paths{$file_tree_unit})) { + # use section to do the file name if there is no node + my $command = $self->tree_unit_element_command($file_tree_unit); + if ($command) { + if ($command->{'cmdname'} eq 'top' and !$node_top + and defined($top_node_filename)) { + $filenames_paths{$top_node_filename} = undef; + $unit_file_name_paths{$file_tree_unit} = $top_node_filename; + $files_source_info{$top_node_filename} + = {'file_info_type' => 'special_file', + 'file_info_name' => 'Top'}; + } else { + my $section_filename + = $self->{'targets'}->{$command}->{'section_filename'}; + $filenames_paths{$section_filename} = undef; + $unit_file_name_paths{$file_tree_unit} = $section_filename; + $files_source_info{$section_filename} + = {'file_info_type' => 'section', + 'file_info_element' => $command} + unless($files_source_info{$section_filename} + and $files_source_info{$section_filename} + ->{'file_info_type'} ne 'stand_in_file'); + } + } else { + # when everything else has failed + if ($file_nr == 0 and !$node_top + and defined($top_node_filename)) { + $filenames_paths{$top_node_filename} = undef; + $unit_file_name_paths{$file_tree_unit} = $top_node_filename; + $files_source_info{$top_node_filename} + = {'file_info_type' => 'stand_in_file', + 'file_info_name' => 'Top'} + unless($files_source_info{$top_node_filename}); + } else { + my $filename = $document_name . "_$file_nr"; + $filename .= $extension; + $filenames_paths{$filename} = undef; + $unit_file_name_paths{$file_tree_unit} = $filename; + $files_source_info{$filename} + = {'file_info_type' => 'stand_in_file', + 'file_info_name' => 'unknown'} + unless($files_source_info{$filename}); + } + $file_nr++; + } + } + } + if (not exists($unit_file_name_paths{$tree_unit})) { + $unit_file_name_paths{$tree_unit} + = $unit_file_name_paths{$file_tree_unit} + } + } + } + + foreach my $tree_unit (@$tree_units) { + my $filename = $unit_file_name_paths{$tree_unit}; + # check + if (!$files_source_info{$filename}) { + print STDERR "BUG: no files_source_info: $filename\n"; + } + my $filepath = $filenames_paths{$filename}; + if (defined($self->{'file_id_setting'}->{'tree_unit_file_name'})) { + # NOTE the information that it is associated with @top or @node Top + # may be determined with $self->element_is_tree_unit_top($tree_unit); + my ($user_filename, $user_filepath) + = &{$self->{'file_id_setting'}->{'tree_unit_file_name'}}( + $self, $tree_unit, $filename, $filepath); + if (defined($user_filename)) { + $filename = $user_filename; + $filenames_paths{$filename} = $user_filepath; + $files_source_info{$filename} = {'file_info_type' => 'special_file', + 'file_info_name' => 'user_defined'}; + } + } + $self->set_tree_unit_file($tree_unit, $filename); + my $tree_unit_filename = $tree_unit->{'structure'}->{'unit_filename'}; + $self->{'file_counters'}->{$tree_unit_filename} = 0 + if (!exists($self->{'file_counters'}->{$tree_unit_filename})); + $self->{'file_counters'}->{$tree_unit_filename}++; + print STDERR 'Page ' + # uncomment for perl object name + #."$tree_unit " + .Texinfo::Structuring::root_or_external_element_cmd_texi($tree_unit) + .": $tree_unit_filename($self->{'file_counters'}->{$tree_unit_filename})\n" + if ($self->get_conf('DEBUG')); + } + if ($special_elements) { + foreach my $special_element (@$special_elements) { + my $filename + = $self->{'targets'}->{$special_element}->{'special_element_filename'}; + if (defined($filename)) { + $filenames_paths{$filename} = undef; + $self->set_tree_unit_file($special_element, $filename); + $self->{'file_counters'}->{$filename} = 0 + if (!exists($self->{'file_counters'}->{$filename})); + $self->{'file_counters'}->{$filename}++; + print STDERR 'Special page' + # uncomment for perl object name + #." $special_element" + .": $filename($self->{'file_counters'}->{$filename})\n" + if ($self->get_conf('DEBUG')); + my $file_source_info = {'file_info_element' => $special_element, + 'file_info_type' => 'special_element'}; + $files_source_info{$filename} = $file_source_info + unless($files_source_info{$filename} + and $files_source_info{$filename}->{'file_info_type'} + ne 'stand_in_file'); + } + } + } + foreach my $filename (keys(%filenames_paths)) { + $self->set_file_path($filename, $destination_directory, + $filenames_paths{$filename}); + } + return %files_source_info; +} + +# $ROOT is a parsed Texinfo tree. Return a list of the "elements" we need to +# output in the HTML file(s). Each "element" is what can go in one HTML file, +# such as the content between @node lines in the Texinfo source. +# Also do some conversion setup that is to be done in both convert() and output(). +sub _prepare_conversion_tree_units($$$$) +{ + my $self = shift; + my $root = shift; + my $destination_directory = shift; + my $document_name = shift; + + my $tree_units; + + if ($self->get_conf('USE_NODES')) { + $tree_units = Texinfo::Structuring::split_by_node($root); + } else { + $tree_units = Texinfo::Structuring::split_by_section($root); + } + + $self->{'tree_units'} = $tree_units + if (defined($tree_units)); + + # This may be done as soon as tree units are available. + $self->_prepare_tree_units_global_targets($tree_units); + + # the presence of contents elements in the document is used in diverse + # places, set it once for all here + my @contents_elements_options + = grep {Texinfo::Common::valid_customization_option($_)} + sort(keys(%contents_command_special_element_variety)); + $self->set_global_document_commands('last', \@contents_elements_options); + + # configuration used to determine if a special element is to be done + # (in addition to contents) + my @conf_for_special_elements = ('footnotestyle'); + $self->set_global_document_commands('last', \@conf_for_special_elements); + # Do that before the other elements, to be sure that special page ids + # are registered before elements id are. + # NOTE if the last value of footnotestyle is separate, all the footnotes + # formatted text are set to the special element set in _prepare_special_elements + # as _html_get_tree_root_element uses the Footnote direction for every footnote. + # Therefore if @footnotestyle separate is set late in the document the current + # value may not be consistent with the link obtained for the footnote + # formatted text. This is not an issue, as the manual says that + # @footnotestyle should only appear in the preamble, and it makes sense + # to have something consistent in the whole document for footnotes position. + my $special_elements + = $self->_prepare_special_elements($tree_units, $destination_directory, + $document_name); + # reset to the default + $self->set_global_document_commands('before', \@conf_for_special_elements); + + if ($special_elements and defined($tree_units) and scalar(@$tree_units)) { + my $previous_tree_unit = $tree_units->[-1]; + foreach my $special_element (@$special_elements) { + $special_element->{'structure'}->{'unit_prev'} = $previous_tree_unit; + $previous_tree_unit->{'structure'}->{'unit_next'} = $special_element; + $previous_tree_unit = $special_element; + } + } + + #if ($tree_units) { + # foreach my $element(@{$tree_units}) { + # print STDERR "ELEMENT $element->{'type'}: $element\n"; + # } + #} + + $self->_set_root_commands_targets_node_files($tree_units); + + # setup untranslated strings + $self->_translate_names(); + + return ($tree_units, $special_elements); +} + +sub _prepare_special_elements($$$$) +{ + my $self = shift; + my $tree_units = shift; + my $destination_directory = shift; + my $document_name = shift; + + my %do_special; + if ($self->{'structuring'} and $self->{'structuring'}->{'sectioning_root'} + and scalar(@{$self->{'structuring'}->{'sections_list'}}) > 1) { + if ($self->get_conf('CONTENTS_OUTPUT_LOCATION') eq 'separate_element') { + foreach my $cmdname ('shortcontents', 'contents') { + my $special_element_variety + = $contents_command_special_element_variety{$cmdname}; + if ($self->get_conf($cmdname)) { + $do_special{$special_element_variety} = 1; + } + } + } + } + if ($self->{'global_commands'}->{'footnote'} + and $self->get_conf('footnotestyle') eq 'separate' + and $tree_units and scalar(@$tree_units) > 1) { + $do_special{'footnotes'} = 1; + } + + if ((!defined($self->get_conf('DO_ABOUT')) + and $tree_units and scalar(@$tree_units) > 1 + and ($self->get_conf('SPLIT') or $self->get_conf('HEADERS'))) + or ($self->get_conf('DO_ABOUT'))) { + $do_special{'about'} = 1; + } + + my $extension = ''; + $extension = $self->get_conf('EXTENSION') + if (defined($self->get_conf('EXTENSION'))); + + my $special_elements = []; + # sort special elements according to their index order from + # special_element_info 'order'. + # First reverse the hash, using arrays in case some elements are at the + # same index, and sort to get alphabetically sorted special element + # varieties that are at the same index. + my %special_elements_indices; + foreach my $special_element_variety + (sort($self->special_element_info('order'))) { + my $index = $self->special_element_info('order', $special_element_variety); + $special_elements_indices{$index} = [] + if (not exists ($special_elements_indices{$index})); + push @{$special_elements_indices{$index}}, $special_element_variety; + } + # now sort according to indices + my @sorted_elements_varieties; + foreach my $index (sort { $a <=> $b } (keys(%special_elements_indices))) { + push @sorted_elements_varieties, @{$special_elements_indices{$index}}; + } + + foreach my $special_element_variety (@sorted_elements_varieties) { + next unless ($do_special{$special_element_variety}); + + my $element = {'type' => 'special_element', + 'extra' => {'special_element_variety' + => $special_element_variety,}, + 'structure' => {'directions' => {}}}; + $element->{'structure'}->{'directions'}->{'This'} = $element; + my $special_element_direction + = $self->special_element_info('direction', $special_element_variety); + $self->{'special_elements_directions'}->{$special_element_direction} + = $element; + push @$special_elements, $element; + + my $target + = $self->special_element_info('target', $special_element_variety); + my $default_filename; + if ($self->get_conf('SPLIT') or !$self->get_conf('MONOLITHIC') + # in general $document_name not defined means called through convert + and defined($document_name)) { + my $special_element_file_string = + $self->special_element_info('file_string', $special_element_variety); + $special_element_file_string = '' if (!defined($special_element_file_string)); + $default_filename = $document_name . $special_element_file_string; + $default_filename .= '.'.$extension if (defined($extension)); + } else { + $default_filename = undef; + } + + my $filename; + if (defined($self->{'file_id_setting'}->{'special_element_target_file_name'})) { + ($target, $filename) + = &{$self->{'file_id_setting'}->{'special_element_target_file_name'}}( + $self, + $element, + $target, + $default_filename); + } + $filename = $default_filename if (!defined($filename)); + + if ($self->get_conf('DEBUG')) { + my $fileout = $filename; + $fileout = 'UNDEF' if (!defined($fileout)); + print STDERR 'Add special' + # uncomment for the perl object name + #." $element" + ." $special_element_variety: target $target,\n". + " filename $fileout\n"; + } + $self->{'targets'}->{$element} = {'target' => $target, + 'special_element_filename' => $filename, + }; + $self->{'seen_ids'}->{$target} = 1; + } + if ($self->get_conf('FRAMES')) { + $self->{'frame_pages_filenames'} = {}; + foreach my $special_element_variety (keys(%{$self->{'frame_pages_file_string'}})) { + my $default_filename; + $default_filename = $document_name. + $self->{'frame_pages_file_string'}->{$special_element_variety}; + $default_filename .= '.'.$extension if (defined($extension)); + + my $element = {'type' => 'special_element', + 'extra' => {'special_element_variety' + => $special_element_variety, }}; + + # only the filename is used + my ($target, $filename); + if (defined($self->{'file_id_setting'}->{'special_element_target_file_name'})) { + ($target, $filename) + = &{$self->{'file_id_setting'}->{'special_element_target_file_name'}}( + $self, + $element, + $target, + $default_filename); + } + $filename = $default_filename if (!defined($filename)); + $self->{'frame_pages_filenames'}->{$special_element_variety} = $filename; + } + } + return $special_elements; +} + +sub _prepare_contents_elements($) +{ + my $self = shift; + + if ($self->{'structuring'} and $self->{'structuring'}->{'sectioning_root'} + and scalar(@{$self->{'structuring'}->{'sections_list'}}) > 1) { + foreach my $cmdname ('contents', 'shortcontents') { + my $special_element_variety + = $contents_command_special_element_variety{$cmdname}; + if ($self->get_conf($cmdname)) { + my $default_filename; + if ($self->get_conf('CONTENTS_OUTPUT_LOCATION') eq 'after_title') { + if ($self->{'tree_units'} and $self->{'tree_units'}->[0]->{'structure'} + and exists($self->{'tree_units'}->[0]->{'structure'}->{'unit_filename'})) { + $default_filename + = $self->{'tree_units'}->[0]->{'structure'}->{'unit_filename'}; + } + } elsif ($self->get_conf('CONTENTS_OUTPUT_LOCATION') eq 'after_top') { + my $section_top = undef; + if ($self->{'global_commands'} and $self->{'global_commands'}->{'top'}) { + $section_top = $self->{'global_commands'}->{'top'}; + $default_filename = $self->command_filename($section_top); + } + } elsif ($self->get_conf('CONTENTS_OUTPUT_LOCATION') eq 'inline') { + if ($self->{'global_commands'} + and $self->{'global_commands'}->{$cmdname}) { + foreach my $command(@{$self->{'global_commands'}->{$cmdname}}) { + my ($root_element, $root_command) + = $self->_html_get_tree_root_element($command); + if (defined($root_element) and $root_element->{'structure'} + and exists($root_element->{'structure'}->{'unit_filename'})) { + $default_filename + = $root_element->{'structure'}->{'unit_filename'}; + last; + } + } + } else { + next; + } + } else { # in this case, there should already be a special element + # if needed, done together with the other special elements. + next; + } + + my $contents_element = {'type' => 'special_element', + 'extra' => {'special_element_variety' + => $special_element_variety}}; + my $special_element_direction + = $self->special_element_info('direction', $special_element_variety); + $self->{'special_elements_directions'}->{$special_element_direction} + = $contents_element; + my $target + = $self->special_element_info('target', $special_element_variety); + my $filename; + if (defined($self->{'file_id_setting'}->{'special_element_target_file_name'})) { + ($target, $filename) + = &{$self->{'file_id_setting'}->{'special_element_target_file_name'}}( + $self, + $contents_element, + $target, + $default_filename); + } + $filename = $default_filename if (!defined($filename)); + if ($self->get_conf('DEBUG')) { + my $str_filename = $filename; + $str_filename = 'UNDEF' if (not defined($str_filename)); + print STDERR 'Add content' + # uncomment to get the perl obect name + #." $contents_element" + ." $special_element_variety: target $target,\n". + " filename $str_filename\n"; + } + $self->{'targets'}->{$contents_element} + = {'target' => $target, + 'special_element_filename' => $filename, + 'filename' => $filename, + }; + } + } + } +} + +# Associate tree units with the global targets, First, Last, Top, Index. +sub _prepare_tree_units_global_targets($$) +{ + my $self = shift; + my $tree_units = shift; + + $self->{'global_target_elements_directions'} = {}; + $self->{'global_target_elements_directions'}->{'First'} = $tree_units->[0]; + $self->{'global_target_elements_directions'}->{'Last'} = $tree_units->[-1]; + # It is always the first printindex, even if it is not output (for example + # it is in @copying and @titlepage, which are certainly wrong constructs). + if ($self->{'global_commands'} and $self->{'global_commands'}->{'printindex'}) { + # Here root_element can only be a tree unit, or maybe undef if there + # are no tree unit at all + my ($root_element, $root_command) + = $self->_html_get_tree_root_element($self->{'global_commands'}->{'printindex'}->[0]); + if (defined($root_element)) { + if ($root_command and $root_command->{'cmdname'} eq 'node' + and $root_command->{'extra'}->{'associated_section'}) { + $root_command = $root_command->{'extra'}->{'associated_section'}; + } + # find the first level 1 sectioning element to associate the printindex with + if ($root_command and $root_command->{'cmdname'} ne 'node') { + while ($root_command->{'structure'}->{'section_level'} > 1 + and $root_command->{'structure'}->{'section_up'} + and $root_command->{'structure'}->{'section_up'} + ->{'structure'}->{'associated_unit'}) { + $root_command = $root_command->{'structure'}->{'section_up'}; + $root_element = $root_command->{'structure'}->{'associated_unit'}; + } + } + $self->{'global_target_elements_directions'}->{'Index'} = $root_element; + } + } + + my $node_top; + $node_top = $self->{'labels'}->{'Top'} if ($self->{'labels'}); + my $section_top; + $section_top = $self->{'global_commands'}->{'top'} if ($self->{'global_commands'}); + if ($section_top) { + $self->{'global_target_elements_directions'}->{'Top'} + = $section_top->{'structure'}->{'associated_unit'}; + } elsif ($node_top) { + my $tree_unit_top = $node_top->{'structure'}->{'associated_unit'}; + if (!$tree_unit_top) { + die "No associated unit for node_top: " + .Texinfo::Common::debug_print_element($node_top, 1); + } + $self->{'global_target_elements_directions'}->{'Top'} = $tree_unit_top; + } else { + $self->{'global_target_elements_directions'}->{'Top'} = $tree_units->[0]; + } + + if ($self->get_conf('DEBUG')) { + print STDERR "GLOBAL DIRECTIONS:\n"; + foreach my $global_direction (@global_directions) { + if (defined($self->global_direction_element($global_direction))) { + my $global_element = $self->global_direction_element($global_direction); + print STDERR "$global_direction" + # uncomment to get the perl object name + # ."($global_element)" + .': '. Texinfo::Structuring::root_or_external_element_cmd_texi($global_element)."\n"; + } + } + } +} + +sub _prepare_index_entries($) +{ + my $self = shift; + + my $indices_information = $self->{'indices_information'}; + if ($indices_information) { + my $no_unidecode; + $no_unidecode = 1 if (defined($self->get_conf('USE_UNIDECODE')) + and !$self->get_conf('USE_UNIDECODE')); + + my $merged_index_entries + = Texinfo::Structuring::merge_indices($indices_information); + my $index_entries_sort_strings; + ($self->{'index_entries_by_letter'}, $index_entries_sort_strings) + = Texinfo::Structuring::sort_indices($self, + $self, $merged_index_entries, + $indices_information, + 'by_letter'); + $self->{'index_entries'} = $merged_index_entries; + + foreach my $index_name (sort(keys(%$indices_information))) { + foreach my $index_entry (@{$indices_information->{$index_name} + ->{'index_entries'}}) { + my $main_entry_element = $index_entry->{'entry_element'}; + # does not refer to the document + next if ($main_entry_element->{'extra'} + and ($main_entry_element->{'extra'}->{'seeentry'} + or $main_entry_element->{'extra'}->{'seealso'})); + my $region = ''; + $region = "$main_entry_element->{'extra'}->{'element_region'}-" + if (defined($main_entry_element->{'extra'}->{'element_region'})); + my $entry_reference_content_element + = Texinfo::Common::index_content_element($main_entry_element, 1); + my @contents = ($entry_reference_content_element); + my $subentries_tree + = $self->comma_index_subentries_tree($main_entry_element, + ' '); + if (defined($subentries_tree)) { + push @contents, @{$subentries_tree->{'contents'}}; + } + my $trimmed_contents + = Texinfo::Common::trim_spaces_comment_from_content(\@contents); + my $normalized_index = + Texinfo::Convert::NodeNameNormalization::normalize_transliterate_texinfo( + {'contents' => \@contents}, $no_unidecode); + my $target_base = "index-" . $region .$normalized_index; + my $nr=1; + my $target = $target_base; + while ($self->{'seen_ids'}->{$target}) { + $target = $target_base.'-'.$nr; + $nr++; + # Avoid integer overflow + die if ($nr == 0); + } + $self->{'seen_ids'}->{$target} = 1; + my $target_element = $main_entry_element; + $target_element = $index_entry->{'entry_associated_element'} + if ($index_entry->{'entry_associated_element'}); + $self->{'targets'}->{$target_element} = {'target' => $target, }; + } + } + } +} + +sub _prepare_footnotes($) +{ + my $self = shift; + + my $footid_base = 'FOOT'; + my $docid_base = 'DOCF'; + + $self->{'pending_footnotes'} = []; + + if ($self->{'global_commands'}->{'footnote'}) { + my $footnote_nr = 0; + foreach my $footnote (@{$self->{'global_commands'}->{'footnote'}}) { + $footnote_nr++; + my $nr = $footnote_nr; + # anchor for the footnote text + my $footid = $footid_base.$nr; + # anchor for the location of the @footnote in the document + my $docid = $docid_base.$nr; + while ($self->{'seen_ids'}->{$docid} or $self->{'seen_ids'}->{$footid}) { + $nr++; + $footid = $footid_base.$nr; + $docid = $docid_base.$nr; + # Avoid integer overflow + die if ($nr == 0); + } + $self->{'seen_ids'}->{$footid} = 1; + $self->{'seen_ids'}->{$docid} = 1; + $self->{'targets'}->{$footnote} = { 'target' => $footid }; + $self->{'special_targets'}->{'footnote_location'}->{$footnote} + = { 'target' => $docid }; + print STDERR 'Enter footnote' + # uncomment for the perl object name + #." $footnote" + .": target $footid, nr $footnote_nr\n" + .Texinfo::Convert::Texinfo::convert_to_texinfo($footnote)."\n" + if ($self->get_conf('DEBUG')); + } + } +} + +sub _external_node_href($$$;$) +{ + my $self = shift; + my $external_node = shift; + my $filename = shift; + # for messages only + my $source_command = shift; + + #print STDERR "external_node: ".join('|', keys(%$external_node))."\n"; + my ($target_filebase, $target) + = $self->_normalized_label_id_file($external_node->{'normalized'}, + $external_node->{'node_content'}); + + my $xml_target = _normalized_to_id($target); + + my $default_target_split = $self->get_conf('EXTERNAL_CROSSREF_SPLIT'); + + my $external_file_extension = ''; + my $external_extension = $self->get_conf('EXTERNAL_CROSSREF_EXTENSION'); + $external_extension = $self->get_conf('EXTENSION') + if (not defined($external_extension)); + $external_file_extension = '.' . $external_extension + if (defined($external_extension) and $external_extension ne ''); + + my $target_split; + my $file; + if ($external_node->{'manual_content'}) { + my $manual_name = Texinfo::Convert::Text::convert_to_text( + {'contents' => $external_node->{'manual_content'}}, + { 'code' => 1, + Texinfo::Convert::Text::copy_options_for_convert_text($self)}); + if ($self->get_conf('IGNORE_REF_TO_TOP_NODE_UP') and $xml_target eq '') { + my $top_node_up = $self->get_conf('TOP_NODE_UP'); + if (defined($top_node_up) and "($manual_name)" eq $top_node_up) { + return ''; + } + } + my $manual_base = $manual_name; + $manual_base =~ s/\.info*$//; + $manual_base =~ s/^.*\///; + my $document_split = $self->get_conf('SPLIT'); + $document_split = 'mono' if (!$document_split); + my $split_found; + my $href; + my $htmlxref_info = $self->{'htmlxref'}->{$manual_base}; + if ($htmlxref_info) { + foreach my $split_ordered (@{$htmlxref_entries{$document_split}}) { + if (defined($htmlxref_info->{$split_ordered})) { + $split_found = $split_ordered; + $href = $self->url_protect_url_text($htmlxref_info->{$split_ordered}); + last; + } + } + } + if (defined($split_found)) { + $target_split = 1 unless ($split_found eq 'mono'); + } else { # nothing specified for that manual, use default + $target_split = $default_target_split; + if ($self->get_conf('CHECK_HTMLXREF')) { + if (defined($source_command) and $source_command->{'source_info'}) { + my $node_manual_key = $source_command.'-'.$manual_name; + if (!$self->{'check_htmlxref_already_warned'}->{$node_manual_key}) { + $self->line_warn($self, sprintf(__( + "no htmlxref.cnf entry found for `%s'"), $manual_name), + $source_command->{'source_info'}); + $self->{'check_htmlxref_already_warned'}->{$node_manual_key} = 1; + } + } else { + if (!$self->{'check_htmlxref_already_warned'}->{'UNDEF-'.$manual_name}) { + $self->document_warn($self, sprintf(__( + "no htmlxref.cnf entry found for `%s'"), $manual_name), + ); + $self->{'check_htmlxref_already_warned'}->{'UNDEF-'.$manual_name} = 1; + cluck; + } + } + } + } + + if ($target_split) { + if (defined($href)) { + $file = $href; + } else { + my $manual_dir = $manual_base; + if (defined($self->{'output_format'}) and $self->{'output_format'} ne '') { + $manual_dir .= '_'.$self->{'output_format'}; + } + if (defined($self->get_conf('EXTERNAL_DIR'))) { + $file = $self->get_conf('EXTERNAL_DIR')."/$manual_dir"; + } elsif ($self->get_conf('SPLIT')) { + $file = "../$manual_dir"; + } + $file = $self->url_protect_file_text($file); + } + $file .= "/"; + } else {# target not split + if (defined($href)) { + $file = $href; + } else { + my $manual_file_name = $manual_base . $external_file_extension; + if (defined($self->get_conf('EXTERNAL_DIR'))) { + $file = $self->get_conf('EXTERNAL_DIR')."/$manual_file_name"; + } elsif ($self->get_conf('SPLIT')) { + $file = "../$manual_file_name"; + } else { + $file = $manual_file_name; + } + $file = $self->url_protect_file_text($file); + } + } + } else { + $file = ''; + $target_split = $default_target_split; + } + + if ($target eq '') { + if ($target_split) { + if (defined($self->get_conf('TOP_NODE_FILE_TARGET'))) { + return $file . $self->get_conf('TOP_NODE_FILE_TARGET'); + } else { + return $file; + } + } else { + return $file . '#Top'; + } + } + + if (! $target_split) { + return $file . '#' . $xml_target; + } else { + my $file_name; + if ($target eq 'Top' and defined($self->get_conf('TOP_NODE_FILE_TARGET'))) { + $file_name = $self->get_conf('TOP_NODE_FILE_TARGET'); + } else { + $file_name = $target_filebase . $external_file_extension; + } + return $file . $file_name . '#' . $xml_target; + } +} + +# Output a list of the nodes immediately below this one +sub _mini_toc +{ + my ($self, $command) = @_; + + my $result = ''; + my $entry_index = 0; + + if ($command->{'structure'} + and $command->{'structure'}->{'section_childs'} + and @{$command->{'structure'}->{'section_childs'}}) { + $result .= $self->html_attribute_class('ul', ['mini-toc']).">\n"; + + foreach my $section (@{$command->{'structure'}->{'section_childs'}}) { + my $tree = $self->command_text($section, 'tree_nonumber'); + my $text = $self->convert_tree($tree, "mini_toc \@$section->{'cmdname'}"); + + $entry_index++; + my $accesskey = ''; + $accesskey = " accesskey=\"$entry_index\"" + if ($self->get_conf('USE_ACCESSKEY') and $entry_index < 10); + + my $href = $self->command_href($section); + if ($text ne '') { + if ($href ne '') { + my $href_attribute = ''; + if ($href ne '') { + $href_attribute = " href=\"$href\""; + } + $result .= "
  • $text"; + } else { + $result .= "
  • $text"; + } + $result .= "
  • \n"; + } + } + $result .= "\n"; + } + return $result; +} + +sub _default_format_contents($$;$$) +{ + my $self = shift; + my $cmdname = shift; + my $command = shift; + my $filename = shift; + + $filename = $self->get_info('current_filename') if (!defined($filename)); + + my $structuring = $self->get_info('structuring'); + return '' + if (!$structuring or !$structuring->{'sectioning_root'}); + + my $section_root = $structuring->{'sectioning_root'}; + my $contents; + $contents = 1 if ($cmdname eq 'contents'); + + my $min_root_level = $section_root->{'structure'}->{'section_childs'}->[0] + ->{'structure'}->{'section_level'}; + my $max_root_level = $section_root->{'structure'}->{'section_childs'}->[0] + ->{'structure'}->{'section_level'}; + foreach my $top_section (@{$section_root->{'structure'}->{'section_childs'}}) { + $min_root_level = $top_section->{'structure'}->{'section_level'} + if ($top_section->{'structure'}->{'section_level'} < $min_root_level); + $max_root_level = $top_section->{'structure'}->{'section_level'} + if ($top_section->{'structure'}->{'section_level'} > $max_root_level); + } + # chapter level elements are considered top-level here. + $max_root_level = 1 if ($max_root_level < 1); + #print STDERR "ROOT_LEVEL Max: $max_root_level, Min: $min_root_level\n"; + my @toc_ul_classes; + push @toc_ul_classes, 'toc-numbered-mark' + if ($self->get_conf('NUMBER_SECTIONS')); + + my $result = ''; + if ($contents and !defined($self->get_conf('BEFORE_TOC_LINES')) + or (!$contents and !defined($self->get_conf('BEFORE_SHORT_TOC_LINES')))) { + $result .= $self->html_attribute_class('div', [$cmdname]).">\n"; + } elsif($contents) { + $result .= $self->get_conf('BEFORE_TOC_LINES'); + } else { + $result .= $self->get_conf('BEFORE_SHORT_TOC_LINES'); + } + + my $toplevel_contents; + if (@{$section_root->{'structure'}->{'section_childs'}} > 1) { + $result .= $self->html_attribute_class('ul', \@toc_ul_classes) .">\n"; + $toplevel_contents = 1; + } + + my $link_to_toc = (!$contents and $self->get_conf('SHORT_TOC_LINK_TO_TOC') + and ($self->get_conf('contents')) + and ($self->get_conf('CONTENTS_OUTPUT_LOCATION') ne 'inline' + or $self->_has_contents_or_shortcontents())); + foreach my $top_section (@{$section_root->{'structure'}->{'section_childs'}}) { + my $section = $top_section; + SECTION: + while ($section) { + if ($section->{'cmdname'} ne 'top') { + my $text = $self->command_text($section); + my $href; + if ($link_to_toc) { + $href = $self->command_contents_href($section, 'contents', $filename); + } else { + $href = $self->command_href($section, $filename); + } + my $toc_id = $self->command_contents_target($section, $cmdname); + if ($text ne '') { + # no indenting for shortcontents + $result .= (' ' x + (2*($section->{'structure'}->{'section_level'} - $min_root_level))) + if ($contents); + if ($toc_id ne '' or $href ne '') { + my $toc_name_attribute = ''; + if ($toc_id ne '') { + $toc_name_attribute = " id=\"$toc_id\""; + } + my $href_attribute = ''; + if ($href ne '') { + $href_attribute = " href=\"$href\""; + } + my $rel = ''; + if ($section->{'extra'} + and $section->{'extra'}->{'associated_node'} + and $section->{'extra'}->{'associated_node'}->{'extra'} + and $section->{'extra'}->{'associated_node'}->{'extra'}->{'isindex'}) { + $rel = ' rel="index"'; + } + $result .= "
  • $text"; + } else { + $result .= "
  • $text"; + } + } + } elsif ($section->{'structure'}->{'section_childs'} + and @{$section->{'structure'}->{'section_childs'}} + and $toplevel_contents) { + $result .= "
  • "; + } + # for shortcontents don't do child if child is not toplevel + if ($section->{'structure'}->{'section_childs'} + and ($contents + or $section->{'structure'}->{'section_level'} < $max_root_level)) { + # no indenting for shortcontents + $result .= "\n" + . ' ' x (2*($section->{'structure'}->{'section_level'} - $min_root_level)) + if ($contents); + $result .= $self->html_attribute_class('ul', \@toc_ul_classes) .">\n"; + $section = $section->{'structure'}->{'section_childs'}->[0]; + } elsif ($section->{'structure'}->{'section_next'} + and $section->{'cmdname'} ne 'top') { + $result .= "
  • \n"; + last if ($section eq $top_section); + $section = $section->{'structure'}->{'section_next'}; + } else { + #last if ($section eq $top_section); + if ($section eq $top_section) { + $result .= "\n" unless ($section->{'cmdname'} eq 'top'); + last; + } + while ($section->{'structure'}->{'section_up'}) { + $section = $section->{'structure'}->{'section_up'}; + $result .= "\n" + . ' ' x (2*($section->{'structure'}->{'section_level'} - $min_root_level)) + . ""; + if ($section eq $top_section) { + $result .= "\n" if ($toplevel_contents); + last SECTION; + } + if ($section->{'structure'}->{'section_next'}) { + $result .= "\n"; + $section = $section->{'structure'}->{'section_next'}; + last; + } + } + } + } + } + if (@{$section_root->{'structure'}->{'section_childs'}} > 1) { + $result .= "\n"; + } + if ($contents and !defined($self->get_conf('AFTER_TOC_LINES')) + or (!$contents and !defined($self->get_conf('AFTER_SHORT_TOC_LINES')))) { + $result .= "\n\n"; + } elsif($contents) { + $result .= $self->get_conf('AFTER_TOC_LINES'); + } else { + $result .= $self->get_conf('AFTER_SHORT_TOC_LINES'); + } + return $result; +} + +sub _default_format_program_string($) +{ + my $self = shift; + if (defined($self->get_conf('PROGRAM')) + and $self->get_conf('PROGRAM') ne '' + and defined($self->get_conf('PACKAGE_URL'))) { + return $self->convert_tree( + $self->gdt('This document was generated on @emph{@today{}} using @uref{{program_homepage}, @emph{{program}}}.', + { 'program_homepage' => $self->get_conf('PACKAGE_URL'), + 'program' => $self->get_conf('PROGRAM') })); + } else { + return $self->convert_tree( + $self->gdt('This document was generated on @emph{@today{}}.')); + } +} + +sub _default_format_end_file($$) +{ + my $self = shift; + my $filename = shift; + + my $program_text = ''; + if ($self->get_conf('PROGRAM_NAME_IN_FOOTER')) { + my $program_string + = &{$self->formatting_function('format_program_string')}($self); + my $open = $self->html_attribute_class('span', ['program-in-footer']); + if ($open ne '') { + $program_string = $open.'>'.$program_string.''; + } + $program_text .= "

    + $program_string +

    "; + } + + my $pre_body_close = $self->get_conf('PRE_BODY_CLOSE'); + $pre_body_close = '' if (!defined($pre_body_close)); + + my $jslicenses = $self->get_info('jslicenses'); + if ($jslicenses + and (($jslicenses->{'infojs'} + and scalar(keys %{$jslicenses->{'infojs'}})) + or (($self->get_file_information('mathjax', $filename) + or !$self->get_conf('SPLIT')) + and ($jslicenses->{'mathjax'} + and scalar(keys %{$jslicenses->{'mathjax'}}))))) { + my $js_setting = $self->get_conf('JS_WEBLABELS'); + my $js_path = $self->get_conf('JS_WEBLABELS_FILE'); + if (defined($js_setting) and defined($js_path) + and ($js_setting eq 'generate' or $js_setting eq 'reference')) { + $pre_body_close .= + '' + .$self->convert_tree($self->gdt('JavaScript license information')) + .''; + } + } + + return "${program_text} + +$pre_body_close + + +"; +} + +sub _root_html_element_attributes_string($) +{ + my $self = shift; + if (defined($self->get_conf('HTML_ROOT_ELEMENT_ATTRIBUTES')) + and $self->get_conf('HTML_ROOT_ELEMENT_ATTRIBUTES') ne '') { + return ' '.$self->get_conf('HTML_ROOT_ELEMENT_ATTRIBUTES'); + } + return ''; +} + +# This is used for normal output files and other files, like +# redirection file headers. $COMMAND is the tree element for +# a @node that is being output in the file. +sub _file_header_information($$;$) +{ + my $self = shift; + my $command = shift; + my $filename = shift; + + my $title; + if ($command) { + my $command_string = $self->command_text($command, 'string'); + if (defined($command_string) + and $command_string ne $self->get_info('title_string')) { + my $element_tree; + if ($self->get_conf('SECTION_NAME_IN_TITLE') + and $command->{'extra'} + and $command->{'extra'}->{'associated_section'} + and $command->{'extra'}->{'associated_section'}->{'args'} + and $command->{'extra'}->{'associated_section'}->{'args'}->[0]) { + $element_tree = $command->{'extra'}->{'associated_section'}->{'args'}->[0]; + } else { + $element_tree = $self->command_text($command, 'tree'); + } + # TRANSLATORS: sectioning element title for the page header + my $title_tree = $self->gdt('{element_text} ({title})', + { 'title' => $self->get_info('title_tree'), + 'element_text' => $element_tree }); + $title = $self->convert_tree_new_formatting_context( + {'type' => '_string', 'contents' => [$title_tree]}, + $command->{'cmdname'}, 'element_title'); + } + } + $title = $self->get_info('title_string') if (!defined($title)); + + my $description = $self->get_info('documentdescription_string'); + $description = $title + if (not defined($description) or $description eq ''); + $description = $self->close_html_lone_element( + "close_html_lone_element( + "get_conf('OUTPUT_ENCODING_NAME')) + and ($self->get_conf('OUTPUT_ENCODING_NAME') ne '')); + + my $date = ''; + if ($self->get_conf('DATE_IN_HEADER')) { + my $today = $self->convert_tree_new_formatting_context( + {'cmdname' => 'today'}, 'DATE_IN_HEADER'); + $date = + $self->close_html_lone_element( + "formatting_function('format_css_lines')}($self, + $filename); + + my $doctype = $self->get_conf('DOCTYPE'); + my $root_html_element_attributes = $self->_root_html_element_attributes_string(); + my $bodytext = $self->get_conf('BODYTEXT'); + if ($self->get_conf('HTML_MATH') and $self->get_conf('HTML_MATH') eq 'mathjax' + and $self->get_file_information('mathjax', $filename)) { + $bodytext .= ' class="tex2jax_ignore"'; + } + my $copying_comment = $self->get_info('copying_comment'); + $copying_comment = '' + if (not defined($copying_comment)); + my $after_body_open = ''; + $after_body_open = $self->get_conf('AFTER_BODY_OPEN') + if (defined($self->get_conf('AFTER_BODY_OPEN'))); + my $extra_head = ''; + $extra_head = $self->get_conf('EXTRA_HEAD') + if (defined($self->get_conf('EXTRA_HEAD'))); + my $program_and_version = $self->get_conf('PACKAGE_AND_VERSION'); + my $program_homepage = $self->get_conf('PACKAGE_URL'); + my $program = $self->get_conf('PROGRAM'); + my $generator = ''; + if (defined($program) and $program ne '') { + $generator = + $self->close_html_lone_element( + "get_conf('INFO_JS_DIR'))) { + if (!$self->get_conf('SPLIT')) { + $self->document_error($self, + sprintf(__("%s not meaningful for non-split output"), + 'INFO_JS_DIR')); + } else { + my $jsdir = $self->get_conf('INFO_JS_DIR'); + if ($jsdir eq '.') { + $jsdir = ''; + } else { + $jsdir =~ s,/*$,/,; # append a single slash + } + + $extra_head .= $self->close_html_lone_element( + ' +'; + } + } + if ((defined($self->get_conf('HTML_MATH')) + and $self->get_conf('HTML_MATH') eq 'mathjax') + and ($self->get_file_information('mathjax', $filename) + # FIXME do we really want the script element if no math was seen? + or !$self->get_conf('SPLIT'))) { + my $mathjax_script = $self->get_conf('MATHJAX_SCRIPT'); + + $extra_head .= +"" +.''; + + } + + return ($title, $description, $encoding, $date, $css_lines, + $doctype, $root_html_element_attributes, $bodytext, $copying_comment, + $after_body_open, $extra_head, $program_and_version, $program_homepage, + $program, $generator); +} + +sub _get_links($$$$) +{ + my $self = shift; + my $filename = shift; + my $element = shift; + my $node_command = shift; + + my $links = ''; + if ($self->get_conf('USE_LINKS')) { + my $link_buttons = $self->get_conf('LINKS_BUTTONS'); + foreach my $link (@$link_buttons) { + my $link_href = $self->from_element_direction($link, 'href', $element, + $filename, $node_command); + #print STDERR "$link -> $link_href \n"; + if ($link_href and $link_href ne '') { + my $link_string = $self->from_element_direction($link, 'string', + $element); + my $link_title = ''; + $link_title = " title=\"$link_string\"" if (defined($link_string)); + my $rel = ''; + my $button_rel = $self->direction_string($link, 'rel', 'string'); + $rel = " rel=\"".$button_rel.'"' if (defined($button_rel)); + $links .= $self->close_html_lone_element( + "tree_unit_element_command($element); + $node_command = $element_command; + if ($element_command and $element_command->{'cmdname'} + and $element_command->{'cmdname'} ne 'node' + and $element_command->{'extra'} + and $element_command->{'extra'}->{'associated_node'}) { + $node_command = $element_command->{'extra'}->{'associated_node'}; + } + + $command_for_title = $element_command if ($self->get_conf('SPLIT')); + } + + my ($title, $description, $encoding, $date, $css_lines, + $doctype, $root_html_element_attributes, $bodytext, $copying_comment, + $after_body_open, $extra_head, $program_and_version, $program_homepage, + $program, $generator) = $self->_file_header_information($command_for_title, + $filename); + + my $links = $self->_get_links($filename, $element, $node_command); + + my $result = "$doctype + + + +$encoding +$copying_comment$title + +$description\n". + $self->close_html_lone_element( + "close_html_lone_element( + "close_html_lone_element( + "close_html_lone_element( + " + + +$after_body_open"; + + return $result; +} + +sub _default_format_node_redirection_page($$) +{ + my $self = shift; + my $command = shift; + + my ($title, $description, $encoding, $date, $css_lines, + $doctype, $root_html_element_attributes, $bodytext, $copying_comment, + $after_body_open, $extra_head, $program_and_version, $program_homepage, + $program, $generator) = $self->_file_header_information($command); + + my $name = $self->command_text($command); + my $href = $self->command_href($command); + my $direction = "$name"; + my $string = $self->convert_tree( + $self->gdt('The node you are looking for is at {href}.', + { 'href' => {'type' => '_converted', 'text' => $direction }})); + my $result = "$doctype + + + + +$encoding +$copying_comment$title + +$description\n". + $self->close_html_lone_element( + "close_html_lone_element( + "close_html_lone_element( + "close_html_lone_element( + "close_html_lone_element( + " + + +$after_body_open +

    $string

    + +"; + return $result; +} + +sub _default_format_footnotes_sequence($) +{ + my $self = shift; + + my @pending_footnotes = $self->get_pending_footnotes(); + my $result = ''; + foreach my $pending_footnote_info_array (@pending_footnotes) { + my ($command, $footid, $docid, $number_in_doc, + $footnote_location_filename, $multi_expanded_region) + = @$pending_footnote_info_array; + my $footnote_location_href = $self->footnote_location_href($command, undef, + $docid, $footnote_location_filename); + # NOTE the @-commands in @footnote that are formatted differently depending + # on $self->in_multi_expanded() cannot know that the original context + # of the @footnote in the main document was $multi_expanded_region. + # We do not want to set multi_expanded in customizable code. However, it + # could be possible to set a shared_conversion_state based on $multi_expanded_region + # and have all the conversion functions calling $self->in_multi_expanded() + # also check the shared_conversion_state. The special situations + # with those @-commands in @footnote in multi expanded + # region do not justify this additional code and complexity. The consequences + # should only be redundant anchors HTML elements. + my $footnote_text + = $self->convert_tree_new_formatting_context($command->{'args'}->[0], + "$command->{'cmdname'} $number_in_doc $footid"); + chomp ($footnote_text); + $footnote_text .= "\n"; + + $result .= $self->html_attribute_class('h5', ['footnote-body-heading']) . '>'. + "($number_in_doc)\n" + . $footnote_text; + } + return $result; +} + +sub _default_format_footnotes_segment($) +{ + my $self = shift; + my $foot_lines + = &{$self->formatting_function('format_footnotes_sequence')}($self); + return '' if ($foot_lines eq ''); + my $class = $self->special_element_info('class', 'footnotes'); + my $result = $self->html_attribute_class('div', [$class.'-segment']).">\n"; + $result .= $self->get_conf('DEFAULT_RULE') . "\n" + if (defined($self->get_conf('DEFAULT_RULE')) + and $self->get_conf('DEFAULT_RULE') ne ''); + my $footnote_heading_tree = $self->special_element_info('heading_tree', + 'footnotes'); + my $footnote_heading; + if (defined($footnote_heading_tree)) { + $footnote_heading + = $self->convert_tree($footnote_heading_tree, + 'convert footnotes special heading'); + } else { + $footnote_heading = ''; + } + my $level = $self->get_conf('FOOTNOTE_END_HEADER_LEVEL'); + $result .= &{$self->formatting_function('format_heading_text')}($self, undef, + [$class.'-heading'], $footnote_heading, $level)."\n"; + $result .= $foot_lines; + $result .= "\n"; + return $result; +} + +sub _default_format_special_body_about($$$) +{ + my $self = shift; + my $special_type = shift; + my $element = shift; + + my $about = ''; + if ($self->get_conf('PROGRAM_NAME_IN_ABOUT')) { + $about .= "

    \n"; + $about .= ' '.&{$self->formatting_function('format_program_string')}($self) ."\n"; + $about .= "

    \n"; + } + $about .= < +EOT + $about .= $self->convert_tree( + $self->gdt(' The buttons in the navigation panels have the following meaning:')) + . "\n"; + $about .= < + + +EOT + # TRANSLATORS: direction column header in the navigation help + $about .= ' \n" . + # TRANSLATORS: button label column header in the navigation help + ' \n" . + # TRANSLATORS: direction description column header in the navigation help + ' \n" . + # TRANSLATORS: section reached column header in the navigation help + ' \n" + . " \n"; + + foreach my $button_spec (@{$self->get_conf('SECTION_BUTTONS')}) { + next if ($button_spec eq ' ' or ref($button_spec) eq 'CODE' + or ref($button_spec) eq 'SCALAR' + or (ref($button_spec) eq 'ARRAY' and scalar(@$button_spec) != 2)); + my $button; + if (ref($button_spec) eq 'ARRAY') { + $button = $button_spec->[0]; + } else { + $button = $button_spec; + } + $about .= " \n ".$self->html_attribute_class('td', + ['button-direction-about']) .'>'; + # if the button spec is an array we do not knwow what the button + # looks like, so we do not show the button but still show explanations. + if (ref($button_spec) ne 'ARRAY') { + my $button_name_string + = $self->direction_string($button, 'button', 'string'); + # FIXME strip FirstInFile from $button to get active icon file? + $about .= + (($self->get_conf('ICONS') && + $self->get_conf('ACTIVE_ICONS')->{$button}) ? + &{$self->formatting_function('format_button_icon_img')}($self, + $button_name_string, $self->get_conf('ACTIVE_ICONS')->{$button}) + : ' [' . $self->direction_string($button, 'text') . '] '); + } + $about .= "\n"; + my $button_name + = $self->direction_string($button, 'button'); + $about .= +' '.$self->html_attribute_class('td', ['name-direction-about']).'>' + .$button_name." + + + +"; + } + + $about .= < + +

    +EOT + $about .= $self->convert_tree($self->gdt(' where the @strong{ Example } assumes that the current position is at @strong{ Subsubsection One-Two-Three } of a document of the following structure:')) . "\n"; + +# where the Example assumes that the current position +# is at Subsubsection One-Two-Three of a document of +# the following structure: + $about .= < + +

      +EOT + my $non_breaking_space = $self->get_info('non_breaking_space'); + # TRANSLATORS: example name of section for section 1 + $about .= '
    • 1. ' . $self->convert_tree($self->gdt('Section One')) . "\n" . +"
        \n" . + # TRANSLATORS: example name of section for section 1.1 +'
      • 1.1 ' . $self->convert_tree($self->gdt('Subsection One-One')) . "\n"; + $about .= < +
      • ...
      • +
      +
    • +EOT + # TRANSLATORS: example name of section for section 1.2 + $about .= '
    • 1.2 ' . $self->convert_tree($self->gdt('Subsection One-Two')) . "\n" . +"
        \n" . + # TRANSLATORS: example name of section for section 1.2.1 +'
      • 1.2.1 ' . $self->convert_tree($self->gdt('Subsubsection One-Two-One')) . "
      • \n" . + # TRANSLATORS: example name of section for section 1.2.2 +'
      • 1.2.2 ' . $self->convert_tree($self->gdt('Subsubsection One-Two-Two')) . "
      • \n" . + # TRANSLATORS: example name of section for section 1.2.3 +'
      • 1.2.3 ' . $self->convert_tree($self->gdt('Subsubsection One-Two-Three')) + . " $non_breaking_space $non_breaking_space\n" +. +' <== ' . $self->convert_tree($self->gdt('Current Position')) . "
      • \n" . + # TRANSLATORS: example name of section for section 1.2.3 +'
      • 1.2.4 ' . $self->convert_tree($self->gdt('Subsubsection One-Two-Four')) . "
      • \n" . +"
      \n" . +"
    • \n" . + # TRANSLATORS: example name of section for section 1.3 +'
    • 1.3 ' . $self->convert_tree($self->gdt('Subsection One-Three')) . "\n"; + $about .= < +
    • ...
    • +
    + +EOT + # TRANSLATORS: example name of section for section 1.4 + $about .= '
  • 1.4 ' . $self->convert_tree($self->gdt('Subsection One-Four')) . "
  • \n"; + + $about .= < + + +EOT + return $about; +} + +sub _default_format_special_body_contents($$$) +{ + my $self = shift; + my $special_type = shift; + my $element = shift; + + return &{$self->formatting_function('format_contents')}($self, 'contents'); +} + +sub _default_format_special_body_shortcontents($$$) +{ + my $self = shift; + my $special_type = shift; + my $element = shift; + + return &{$self->formatting_function('format_contents')}($self, 'shortcontents'); +} + +sub _default_format_special_body_footnotes($$$) +{ + my $self = shift; + my $special_type = shift; + my $element = shift; + + return &{$self->formatting_function('format_footnotes_sequence')}($self); +} + +sub _do_jslicenses_file { + my $self = shift; + my $destination_directory = shift; + + my $setting = $self->get_conf('JS_WEBLABELS'); + my $path = $self->get_conf('JS_WEBLABELS_FILE'); + + # Possible settings: + # 'generate' - create file at JS_WEBLABELS_FILE + # 'reference' - reference file at JS_WEBLABELS_FILE but do not create it + # 'omit' - do nothing + return if (!$setting or $setting ne 'generate'); + + my $doctype = $self->get_conf('DOCTYPE'); + my $root_html_element_attributes = $self->_root_html_element_attributes_string(); + my $a = $doctype . "\n" . +"".'jslicense labels + +
    ' . $self->convert_tree($self->gdt('Button')) . " ' . $self->convert_tree($self->gdt('Name')) . " ' . $self->convert_tree($self->gdt('Go to')) . " ' . $self->convert_tree($self->gdt('From 1.2.3 go to')) . "
    ".$self->direction_string($button, 'description')."".$self->direction_string($button, 'example')."
    +'; + + my $jslicenses = $self->get_info('jslicenses'); + foreach my $category (sort(keys %$jslicenses)) { + foreach my $file (sort(keys %{$jslicenses->{$category}})) { + my $file_info = $jslicenses->{$category}->{$file}; + $a .= "\n"; + $a .= '\n"; + $a .= '\n"; + $a .= '\n"; + $a .= "\n"; + } + } + + $a .= "
    $file$file_info->[0]$file_info->[2]
    \n\n"; + + if (File::Spec->file_name_is_absolute($path) or $path =~ /^[A-Za-z]*:/) { + $self->document_warn($self, sprintf( +__("cannot use absolute path or URL `%s' for JS_WEBLABELS_FILE when generating web labels file"), $path)); + return; + } + my $license_file = File::Spec->catdir($destination_directory, + $path); + # sequence of bytes + my ($licence_file_path, $path_encoding) + = $self->encoded_output_file_name($license_file); + my ($fh, $error_message_licence_file) + = Texinfo::Common::output_files_open_out( + $self->output_files_information(), $self, + $licence_file_path); + if (defined($fh)) { + print $fh $a; + Texinfo::Common::output_files_register_closed( + $self->output_files_information(), $licence_file_path); + if (!close ($fh)) { + $self->document_error($self, + sprintf(__("error on closing %s: %s"), + $license_file, $!)); + } + } else { + $self->document_error($self, + sprintf(__("could not open %s for writing: %s"), + $license_file, $error_message_licence_file)); + } +} + +# FIXME the file opening should be done in main program, only +# the formatting should be done in customization function. Frames +# are deprecated in HTML, however, and therefore there is no point +# in investing time in better code to produce them. +sub _default_format_frame_files($$) +{ + my $self = shift; + my $destination_directory = shift; + + my $frame_file = $self->{'frame_pages_filenames'}->{'Frame'}; + my $frame_outfile; + if (defined($destination_directory) and $destination_directory ne '') { + $frame_outfile = File::Spec->catfile($destination_directory, + $frame_file); + } else { + $frame_outfile = $frame_file; + } + + my $toc_frame_file = $self->{'frame_pages_filenames'}->{'Toc_Frame'}; + my $toc_frame_outfile; + if (defined($destination_directory) and $destination_directory ne '') { + $toc_frame_outfile = File::Spec->catfile($destination_directory, + $toc_frame_file); + } else { + $toc_frame_outfile = $toc_frame_file; + } + # sequence of bytes + my ($frame_file_path, $frame_path_encoding) + = $self->encoded_output_file_name($frame_outfile); + my ($frame_fh, $error_message_frame) = Texinfo::Common::output_files_open_out( + $self->output_files_information(), $self, $frame_file_path); + if (defined($frame_fh)) { + my $doctype = $self->get_conf('FRAMESET_DOCTYPE'); + my $root_html_element_attributes = $self->_root_html_element_attributes_string(); + my $top_file = ''; + my $top_element = $self->global_direction_element('Top'); + if ($top_element) { + $top_file = $top_element->{'structure'}->{'unit_filename'}; + } + my $title = $self->{'title_string'}; + print $frame_fh < +$title + + + + + +EOT + + Texinfo::Common::output_files_register_closed( + $self->output_files_information(), $frame_file_path); + if (!close ($frame_fh)) { + $self->document_error($self, + sprintf(__("error on closing frame file %s: %s"), + $frame_outfile, $!)); + return 0; + } + } else { + $self->document_error($self, + sprintf(__("could not open %s for writing: %s"), + $frame_outfile, $error_message_frame)); + return 0; + } + # sequence of bytes + my ($toc_frame_path, $toc_frame_path_encoding) + = $self->encoded_output_file_name($toc_frame_outfile); + my ($toc_frame_fh, $toc_frame_error_message) + = Texinfo::Common::output_files_open_out( + $self->output_files_information(), $self, + $toc_frame_path); + if (defined($toc_frame_fh)) { + + # this is needed to collect CSS rules. + $self->{'current_filename'} = $toc_frame_file; + my $shortcontents = + &{$self->formatting_function('format_contents')}($self, 'shortcontents'); + $shortcontents =~ s/\bhref=/target="main" href=/g; + my $header = &{$self->formatting_function('format_begin_file')}($self, + $toc_frame_file, undef); + print $toc_frame_fh $header; + print $toc_frame_fh '

    Content

    '."\n"; + print $toc_frame_fh $shortcontents; + print $toc_frame_fh "\n"; + + $self->{'current_filename'} = undef; + + Texinfo::Common::output_files_register_closed( + $self->output_files_information(), $toc_frame_path); + if (!close ($toc_frame_fh)) { + $self->document_error($self, + sprintf(__("error on closing TOC frame file %s: %s"), + $toc_frame_outfile, $!)); + return 0; + } + } else { + $self->document_error($self, + sprintf(__("could not open %s for writing: %s"), + $toc_frame_outfile, $toc_frame_error_message)); + return 0; + } + return 1; +} + +sub _has_contents_or_shortcontents($) +{ + my $self = shift; + my $global_commands = $self->get_info('global_commands'); + foreach my $cmdname ('contents', 'shortcontents') { + if ($global_commands and $global_commands->{$cmdname}) { + return 1; + } + } + return 0; +} + +# to be called before starting conversion. +# NOTE not called directly by convert_tree, which means that convert_tree +# needs to be called from a converter which would have had this function +# called already. +sub _initialize_output_state($) +{ + my $self = shift; + + # for diverse API used in conversion + $self->{'shared_conversion_state'} = {}; + + $self->{'associated_inline_content'} = {}; + + # even if there is no actual file, this is needed if the API is used. + $self->{'files_information'} = {}; + + # Needed for CSS gathering, even if nothing related to CSS is output + $self->{'document_global_context_css'} = {}; + $self->{'file_css'} = {}; + + # direction strings + foreach my $string_type (keys(%default_translated_directions_strings)) { + # those will be determined from translatable strings + $self->{'directions_strings'}->{$string_type} = {}; + }; + + # targets and directions + + # used for diverse elements: tree units, indices, footnotes, special + # elements, contents elements... + $self->{'targets'} = {}; + $self->{'seen_ids'} = {}; + + # to avoid infinite recursions when a section refers to itself, possibly + # indirectly + $self->{'referred_command_stack'} = []; + + # for directions to special elements, only filled if special + # elements are actually used. + $self->{'special_elements_directions'} = {}; + + # for footnotes + $self->{'special_targets'} = {'footnote_location' => {}}; + + # other + + $self->{'check_htmlxref_already_warned'} = {} + if ($self->get_conf('CHECK_HTMLXREF')); +} + +sub convert($$) +{ + my $self = shift; + my $root = shift; + + my $result = ''; + + $self->_initialize_output_state(); + + # needed for CSS rules gathering + $self->{'current_filename'} = ''; + + # call before _prepare_conversion_tree_units, which calls _translate_names. + # Some information is not available yet. + $self->_reset_info(); + + my ($tree_units, $special_elements) + = $self->_prepare_conversion_tree_units($root, undef, undef); + + $self->_prepare_index_entries(); + $self->_prepare_footnotes(); + + # title + $self->{'title_titlepage'} + = &{$self->formatting_function('format_title_titlepage')}($self); + + # complete information should be available. + $self->_reset_info(); + + if (!defined($tree_units)) { + print STDERR "\nC NO UNIT\n" if ($self->get_conf('DEBUG')); + $result = $self->_convert($root, 'convert no unit'); + $result .= &{$self->formatting_function('format_footnotes_segment')}($self); + } else { + my $unit_nr = 0; + # TODO there is no rule before the footnotes special element in + # case of separate footnotes in the default formatting style. + # Not sure if it is an issue. + foreach my $tree_unit (@$tree_units, @$special_elements) { + print STDERR "\nC UNIT $unit_nr\n" if ($self->get_conf('DEBUG')); + my $tree_unit_text = $self->_convert($tree_unit, "convert unit $unit_nr"); + $result .= $tree_unit_text; + $unit_nr++; + } + } + + return $result; +} + +# This is called from the main program on the converter. +sub output_internal_links($) +{ + my $self = shift; + my $out_string = ''; + if ($self->{'tree_units'}) { + foreach my $tree_unit (@{$self->{'tree_units'}}) { + my $text; + my $href; + my $command = $self->tree_unit_element_command($tree_unit); + if (defined($command)) { + # Use '' for filename, to force a filename in href. + $href = $self->command_href($command, ''); + my $tree = $self->command_text($command, 'tree'); + if ($tree) { + $text = Texinfo::Convert::Text::convert_to_text($tree, + {Texinfo::Convert::Text::copy_options_for_convert_text($self)}); + } + } + if (defined($href) or defined($text)) { + $out_string .= $href if (defined($href)); + $out_string .= "\ttoc\t"; + $out_string .= $text if (defined($text)); + $out_string .= "\n"; + } + } + } + my $index_entries_by_letter = $self->get_info('index_entries_by_letter'); + if ($index_entries_by_letter) { + my %options = Texinfo::Convert::Text::copy_options_for_convert_text($self); + foreach my $index_name (sort(keys (%{$index_entries_by_letter}))) { + foreach my $letter_entry (@{$index_entries_by_letter->{$index_name}}) { + foreach my $index_entry (@{$letter_entry->{'entries'}}) { + my $main_entry_element = $index_entry->{'entry_element'}; + my $in_code + = $self->{'indices_information'}->{$index_entry->{'index_name'}} + ->{'in_code'}; + # does not refer to the document + next if ($main_entry_element->{'extra'} + and ($main_entry_element->{'extra'}->{'seeentry'} + or $main_entry_element->{'extra'}->{'seealso'})); + my $href; + $href = $self->command_href($main_entry_element, ''); + # Obtain term by converting to text + my $converter_options = {%options}; + $converter_options->{'code'} = $in_code; + my $entry_reference_content_element + = Texinfo::Common::index_content_element($main_entry_element); + my @contents = ($entry_reference_content_element); + my $subentries_tree + = $self->comma_index_subentries_tree($main_entry_element); + if (defined($subentries_tree)) { + push @contents, @{$subentries_tree->{'contents'}}; + } + my $index_term = Texinfo::Convert::Text::convert_to_text( + {'contents' => \@contents}, $converter_options); + if (defined($index_term) and $index_term =~ /\S/) { + $out_string .= $href if (defined($href)); + $out_string .= "\t$index_name\t"; + $out_string .= $index_term; + $out_string .= "\n"; + } + } + } + } + } + if ($out_string ne '') { + return $out_string; + } else { + return undef; + } +} + +sub run_stage_handlers($$$) +{ + my $converter = shift; + my $root = shift; + my $stage = shift; + + my $stage_handlers = Texinfo::Config::GNUT_get_stage_handlers(); + return 0 if (!defined($stage_handlers->{$stage})); + + my @sorted_priorities = sort keys(%{$stage_handlers->{$stage}}); + foreach my $priority (@sorted_priorities) { + foreach my $handler (@{$stage_handlers->{$stage}->{$priority}}) { + if ($converter->get_conf('DEBUG')) { + print STDERR "HANDLER($stage) , priority $priority: $handler\n"; + } + my $status = &{$handler}($converter, $root, $stage); + if ($status != 0) { + if ($status < 0) { + $converter->document_error($converter, + sprintf(__("handler %s of stage %s priority %s failed"), + $handler, $stage, $priority)); + } else { + # the handler is supposed to have output an error message + # already if $status > 0 + if ($converter->get_conf('VERBOSE') or $converter->get_conf('DEBUG')) { + print STDERR "Handler $handler of $stage($priority) failed\n"; + } + } + return $status; + } + } + } + return 0; +} + +sub _reset_info() +{ + my $self = shift; + + # reset to be sure that there is no stale information + $self->{'converter_info'} = {}; + foreach my $converter_info (keys(%available_converter_info)) { + if (exists($self->{$converter_info})) { + if (ref($self->{$converter_info}) eq '') { + # for scalar, use references in case it may change + $self->{'converter_info'}->{$converter_info} = \$self->{$converter_info}; + } else { + $self->{'converter_info'}->{$converter_info} = $self->{$converter_info}; + } + } + } +} + +# Main function for outputting a manual in HTML. +# $SELF is the output converter object of class Texinfo::Convert::HTML (this +# module), and $ROOT is the Texinfo tree from the parser. +sub output($$) +{ + my $self = shift; + my $root = shift; + + $self->{'current_filename'} = undef; + + $self->_initialize_output_state(); + + # no splitting when writing to the null device or to stdout or returning + # a string + if (defined($self->get_conf('OUTFILE')) + and ($Texinfo::Common::null_device_file{$self->get_conf('OUTFILE')} + or $self->get_conf('OUTFILE') eq '-' + or $self->get_conf('OUTFILE') eq '')) { + $self->force_conf('SPLIT', 0); + $self->force_conf('MONOLITHIC', 1); + $self->force_conf('FRAMES', 0); + } + if ($self->get_conf('SPLIT')) { + $self->set_conf('NODE_FILES', 1); + } + if ($self->get_conf('FRAMES')) { + $self->set_conf('shortcontents', 1); + } + $self->set_conf('EXTERNAL_CROSSREF_SPLIT', $self->get_conf('SPLIT')); + + if (not defined($self->get_conf('NODE_NAME_IN_INDEX'))) { + $self->set_conf('NODE_NAME_IN_INDEX', $self->get_conf('USE_NODES')); + } + + my $handler_fatal_error_level = $self->get_conf('HANDLER_FATAL_ERROR_LEVEL'); + + if ($self->get_conf('HTML_MATH') + and $self->get_conf('HTML_MATH') eq 'mathjax') { + # See https://www.gnu.org/licenses/javascript-labels.html + # + # The link to the source for mathjax does not strictly follow the advice + # there: instead we link to instructions for obtaining the full source in + # its preferred form of modification. + + my $mathjax_script = $self->get_conf('MATHJAX_SCRIPT'); + if (! defined($mathjax_script)) { + $mathjax_script = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js'; + $self->set_conf('MATHJAX_SCRIPT', $mathjax_script); + } + + my $mathjax_source = $self->get_conf('MATHJAX_SOURCE'); + if (! defined($mathjax_source)) { + $mathjax_source = 'http://docs.mathjax.org/en/latest/web/hosting.html#getting-mathjax-via-git'; + $self->set_conf('MATHJAX_SOURCE', $mathjax_source); + } + } + + if ($self->get_conf('HTML_MATH') + and not defined($self->get_conf('CONVERT_TO_LATEX_IN_MATH'))) { + $self->set_conf('CONVERT_TO_LATEX_IN_MATH', 1); + } + + if ($self->get_conf('CONVERT_TO_LATEX_IN_MATH')) { + $self->{'options_latex_math'} + = { Texinfo::Convert::LaTeX::copy_options_for_convert_to_latex_math($self) }; + } + + if ($self->get_conf('NO_TOP_NODE_OUTPUT') + and not defined($self->get_conf('SHOW_TITLE'))) { + $self->set_conf('SHOW_TITLE', 1); + } + + # set information, to have some information for run_stage_handlers. + # Some information is not available yet. + $self->_reset_info(); + + my $setup_status = $self->run_stage_handlers($root, 'setup'); + return undef unless ($setup_status < $handler_fatal_error_level + and $setup_status > -$handler_fatal_error_level); + + # the configuration has potentially been modified for + # this output file especially. Set a corresponding initial + # configuration. + $self->{'output_init_conf'} = { %{$self->{'conf'}} }; + + $self->{'jslicenses'} = {}; + if ($self->get_conf('HTML_MATH') + and $self->get_conf('HTML_MATH') eq 'mathjax') { + # See https://www.gnu.org/licenses/javascript-labels.html + + my $mathjax_script = $self->get_conf('MATHJAX_SCRIPT'); + my $mathjax_source = $self->get_conf('MATHJAX_SOURCE'); + + $self->{'jslicenses'}->{'mathjax'} = { + $mathjax_script => + [ 'Apache License, Version 2.0.', + 'https://www.apache.org/licenses/LICENSE-2.0', + $mathjax_source ]}; + } + if ($self->get_conf('INFO_JS_DIR')) { + $self->{'jslicenses'}->{'infojs'} = { + 'js/info.js' => + [ 'GNU General Public License 3.0 or later', + 'http://www.gnu.org/licenses/gpl-3.0.html', + 'js/info.js' ], + 'js/modernizr.js' => + [ 'Expat', + 'http://www.jclark.com/xml/copying.txt', + 'js/modernizr.js' ]}; + } + $self->_prepare_css(); + + # this sets OUTFILE, to be used if not split, but also + # 'destination_directory' and 'output_filename' that are useful when split. + my ($output_file, $destination_directory, $output_filename, + $document_name) = $self->determine_files_and_directory(); + my ($encoded_destination_directory, $dir_encoding) + = $self->encoded_output_file_name($destination_directory); + my $succeeded + = $self->create_destination_directory($encoded_destination_directory, + $destination_directory); + return undef unless $succeeded; + + # set for init files + $self->{'document_name'} = $document_name; + $self->{'destination_directory'} = $destination_directory; + + # set information, to have it available for the conversions below, + # in translate_names called by _prepare_conversion_tree_units and in + # titles formatting. + # Some information is not available yet. + $self->_reset_info(); + + # Get the list of "elements" to be processed, i.e. nodes or sections. + # This should return undef if called on a tree without node or sections. + my ($tree_units, $special_elements) + = $self->_prepare_conversion_tree_units($root, $destination_directory, + $document_name); + + Texinfo::Structuring::split_pages($tree_units, $self->get_conf('SPLIT')); + + # determine file names associated with the different pages, and setup + # the counters for special element pages. + my %files_source_info; + if ($output_file ne '') { + %files_source_info = + $self->_html_set_pages_files($tree_units, $special_elements, $output_file, + $destination_directory, $output_filename, $document_name); + } + + $self->_prepare_contents_elements(); + + # do tree units directions. + Texinfo::Structuring::elements_directions($self, $self->{'labels'}, $tree_units); + + # do element directions related to files. + # FIXME do it here or before? Here it means that + # PrevFile and NextFile can be set. + Texinfo::Structuring::elements_file_directions($tree_units); + + # Associate the special elements that have no page with the main page. + # This may only happen if not split. + if ($special_elements + and $tree_units and $tree_units->[0] + and $tree_units->[0]->{'structure'} + and defined($tree_units->[0]->{'structure'}->{'unit_filename'})) { + foreach my $special_element (@$special_elements) { + if (!defined($special_element->{'structure'}->{'unit_filename'})) { + $special_element->{'structure'}->{'unit_filename'} + = $tree_units->[0]->{'structure'}->{'unit_filename'}; + $self->{'file_counters'}->{$special_element->{'structure'}->{'unit_filename'}}++; + } + } + } + + $self->_prepare_index_entries(); + $self->_prepare_footnotes(); + + # only in HTML, not in Texinfo::Convert::Converter + $self->{'elements_in_file_count'} = {}; + # condition could also be based on $output_file ne '' + if ($self->{'file_counters'}) { + # 'file_counters' is dynamic, decreased when the element is encountered + # 'elements_in_file_count' is not modified afterwards + foreach my $filename (keys(%{$self->{'file_counters'}})) { + $self->{'elements_in_file_count'}->{$filename} + = $self->{'file_counters'}->{$filename}; + } + } + + # set information, to have it ready for + # run_stage_handlers. Some information is not available yet. + $self->_reset_info(); + + my $structure_status = $self->run_stage_handlers($root, 'structure'); + return undef unless ($structure_status < $handler_fatal_error_level + and $structure_status > -$handler_fatal_error_level); + + my $default_document_language = $self->get_conf('documentlanguage'); + + $self->set_global_document_commands('preamble', ['documentlanguage']); + + my $preamble_document_language = $self->get_conf('documentlanguage'); + $self->set_conf('BODYTEXT', + 'lang="' . $preamble_document_language . '"'); + + if ($default_document_language ne $preamble_document_language) { + $self->_translate_names(); + } + + # prepare title. fulltitle uses more possibility than simpletitle for + # title, including @-commands found in @titlepage only. Therefore + # simpletitle is more in line with what makeinfo in C does. + my $fulltitle; + foreach my $fulltitle_command('settitle', 'title', 'shorttitlepage', 'top') { + if ($self->{'global_commands'}->{$fulltitle_command}) { + my $command = $self->{'global_commands'}->{$fulltitle_command}; + next if (!$command->{'args'} + or (!$command->{'args'}->[0]->{'contents'} + or ($command->{'extra'} + and $command->{'extra'}->{'missing_argument'}))); + print STDERR "Using $fulltitle_command as title\n" + if ($self->get_conf('DEBUG')); + $fulltitle = {'contents' => $command->{'args'}->[0]->{'contents'}}; + last; + } + } + if (!$fulltitle and $self->{'global_commands'}->{'titlefont'} + and $self->{'global_commands'}->{'titlefont'}->[0]->{'args'} + and defined($self->{'global_commands'}->{'titlefont'}->[0]->{'args'}->[0]) + and $self->{'global_commands'}->{'titlefont'}->[0] + ->{'args'}->[0]->{'contents'} + and @{$self->{'global_commands'}->{'titlefont'}->[0] + ->{'args'}->[0]->{'contents'}}) { + $fulltitle = $self->{'global_commands'}->{'titlefont'}->[0]; + } + # prepare simpletitle + foreach my $simpletitle_command ('settitle', 'shorttitlepage') { + if ($self->{'global_commands'}->{$simpletitle_command}) { + my $command = $self->{'global_commands'}->{$simpletitle_command}; + next if (!$command->{'args'} + or !$command->{'args'}->[0]->{'contents'} + or ($command->{'extra'} + and $command->{'extra'}->{'missing_argument'})); + $self->{'simpletitle_tree'} = + {'contents' => $command->{'args'}->[0]->{'contents'}}; + $self->{'simpletitle_command_name'} = $simpletitle_command; + last; + } + } + + my $html_title_string; + if ($fulltitle) { + $self->{'title_tree'} = $fulltitle; + $html_title_string = $self->convert_tree_new_formatting_context( + {'type' => '_string', 'contents' => [$self->{'title_tree'}]}, + 'title_string'); + } + if (!defined($html_title_string) or $html_title_string !~ /\S/) { + my $default_title = $self->gdt('Untitled Document'); + $self->{'title_tree'} = $default_title; + $self->{'title_string'} = $self->convert_tree_new_formatting_context( + {'type' => '_string', 'contents' => [$self->{'title_tree'}]}, + 'title_string'); + $self->line_warn($self, __( + "must specify a title with a title command or \@top"), + {'file_name' => $self->{'parser_info'}->{'input_file_name'}}); + } else { + $self->{'title_string'} = $html_title_string; + } + + # copying comment + if ($self->{'global_commands'}->{'copying'}) { + my $copying_comment = Texinfo::Convert::Text::convert_to_text( + {'contents' => $self->{'global_commands'}->{'copying'}->{'contents'}}, + {Texinfo::Convert::Text::copy_options_for_convert_text($self)}); + if ($copying_comment ne '') { + $self->{'copying_comment'} + = &{$self->formatting_function('format_comment')}($self, $copying_comment); + } + } + $self->set_global_document_commands('before', ['documentlanguage']); + + if ($default_document_language ne $preamble_document_language) { + $self->_translate_names(); + } + + # documentdescription + if (defined($self->get_conf('documentdescription'))) { + $self->{'documentdescription_string'} + = $self->get_conf('documentdescription'); + } elsif ($self->{'global_commands'}->{'documentdescription'}) { + $self->{'documentdescription_string'} + = $self->convert_tree_new_formatting_context( + {'type' => '_string', + 'contents' => + $self->{'global_commands'}->{'documentdescription'}->{'contents'}}, + 'documentdescription'); + chomp($self->{'documentdescription_string'}); + } + + # set information, to have it ready for run_stage_handlers. + # Some information is not available yet. + $self->_reset_info(); + + my $init_status = $self->run_stage_handlers($root, 'init'); + return undef unless ($init_status < $handler_fatal_error_level + and $init_status > -$handler_fatal_error_level); + + if ($self->get_conf('FRAMES')) { + my $status = &{$self->formatting_function('format_frame_files')}($self, + $destination_directory); + return undef if (!$status); + } + + # determine first file name + if (!$tree_units + or !defined($tree_units->[0]->{'structure'}->{'unit_filename'})) { + # no page + if ($output_file ne '') { + my $no_page_output_filename; + my $no_page_out_filepath; + if ($self->get_conf('SPLIT')) { + $no_page_output_filename = $self->top_node_filename($document_name); + if (defined($destination_directory) and $destination_directory ne '') { + $no_page_out_filepath = File::Spec->catfile($destination_directory, + $no_page_output_filename); + } else { + $no_page_out_filepath = $no_page_output_filename; + } + } else { + $no_page_out_filepath = $output_file; + $no_page_output_filename = $output_filename; + } + $self->{'out_filepaths'}->{$no_page_output_filename} = $no_page_out_filepath; + + $self->{'current_filename'} = $no_page_output_filename; + } else { + $self->{'current_filename'} = $output_filename; + } + } else { + $self->{'current_filename'} + = $tree_units->[0]->{'structure'}->{'unit_filename'}; + } + # title + $self->{'title_titlepage'} + = &{$self->formatting_function('format_title_titlepage')}($self); + + # complete information should be available. + $self->_reset_info(); + + my $output = ''; + + if (!$tree_units or !$tree_units->[0]->{'structure'} + or !defined($tree_units->[0]->{'structure'}->{'unit_filename'})) { + my $fh; + my $encoded_no_page_out_filepath; + my $no_page_out_filepath; + if ($self->{'current_filename'} ne '' + and $self->{'out_filepaths'} + and defined($self->{'out_filepaths'}->{$self->{'current_filename'}})) { + my $path_encoding; + $no_page_out_filepath + = $self->{'out_filepaths'}->{$self->{'current_filename'}}; + ($encoded_no_page_out_filepath, $path_encoding) + = $self->encoded_output_file_name($no_page_out_filepath); + my $error_message; + ($fh, $error_message) = Texinfo::Common::output_files_open_out( + $self->output_files_information(), $self, + $encoded_no_page_out_filepath); + if (!$fh) { + $self->document_error($self, + sprintf(__("could not open %s for writing: %s"), + $no_page_out_filepath, $error_message)); + return undef; + } + } + my $body = ''; + if ($tree_units and @$tree_units) { + my $unit_nr = 0; + # TODO there is no rule before the footnotes special element in + # case of separate footnotes in the default formatting style. + # Not sure if it is an issue. + foreach my $tree_unit (@$tree_units, @$special_elements) { + print STDERR "\nUNIT NO-PAGE $unit_nr\n" if ($self->get_conf('DEBUG')); + my $tree_unit_text + = $self->_convert($tree_unit, "no-page output unit $unit_nr"); + $body .= $tree_unit_text; + $unit_nr++; + } + } else { + $body .= $self->get_info('title_titlepage'); + print STDERR "\nNO UNIT NO PAGE\n" if ($self->get_conf('DEBUG')); + $body .= $self->_convert($root, 'no-page output no unit'); + $body .= &{$self->formatting_function('format_footnotes_segment')}($self); + } + + # do end file first, in case it needs some CSS + my $footer = &{$self->formatting_function('format_end_file')}($self, + $output_filename); + my $header = &{$self->formatting_function('format_begin_file')}($self, + $output_filename, undef); + $output .= $self->write_or_return($header, $fh); + $output .= $self->write_or_return($body, $fh); + $output .= $self->write_or_return($footer, $fh); + + # NOTE do not close STDOUT now to avoid a perl warning. + if ($fh and $no_page_out_filepath ne '-') { + Texinfo::Common::output_files_register_closed( + $self->output_files_information(), $encoded_no_page_out_filepath); + if (!close($fh)) { + $self->document_error($self, + sprintf(__("error on closing %s: %s"), + $no_page_out_filepath, $!)); + } + } + $self->{'current_filename'} = undef; + return $output if ($output_file eq ''); + } else { + # output with pages + print STDERR "DO Elements with filenames\n" + if ($self->get_conf('DEBUG')); + my %files; + + my $unit_nr = -1; + # Now do the output, converting each tree units and special elements in turn + $special_elements = [] if (!defined($special_elements)); + foreach my $element (@$tree_units, @$special_elements) { + my $element_filename = $element->{'structure'}->{'unit_filename'}; + my $out_filepath = $self->{'out_filepaths'}->{$element_filename}; + $self->{'current_filename'} = $element_filename; + + $unit_nr++; + # First do the special pages, to avoid outputting these if they are + # empty. + my $special_element_content; + if (defined($element->{'type'}) + and $element->{'type'} eq 'special_element') { + print STDERR "\nUNIT SPECIAL\n" if ($self->get_conf('DEBUG')); + $special_element_content + .= $self->_convert($element, "output s-unit $unit_nr"); + if ($special_element_content eq '') { + $self->{'file_counters'}->{$element_filename}--; + next ; + } + } + + # convert body before header in case this affects the header + my $body = ''; + if (defined($special_element_content)) { + $body = $special_element_content; + } else { + print STDERR "\nUNIT $unit_nr\n" if ($self->get_conf('DEBUG')); + $body = $self->_convert($element, "output unit $unit_nr"); + } + + # register the element but do not print anything. Printing + # only when file_counters reach 0, to be sure that all the + # elements have been converted. + if (!exists($files{$element_filename})) { + $files{$element_filename} = {'first_element' => $element, + 'body' => ''}; + } + $files{$element_filename}->{'body'} .= $body; + $self->{'file_counters'}->{$element_filename}--; + if ($self->{'file_counters'}->{$element_filename} == 0) { + my $file_element = $files{$element_filename}->{'first_element'}; + my ($encoded_out_filepath, $path_encoding) + = $self->encoded_output_file_name($out_filepath); + my ($file_fh, $error_message) + = Texinfo::Common::output_files_open_out( + $self->output_files_information(), $self, + $encoded_out_filepath); + if (!$file_fh) { + $self->document_error($self, + sprintf(__("could not open %s for writing: %s"), + $out_filepath, $error_message)); + return undef; + } + # do end file first in case it requires some CSS + my $end_file = &{$self->formatting_function('format_end_file')}($self, + $element_filename); + print $file_fh "".&{$self->formatting_function('format_begin_file')}( + $self, $element_filename, $file_element); + print $file_fh "".$files{$element_filename}->{'body'}; + # end file + print $file_fh "". $end_file; + + # NOTE do not close STDOUT here to avoid a perl warning + if ($out_filepath ne '-') { + Texinfo::Common::output_files_register_closed( + $self->output_files_information(), $encoded_out_filepath); + if (!close($file_fh)) { + $self->document_error($self, + sprintf(__("error on closing %s: %s"), + $out_filepath, $!)); + return undef; + } + } + } + } + delete $self->{'current_filename'}; + if ($self->get_conf('INFO_JS_DIR')) { + my $jsdir = File::Spec->catdir($destination_directory, + $self->get_conf('INFO_JS_DIR')); + if (!-d $jsdir) { + if (-f $jsdir) { + $self->document_error($self, + sprintf(__("%s already exists but is not a directory"), $jsdir)); + } else { + mkdir $jsdir; + } + } + # Copy JS files. + if (-d $jsdir) { + if (!$self->get_conf('TEST')) { + my $jssrcdir; + if (!$Texinfo::ModulePath::texinfo_uninstalled) { + $jssrcdir = File::Spec->catdir( + $Texinfo::ModulePath::pkgdatadir, 'js'); + } else { + $jssrcdir = File::Spec->catdir( + $Texinfo::ModulePath::top_srcdir, 'js'); + } + for my $f ('info.js', 'modernizr.js', 'info.css') { + my $from = File::Spec->catfile($jssrcdir, $f); + + if (!copy($from, $jsdir)) { + $self->document_error($self, + sprintf(__("error on copying %s into %s"), $from, $jsdir)); + } + } + } else { + # create empty files for tests to keep results stable. + for my $f ('info.js', 'modernizr.js', 'info.css') { + my $filename = File::Spec->catfile($jsdir, $f); + if (!open (FH, '>', $filename)) { + $self->document_error($self, + sprintf(__("error on creating empty %s: %s"), + $filename, $!)); + } + if (!close(FH)) { + $self->document_error($self, + sprintf(__("error on closing empty %s: %s"), + $filename, $!)); + } + } + } + } + } + } + + my $jslicenses = $self->get_info('jslicenses'); + if ($jslicenses and scalar(%$jslicenses)) { + $self->_do_jslicenses_file($destination_directory); + } + + my $finish_status = $self->run_stage_handlers($root, 'finish'); + return undef unless ($finish_status < $handler_fatal_error_level + and $finish_status > -$handler_fatal_error_level); + + my $extension = ''; + $extension = '.'.$self->get_conf('EXTENSION') + if (defined($self->get_conf('EXTENSION')) + and $self->get_conf('EXTENSION') ne ''); + # do node redirection pages + $self->{'current_filename'} = undef; + if ($self->get_conf('NODE_FILES') + and $self->{'labels'} and $output_file ne '') { + my %redirection_filenames; + foreach my $label (sort(keys (%{$self->{'labels'}}))) { + my $target_element = $self->{'labels'}->{$label}; + my $label_element = Texinfo::Common::get_label_element($target_element); + my $label_contents = $label_element->{'contents'}; + my $target = $self->_get_target($target_element); + # filename may not be defined in case of an @anchor or similar in + # @titlepage, and @titlepage is not used. + my $filename = $self->command_filename($target_element); + my $node_filename; + # NOTE 'node_filename' is not used for Top, TOP_NODE_FILE_TARGET + # is. The other manual must use the same convention to get it + # right. We do not do 'node_filename' as a redirection file + # either. + if ($target_element->{'extra'} + and $target_element->{'extra'}->{'normalized'} + and $target_element->{'extra'}->{'normalized'} eq 'Top' + and defined($self->get_conf('TOP_NODE_FILE_TARGET'))) { + $node_filename = $self->get_conf('TOP_NODE_FILE_TARGET'); + } else { + $node_filename = $target->{'node_filename'}; + } + + if (defined($filename) and $node_filename ne $filename) { + my $redirection_filename + = $self->register_normalize_case_filename($node_filename); + # first condition finds conflict with tree elements + if ($self->{'elements_in_file_count'}->{$redirection_filename} + or $redirection_filenames{$redirection_filename}) { + $self->line_warn($self, + sprintf(__("\@%s `%s' file %s for redirection exists"), + $target_element->{'cmdname'}, + Texinfo::Convert::Texinfo::convert_to_texinfo({'contents' + => $label_contents}), + $redirection_filename), + $target_element->{'source_info'}); + my $file_source = $files_source_info{$redirection_filename}; + my $file_info_type = $file_source->{'file_info_type'}; + if ($file_info_type eq 'special_file' + or $file_info_type eq 'stand_in_file') { + my $name = $file_source->{'file_info_name'}; + if ($name eq 'non_split') { + # This cannot actually happen, as the @anchor/@node/@float + # with potentially conflicting name will also be in the + # non-split output document and therefore does not need + # a redirection. + $self->document_warn($self, + __("conflict with whole document file"), 1); + } elsif ($name eq 'Top') { + $self->document_warn($self, + __("conflict with Top file"), 1); + } elsif ($name eq 'user_defined') { + $self->document_warn($self, + __("conflict with user-defined file"), 1); + } elsif ($name eq 'unknown_node') { + $self->document_warn($self, + __("conflict with unknown node file"), 1); + } elsif ($name eq 'unknown') { + $self->document_warn($self, + __("conflict with file without known source"), 1); + } + } elsif ($file_info_type eq 'node') { + my $conflicting_node = $file_source->{'file_info_element'}; + $self->line_warn($self, + sprintf(__p('conflict of redirection file with file based on node name', + "conflict with \@%s `%s' file"), + $conflicting_node->{'cmdname'}, + Texinfo::Convert::Texinfo::convert_to_texinfo({'contents' + => $conflicting_node->{'args'}->[0]->{'contents'}}), + ), + $conflicting_node->{'source_info'}, 1); + } elsif ($file_info_type eq 'redirection') { + my $conflicting_node = $file_source->{'file_info_element'}; + my $conflicting_label_contents + = $file_source->{'file_info_label_contents'}; + $self->line_warn($self, + sprintf(__("conflict with \@%s `%s' redirection file"), + $conflicting_node->{'cmdname'}, + Texinfo::Convert::Texinfo::convert_to_texinfo({'contents' + => $conflicting_label_contents}), + ), + $conflicting_node->{'source_info'}, 1); + } elsif ($file_info_type eq 'section') { + my $conflicting_section = $file_source->{'file_info_element'}; + $self->line_warn($self, + sprintf(__p('conflict of redirection file with file based on section name', + "conflict with \@%s `%s' file"), + $conflicting_section->{'cmdname'}, + Texinfo::Convert::Texinfo::convert_to_texinfo({'contents' + => $conflicting_section->{'args'}->[0]->{'contents'}}), + ), + $conflicting_section->{'source_info'}, 1); + } elsif ($file_info_type eq 'special_element') { + my $special_element = $file_source->{'file_info_element'}; + my $element_variety + = $special_element->{'extra'}->{'special_element_variety'}; + $self->document_warn($self, + sprintf(__("conflict with %s special element"), + $element_variety), 1); + } + next; + } + $redirection_filenames{$redirection_filename} = $target_element; + $files_source_info{$redirection_filename} + = {'file_info_type' => 'redirection', + 'file_info_element' => $target_element, + 'file_info_label_contents' => $label_contents}; + my $redirection_page + = &{$self->formatting_function('format_node_redirection_page')}($self, + $target_element); + my $out_filename; + if (defined($destination_directory) and $destination_directory ne '') { + $out_filename = File::Spec->catfile($destination_directory, + $redirection_filename); + } else { + $out_filename = $redirection_filename; + } + my ($encoded_out_filename, $path_encoding) + = $self->encoded_output_file_name($out_filename); + my ($file_fh, $error_message) + = Texinfo::Common::output_files_open_out( + $self->output_files_information(), $self, + $encoded_out_filename); + if (!$file_fh) { + $self->document_error($self, sprintf(__( + "could not open %s for writing: %s"), + $out_filename, $error_message)); + } else { + print $file_fh $redirection_page; + Texinfo::Common::output_files_register_closed( + $self->output_files_information(), $encoded_out_filename); + if (!close ($file_fh)) { + $self->document_error($self, sprintf(__( + "error on closing redirection node file %s: %s"), + $out_filename, $!)); + return undef; + } + } + } + } + } + return undef; +} + +#my $characters_replaced_from_class_names = quotemeta('[](),~#:/\\@+=!;.,?* '); +# FIXME not clear what character should be allowed and which ones replaced +# besides space +my $characters_replaced_from_class_names = quotemeta(' '); +sub _protect_class_name($$) +{ + my $self = shift; + my $class_name = shift; + $class_name =~ s/[$characters_replaced_from_class_names]/-/g; + + # API info: using the API to allow for customization would be: + # return &{$self->formatting_function('format_protect_text')}($self, $class_name); + return _default_format_protect_text($self, $class_name); +} + +my $debug; # whether to print debugging output + +# Convert tree element $ELEMENT, and return HTML text for the output files. +sub _convert($$;$); +sub _convert($$;$) +{ + my $self = shift; + my $element = shift; + if (!defined($element)) { + cluck('BUG: _convert: element UNDEF'); + return ''; + } + # only used for debug + my $explanation = shift; + + # to help debug and trace + my $command_type = ''; + if ($element->{'cmdname'}) { + $command_type = "\@$element->{'cmdname'} "; + } + if (defined($element->{'type'})) { + $command_type .= $element->{'type'}; + } + + $debug = $self->get_conf('DEBUG') if !defined($debug); + # cache return value of get_conf for speed + + if ($debug) { + $explanation = 'NO EXPLANATION' if (!defined($explanation)); + my @contexts_names = map {defined($_->{'context_name'}) + ? $_->{'context_name'}: 'UNDEF'} + @{$self->{'document_context'}->[-1]->{'formatting_context'}}; + print STDERR "ELEMENT($explanation) (".join('|',@contexts_names)."), ->"; + print STDERR " cmd: $element->{'cmdname'}," if ($element->{'cmdname'}); + print STDERR " type: $element->{'type'}" if ($element->{'type'}); + my $text = $element->{'text'}; + if (defined($text)) { + $text =~ s/\n/\\n/; + print STDERR " text: $text"; + } + # uncomment to show perl objects + #print STDERR " $element (".join('|',@{$self->{'document_context'}->[-1]->{'formatting_context'}}).")"; + print STDERR "\n"; + } + + if (ref($element) ne 'HASH') { + cluck "_convert: tree element not a HASH\n"; + return ''; + } + + if (($element->{'type'} + and exists ($self->{'types_conversion'}->{$element->{'type'}}) + and !defined($self->{'types_conversion'}->{$element->{'type'}})) + or ($element->{'cmdname'} + and exists($self->{'commands_conversion'}->{$element->{'cmdname'}}) + and !defined($self->{'commands_conversion'}->{$element->{'cmdname'}}))) { + if ($debug) { + my $string = 'IGNORED'; + $string .= " \@$element->{'cmdname'}" if ($element->{'cmdname'}); + $string .= " $element->{'type'}" if ($element->{'type'}); + print STDERR "$string\n"; + } + return ''; + } + + # Process text + if (defined($element->{'text'})) { + # already converted to html, keep it as is + if ($element->{'type'} and $element->{'type'} eq '_converted') { + return $element->{'text'}; + } + if ($element->{'type'} and $element->{'type'} eq 'untranslated') { + my $translated = $self->gdt($element->{'text'}); + my $result = $self->_convert($translated, 'translated TEXT'); + return $result; + } + my $result = &{$self->{'types_conversion'}->{'text'}} ($self, + $element->{'type'}, + $element, + $element->{'text'}); + print STDERR "DO TEXT => `$result'\n" if $debug; + return $result; + } + + if ($element->{'extra'} and $element->{'extra'}->{'missing_argument'} + and (!$element->{'contents'} or !@{$element->{'contents'}})) { + print STDERR "MISSING_ARGUMENT\n" if $debug; + return ''; + } + + # commands like @deffnx have both a cmdname and a def_line type. It is + # better to consider them as a def_line type, as the whole point of the + # def_line type is to handle the same the def*x and def* line formatting. + if ($element->{'cmdname'} + and !(($element->{'type'} and $element->{'type'} eq 'def_line') + or ($element->{'type'} + and $element->{'type'} eq 'definfoenclose_command') + or ($element->{'type'} + and $element->{'type'} eq 'index_entry_command'))) { + my $command_name = $element->{'cmdname'}; + if ($root_commands{$command_name}) { + $self->{'current_root_command'} = $element; + } + if (exists($self->{'commands_conversion'}->{$command_name})) { + my $convert_to_latex; + if (exists($brace_commands{$command_name}) + and $brace_commands{$command_name} eq 'context') { + $self->_new_document_context($command_name); + } + if (exists($format_context_commands{$command_name})) { + push @{$self->{'document_context'}->[-1]->{'formatting_context'}}, + {'context_name' => '@'.$command_name}; + } + if (exists($block_commands{$command_name})) { + push @{$self->{'document_context'}->[-1]->{'block_commands'}}, + $command_name; + } + if (exists ($composition_context_commands{$command_name})) { + push @{$self->{'document_context'}->[-1]->{'composition_context'}}, + $command_name; + } + if ($pre_class_commands{$command_name}) { + push @{$self->{'document_context'}->[-1]->{'preformatted_classes'}}, + $pre_class_commands{$command_name}; + } + if ($format_raw_commands{$command_name}) { + $self->{'document_context'}->[-1]->{'raw'}++; + } elsif ($command_name eq 'verbatim') { + $self->{'document_context'}->[-1]->{'verbatim'}++; + } + if ($brace_code_commands{$command_name} or + $preformatted_code_commands{$command_name}) { + push @{$self->{'document_context'}->[-1]->{'monospace'}}, 1; + } elsif ($brace_commands{$command_name} + and $brace_commands{$command_name} eq 'style_no_code') { + push @{$self->{'document_context'}->[-1]->{'monospace'}}, 0; + } elsif ($upper_case_commands{$command_name}) { + $self->{'document_context'}->[-1]->{'formatting_context'}->[-1] + ->{'upper_case'}++; + } elsif ($math_commands{$command_name}) { + $self->{'document_context'}->[-1]->{'math'}++; + $convert_to_latex = 1 if ($self->get_conf('CONVERT_TO_LATEX_IN_MATH')); + } + if ($command_name eq 'verb') { + $self->{'document_context'}->[-1]->{'formatting_context'}->[-1] + ->{'space_protected'}++; + } elsif ($command_name eq 'w') { + $self->{'document_context'}->[-1]->{'formatting_context'}->[-1] + ->{'no_break'}++; + } + my $result = ''; + if (defined($self->{'commands_open'}->{$command_name})) { + $result .= &{$self->{'commands_open'}->{$command_name}}($self, + $command_name, $element); + } + my $content_formatted = ''; + if ($element->{'contents'}) { + if ($convert_to_latex) { + $content_formatted + = Texinfo::Convert::LaTeX::convert_to_latex_math(undef, + {'contents' => $element->{'contents'}}, + $self->{'options_latex_math'}); + } else { + my $content_idx = 0; + foreach my $content (@{$element->{'contents'}}) { + $content_formatted + .= _convert($self, $content, "$command_type c[$content_idx]"); + $content_idx++; + } + } + } + my $args_formatted; + if ($brace_commands{$command_name} + or ($line_commands{$command_name} + and $line_commands{$command_name} eq 'line') + or (($command_name eq 'item' or $command_name eq 'itemx') + and ($element->{'parent'}->{'type'} + and $element->{'parent'}->{'type'} eq 'table_term')) + or ($command_name eq 'quotation' + or $command_name eq 'smallquotation') + or $command_name eq 'float' + or $command_name eq 'cartouche') { + $args_formatted = []; + if ($element->{'args'}) { + my @args_specification; + @args_specification = @{$default_commands_args{$command_name}} + if (defined($default_commands_args{$command_name})); + my $arg_idx = 0; + foreach my $arg (@{$element->{'args'}}) { + my $arg_spec = shift @args_specification; + $arg_spec = ['normal'] if (!defined($arg_spec)); + my $arg_formatted = {'tree' => $arg}; + foreach my $arg_type (@$arg_spec) { + my $explanation = "$command_type A[$arg_idx]$arg_type"; + if ($arg_type eq 'normal') { + if ($convert_to_latex) { + $arg_formatted->{'normal'} + = Texinfo::Convert::LaTeX::convert_to_latex_math(undef, $arg, + $self->{'options_latex_math'}); + } else { + $arg_formatted->{'normal'} = $self->_convert($arg, $explanation); + } + } elsif ($arg_type eq 'monospace') { + push @{$self->{'document_context'}->[-1]->{'monospace'}}, 1; + $arg_formatted->{$arg_type} = $self->_convert($arg, $explanation); + pop @{$self->{'document_context'}->[-1]->{'monospace'}}; + } elsif ($arg_type eq 'string') { + $self->_new_document_context($command_type); + $self->{'document_context'}->[-1]->{'string'}++; + $arg_formatted->{$arg_type} = $self->_convert($arg, $explanation); + $self->_pop_document_context(); + } elsif ($arg_type eq 'monospacestring') { + $self->_new_document_context($command_type); + $self->{'document_context'}->[-1]->{'monospace'}->[-1] = 1; + $self->{'document_context'}->[-1]->{'string'}++; + $arg_formatted->{$arg_type} = $self->_convert($arg, $explanation); + $self->_pop_document_context(); + } elsif ($arg_type eq 'monospacetext') { + $arg_formatted->{$arg_type} + = Texinfo::Convert::Text::convert_to_text($arg, + {'code' => 1, + Texinfo::Convert::Text::copy_options_for_convert_text($self)}); + } elsif ($arg_type eq 'filenametext') { + # Always use encoded characters for file names + $arg_formatted->{$arg_type} + = Texinfo::Convert::Text::convert_to_text($arg, + {'code' => 1, + Texinfo::Convert::Text::copy_options_for_convert_text($self, 1)}); + } elsif ($arg_type eq 'url') { + # set the encoding to UTF-8 to always have a string that is suitable + # for percent encoding. + my $text_conversion_options = {'code' => 1, + Texinfo::Convert::Text::copy_options_for_convert_text($self, 1)}; + $text_conversion_options->{'enabled_encoding'} = 'utf-8'; + $arg_formatted->{$arg_type} + = Texinfo::Convert::Text::convert_to_text($arg, + $text_conversion_options); + } elsif ($arg_type eq 'raw') { + $self->{'document_context'}->[-1]->{'raw'}++; + $arg_formatted->{$arg_type} = $self->_convert($arg, $explanation); + $self->{'document_context'}->[-1]->{'raw'}--; + } + } + push @$args_formatted, $arg_formatted; + $arg_idx++; + } + } + } + if (exists ($composition_context_commands{$command_name})) { + pop @{$self->{'document_context'}->[-1]->{'composition_context'}}; + } + if ($pre_class_commands{$command_name}) { + pop @{$self->{'document_context'}->[-1]->{'preformatted_classes'}}; + } + if ($preformatted_code_commands{$command_name} + or ($brace_commands{$command_name} + and $brace_commands{$command_name} eq 'style_no_code') + or $brace_code_commands{$command_name}) { + pop @{$self->{'document_context'}->[-1]->{'monospace'}}; + } elsif ($upper_case_commands{$command_name}) { + $self->{'document_context'}->[-1]->{'formatting_context'}->[-1] + ->{'upper_case'}--; + } elsif ($math_commands{$command_name}) { + $self->{'document_context'}->[-1]->{'math'}--; + } + if ($command_name eq 'verb') { + $self->{'document_context'}->[-1]->{'formatting_context'}->[-1] + ->{'space_protected'}--; + } elsif ($command_name eq 'w') { + $self->{'document_context'}->[-1]->{'formatting_context'}->[-1] + ->{'no_break'}--; + } + if ($format_raw_commands{$command_name}) { + $self->{'document_context'}->[-1]->{'raw'}--; + } elsif ($command_name eq 'verbatim') { + $self->{'document_context'}->[-1]->{'verbatim'}--; + } + if (exists($block_commands{$command_name})) { + pop @{$self->{'document_context'}->[-1]->{'block_commands'}}; + } + if (exists($format_context_commands{$command_name})) { + pop @{$self->{'document_context'}->[-1]->{'formatting_context'}}; + } + if (exists($brace_commands{$command_name}) + and $brace_commands{$command_name} eq 'context') { + $self->_pop_document_context(); + } + + if ($element->{'cmdname'} eq 'node') { + $self->{'current_node'} = $element; + } + # args are formatted, now format the command itself + if ($args_formatted) { + if (!defined($self->{'commands_conversion'}->{$command_name})) { + print STDERR "No command_conversion for $command_name\n"; + } else { + $result .= &{$self->{'commands_conversion'}->{$command_name}}($self, + $command_name, $element, $args_formatted, $content_formatted); + } + } else { + $result .= &{$self->{'commands_conversion'}->{$command_name}}($self, + $command_name, $element, undef, $content_formatted); + } + if ($command_name eq 'documentlanguage') { + $self->_translate_names(); + } + return $result; + } else { + print STDERR "Command not converted: $command_name\n" + if ($self->get_conf('VERBOSE') or $self->get_conf('DEBUG')); + return ''; + } + if ($root_commands{$command_name}) { + delete $self->{'current_root_command'}; + } + } elsif ($element->{'type'}) { + + my $result = ''; + my $type_name = $element->{'type'}; + if (defined($self->{'types_open'}->{$type_name})) { + $result .= &{$self->{'types_open'}->{$type_name}}($self, + $type_name, $element); + } + if ($type_name eq 'paragraph') { + $self->{'document_context'}->[-1]->{'formatting_context'}->[-1] + ->{'paragraph_number'}++; + } elsif ($type_name eq 'preformatted' + or $type_name eq 'rawpreformatted') { + $self->{'document_context'}->[-1]->{'formatting_context'}->[-1] + ->{'preformatted_number'}++; + } elsif ($type_name eq 'unit' + or $type_name eq 'special_element') { + $self->{'current_root_element'} = $element; + } elsif ($self->{'pre_class_types'}->{$type_name}) { + push @{$self->{'document_context'}->[-1]->{'preformatted_classes'}}, + $self->{'pre_class_types'}->{$type_name}; + push @{$self->{'document_context'}->[-1]->{'composition_context'}}, + $type_name; + } + + if ($self->{'code_types'}->{$type_name}) { + push @{$self->{'document_context'}->[-1]->{'monospace'}}, 1; + } + if ($type_name eq '_string') { + $self->{'document_context'}->[-1]->{'string'}++; + } + + my $content_formatted = ''; + if ($type_name eq 'definfoenclose_command') { + if ($element->{'args'}) { + $content_formatted = $self->_convert($element->{'args'}->[0]); + } + } elsif ($element->{'contents'}) { + my $content_idx = 0; + foreach my $content (@{$element->{'contents'}}) { + $content_formatted + .= _convert($self, $content, "$command_type c[$content_idx]"); + $content_idx++; + } + } + + if (exists($self->{'types_conversion'}->{$type_name})) { + $result .= &{$self->{'types_conversion'}->{$type_name}} ($self, + $type_name, + $element, + $content_formatted); + } elsif (defined($content_formatted)) { + $result .= $content_formatted; + } + if ($self->{'code_types'}->{$type_name}) { + pop @{$self->{'document_context'}->[-1]->{'monospace'}}; + } + if ($type_name eq '_string') { + $self->{'document_context'}->[-1]->{'string'}--; + } + if ($type_name eq 'unit' or $type_name eq 'special_element') { + delete $self->{'current_root_element'}; + } elsif ($self->{'pre_class_types'}->{$type_name}) { + pop @{$self->{'document_context'}->[-1]->{'preformatted_classes'}}; + pop @{$self->{'document_context'}->[-1]->{'composition_context'}}; + } + print STDERR "DO type ($type_name) => `$result'\n" if $debug; + return $result; + # no type, no cmdname, but contents. + } elsif ($element->{'contents'}) { + # this happens inside accents, for section/node names, for @images. + my $content_formatted = ''; + my $i = 0; + foreach my $content (@{$element->{'contents'}}) { + $content_formatted .= $self->_convert($content, "$command_type C[$i]"); + $i++; + } + print STDERR "UNNAMED HOLDER => `$content_formatted'\n" if $debug; + return $content_formatted; + } else { + print STDERR "UNNAMED empty\n" if $debug; + if ($self->{'types_conversion'}->{''}) { + return &{$self->{'types_conversion'}->{''}} ($self, $element); + } else { + return ''; + } + } + print STDERR "DEBUG: HERE!($element)\n"; +} + +sub _set_variables_texi2html($) +{ + my $options = shift; + my @texi2html_options = ( + # added hopefully temporarily to be able to validate with W3C validator + #['DOCTYPE', ''], + #['DOCTYPE', ''], + ['FORMAT_MENU', 'menu'], + ['NO_USE_SETFILENAME', 1], + ['USE_SETFILENAME_EXTENSION', 0], + ['footnotestyle', 'separate'], + ['CONTENTS_OUTPUT_LOCATION', 'separate_element'], + ['FORCE', 1], + ['AVOID_MENU_REDUNDANCY', 1], + ['USE_ACCESSKEY', 0], + ['NODE_NAME_IN_MENU', 0], + ['SHORT_TOC_LINK_TO_TOC', 0], + ['SHOW_TITLE', 1], + ['USE_UP_NODE_FOR_ELEMENT_UP', 1], + ['USE_REL_REV', 0], + ['USE_LINKS', 0], + ['USE_NODES', 0], + ['SPLIT', ''], + ['PROGRAM_NAME_IN_FOOTER', 1], + ['PROGRAM_NAME_IN_ABOUT', 1], + ['HEADER_IN_TABLE', 1], + ['MENU_ENTRY_COLON', ''], + ['INDEX_ENTRY_COLON', ''], + ['DO_ABOUT', undef], + ['CHAPTER_HEADER_LEVEL', 1], + ['BIG_RULE', '
    '], + ['FOOTNOTE_END_HEADER_LEVEL', 3], + ['FOOTNOTE_SEPARATE_HEADER_LEVEL', 1], + ['SECTION_BUTTONS', ['FastBack', 'Back', 'Up', 'Forward', 'FastForward', + ' ', ' ', ' ', ' ', + 'Top', 'Contents', 'Index', 'About' ]], + ['TOP_BUTTONS', ['Back', 'Forward', ' ', + 'Contents', 'Index', 'About']], + + ['MISC_BUTTONS', [ 'Top', 'Contents', 'Index', 'About' ]], + ['CHAPTER_BUTTONS', [ 'FastBack', 'FastForward', ' ', + ' ', ' ', ' ', ' ', + 'Top', 'Contents', 'Index', 'About', ]], + ['SECTION_FOOTER_BUTTONS', [ 'FastBack', 'FirstInFileBack', 'FirstInFileUp', + 'Forward', 'FastForward' ]], + ['CHAPTER_FOOTER_BUTTONS', [ 'FastBack', 'FastForward', ' ', + ' ', ' ', ' ', ' ', + 'Top', 'Contents', 'Index', 'About', ]], + ['NODE_FOOTER_BUTTONS', [ 'FastBack', 'Back', + 'Up', 'Forward', 'FastForward', + ' ', ' ', ' ', ' ', + 'Top', 'Contents', 'Index', 'About' ]], + ); + foreach my $option (@texi2html_options) { + #no warnings 'once'; + #$defaults{$option->[0]} = $option->[1]; + $options->{$option->[0]} = $option->[1]; + } +} + +1; + +# The documentation of the customization API is in the customization_api +# Texinfo manual. POD format is not suitable for such a documentation, because +# of the module documentation style, the language limitations, and also because +# the customization API involves multiple modules as well as the main program. + +__END__ +# Automatically generated from maintain/template.pod + +=head1 NAME + +Texinfo::Convert::HTML - Convert Texinfo tree to HTML + +=head1 SYNOPSIS + + my $converter + = Texinfo::Convert::HTML->converter({'parser' => $parser}); + + $converter->output($tree); + $converter->convert($tree); + $converter->convert_tree($tree); + $converter->output_internal_links(); # HTML only + +=head1 NOTES + +The Texinfo Perl module main purpose is to be used in C to convert +Texinfo to other formats. There is no promise of API stability. + +=head1 DESCRIPTION + +Texinfo::Convert::HTML converts a Texinfo tree to HTML. + +=head1 METHODS + +=over + +=item $converter = Texinfo::Convert::HTML->converter($options) + +Initialize converter from Texinfo to HTML. + +The I<$options> hash reference holds options for the converter. In +this option hash reference a L +may be associated with the I key. The other options +are Texinfo customization options and a few other options that can +be passed to the converter. Most of the customization options are described in +the Texinfo manual. Those customization options, when appropriate, override +the document content. The parser should not be available directly anymore +after getting the associated information. + +See L for more information. + +=item $converter->output($tree) + +Convert a Texinfo tree I<$tree> and output the result in files as +described in the Texinfo manual. + +=item $result = $converter->convert($tree) + +Convert a Texinfo tree I<$tree> and return the resulting output. + +=item $result = $converter->convert_tree($tree) + +Convert a Texinfo tree portion I<$tree> and return the resulting +output. This function does not try to output a full document but only +portions. For a full document use C. + +=item $result = $converter->output_internal_links() +X> + +Returns text representing the links in the document. The format should +follow the C<--internal-links> option of the C +specification. This is only supported in (and relevant for) HTML. + +=back + +=head1 AUTHOR + +Patrice Dumas, Epertusus@free.frE + +=head1 COPYRIGHT AND LICENSE + +Copyright 2010- Free Software Foundation, Inc. See the source file for +all copyright years. + +This library 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. + +=cut diff --git a/gettext-tools/tests/xgettext-perl-stackovfl-5 b/gettext-tools/tests/xgettext-perl-stackovfl-5 new file mode 100755 index 000000000..efd27e56a --- /dev/null +++ b/gettext-tools/tests/xgettext-perl-stackovfl-5 @@ -0,0 +1,13 @@ +#!/bin/sh +. "${srcdir=.}/init.sh"; path_prepend_ . ../src + +# Test of Perl support: nesting depth check shouldn't prevent parsing a large +# but mostly flat input file. + +: ${XGETTEXT=xgettext} +LANGUAGE= LC_ALL=C ${XGETTEXT} --no-location -d xg-pl-so-5.tmp "$wabs_srcdir"/testdata/xg-pl-so-5.pl 2>xg-pl-so-5.err +result=$? +cat xg-pl-so-5.err +test $result = 0 || Exit 1 + +exit 0