# HTML.pm: output tree as HTML. # # Copyright 2011-2020 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 . # # # There are three categories of formatting functions that can be # replaced by the user, together with the hash with default functions: # * command tree element formatting functions registered in # %default_commands_conversion # * type tree element (element without @-command) formatting # functions, registered in %default_types_conversion # * other formatting functions, registered in # %default_formatting_references # # The functions used in the default case for all the functions # that may be replaced should not have side effects, 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. # # In most formatting functions, the case where $self->in_string() is # true should be handled explicitely and the simplest formatting should be # done in that case, without any HTML element such that the result # can be in an attribute or in a comment. # # FIXME: there is already a case with a side effect, with the # variable $html_menu_entry_index. # # 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 strict; use Texinfo::Convert::Converter; use Texinfo::Common; use Texinfo::Convert::Texinfo; use Texinfo::Convert::Text; use Texinfo::Convert::Unicode; use Texinfo::Convert::NodeNameNormalization; use Carp qw(cluck confess); use File::Copy qw(copy); require Exporter; use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); @ISA = qw(Exporter Texinfo::Convert::Converter); %EXPORT_TAGS = ( 'all' => [ qw( convert convert_tree output output_internal_links ) ] ); @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); @EXPORT = qw( ); $VERSION = '6.8'; # misc commands that are of use for formatting. my %formatting_misc_commands = %Texinfo::Convert::Text::formatting_misc_commands; my %no_brace_commands = %Texinfo::Common::no_brace_commands; my %accent_commands = %Texinfo::Common::accent_commands; my %misc_commands = %Texinfo::Common::misc_commands; my %sectioning_commands = %Texinfo::Common::sectioning_commands; my %def_commands = %Texinfo::Common::def_commands; my %ref_commands = %Texinfo::Common::ref_commands; my %brace_commands = %Texinfo::Common::brace_commands; my %block_commands = %Texinfo::Common::block_commands; my %menu_commands = %Texinfo::Common::menu_commands; my %root_commands = %Texinfo::Common::root_commands; my %preformatted_commands = %Texinfo::Common::preformatted_commands; my %math_commands = %Texinfo::Common::math_commands; my %explained_commands = %Texinfo::Common::explained_commands; my %item_container_commands = %Texinfo::Common::item_container_commands; my %raw_commands = %Texinfo::Common::raw_commands; my %format_raw_commands = %Texinfo::Common::format_raw_commands; my %inline_commands = %Texinfo::Common::inline_commands; my %inline_format_commands = %Texinfo::Common::inline_format_commands; my %code_style_commands = %Texinfo::Common::code_style_commands; my %regular_font_style_commands = %Texinfo::Common::regular_font_style_commands; my %preformatted_code_commands = %Texinfo::Common::preformatted_code_commands; my %default_index_commands = %Texinfo::Common::default_index_commands; my %style_commands = %Texinfo::Common::style_commands; my %align_commands = %Texinfo::Common::align_commands; my %region_commands = %Texinfo::Common::region_commands; my %context_brace_commands = %Texinfo::Common::context_brace_commands; my %letter_no_arg_commands = %Texinfo::Common::letter_no_arg_commands; my %small_alias; for my $cmd ('example', 'display', 'format', 'lisp', 'quotation', 'indentedblock') { $small_alias{'small'.$cmd} = $cmd; }; foreach my $def_command (keys(%def_commands)) { $formatting_misc_commands{$def_command} = 1 if ($misc_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 %composition_context_commands = (%preformatted_commands, %root_commands, %menu_commands, %align_commands); $composition_context_commands{'float'} = 1; my %pre_class_types; # FIXME allow customization? (also in DocBook) my %upper_case_commands = ( 'sc' => 1 ); 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 $pre_class_types{$context} or ($menu_commands{$context} 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_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 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 $filename = shift; if (defined($self->{'elements_in_file_count'}->{$filename})) { return $self->{'elements_in_file_count'}->{$filename}; } return undef; } sub top_format($) { my $self = shift; return $self->{'document_context'}->[-1]->{'formats'}->[-1]; } sub commands_stack($) { my $self = shift; return @{$self->{'document_context'}->[-1]->{'commands'}}; } 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 ($align_commands{$context}) { return $context; } else { return undef; } } # $COMMAND should be a tree element which is a possible target of a link. # # Returns a hash that may have these keys set: # 'target': A unique string representing the target. Used as argument to # 'id' attribute. # 'node_filename', 'section_filename', # 'misc_filename', 'filename'. Possibly others. # # Some functions cache their results in these hashes. 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_commands{$command->{'cmdname'}} and !$root_commands{$command->{'cmdname'}}) { $target = $self->_new_sectioning_command_target($command); } return $target; } # API for the elements formatting 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; } } # Return href target for linking to this command sub command_target($$) { my $self = shift; my $command = shift; if ($command->{'extra'} and $command->{'extra'}->{'associated_node'}) { $command = $command->{'extra'}->{'associated_node'}; } my $target = $self->_get_target($command); if ($target) { return $target->{'target'}; } else { return undef; } } sub command_filename($$) { my $self = shift; my $command = shift; my $target = $self->_get_target($command); if ($target) { if (defined($target->{'filename'})) { return $target->{'filename'}; } my ($element, $root_command) = $self->_get_element($command, 1); if (defined($root_command)) { $target->{'root_command'} = $root_command; } if (defined($element)) { $target->{'element'} = $element; $target->{'filename'} = $element->{'filename'}; return $element->{'filename'}; } } return undef; } sub command_element($$) { my $self = shift; my $command = shift; my $target = $self->_get_target($command); if ($target) { $self->command_filename($command); return $target->{'element'}; } return undef; } sub command_element_command($$) { my $self = shift; my $command = shift; my ($element, $root_command) = $self->_get_element($command); #my $element = $self->command_element($command); if ($element and $element->{'extra'}) { return $element->{'extra'}->{'element_command'}; } return undef; } sub element_command($$) { my $self = shift; my $element = shift; if ($element and $element->{'extra'}) { if ($element->{'extra'}->{'element_command'}) { return $element->{'extra'}->{'element_command'}; } elsif ($element->{'extra'}->{'special_element'}) { return $element; } } return undef; } sub command_node($$) { my $self = shift; my $command = shift; my $target = $self->_get_target($command); if ($target) { $self->command_filename($command); my $root_command = $target->{'root_command'}; if (defined($root_command)) { if ($root_command->{'cmdname'} and $root_command->{'cmdname'} eq 'node') { return $root_command; } if ($root_command->{'extra'} and $root_command->{'extra'}->{'associated_node'}) { return $root_command->{'extra'}->{'associated_node'}; } } } return undef; } # Return string for linking to $COMMAND with sub command_href($$;$$) { my $self = shift; my $command = shift; my $filename = shift; my $link_command = shift; $filename = $self->{'current_filename'} if (!defined($filename)); if ($command->{'manual_content'}) { return $self->_external_node_href($command, $filename, $link_command); } my $target = $self->command_target($command); 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->{'elements'} and $self->{'elements'}->[0] and defined($self->{'elements'}->[0]->{'filename'})) { # In that case use the first page. $target_filename = $self->{'elements'}->[0]->{'filename'}; } } if (defined($target_filename)) { if (!defined($filename) or $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_element_command = $self->command_element_command($command); if (defined($filename) and defined($command_element_command) and ($command_element_command eq $command or (defined($command_element_command->{'extra'}) and defined($command_element_command->{'extra'}->{'associated_section'}) and $command_element_command->{'extra'}->{'associated_section'} eq $command))) { my $count_elements_in_file = $self->count_elements_in_filename($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_element_name = ( 'contents' => 'Contents', 'shortcontents' => 'Overview', 'summarycontents' => 'Overview', ); # 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 $filename = shift; my $href; my $name = $contents_command_element_name{$contents_or_shortcontents}; my $target = $self->command_contents_target($command, $contents_or_shortcontents); my $target_element = $self->special_element($name); my $target_filename; # !defined happens when called as convert() and not output() if (defined($target_element)) { $target_filename = $self->command_filename($target_element); } if (defined($target_filename) and (!defined($filename) or $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( $tree, $command->{'cmdname'}); return $result; } } my $target = $self->_get_target($command); if ($target) { my $explanation; $explanation = "command_text \@$command->{'cmdname'}" if ($command->{'cmdname'}); if (defined($target->{$type})) { return $target->{$type}; } my $tree; if (!$target->{'tree'}) { if ($command->{'extra'} and $command->{'extra'}->{'special_element'}) { my $special_element = $command->{'extra'}->{'special_element'}; $tree = $self->get_conf('SPECIAL_ELEMENTS_NAME')->{$special_element}; $explanation = "command_text $special_element"; } elsif ($command->{'cmdname'} and ($command->{'cmdname'} eq 'node' or $command->{'cmdname'} eq 'anchor')) { $tree = {'type' => '_code', 'contents' => $command->{'extra'}->{'node_content'}}; } elsif ($command->{'cmdname'} and ($command->{'cmdname'} eq 'float')) { $tree = $self->_float_type_number($command); } elsif ($command->{'extra'}->{'missing_argument'}) { if ($type eq 'tree' or $type eq 'tree_nonumber') { return {}; } else { return ''; } } else { if (!$command->{'args'}->[0]->{'contents'}) { cluck "No misc_content: " .Texinfo::Common::_print_current($command); } if (defined($command->{'number'}) and ($self->get_conf('NUMBER_SECTIONS') or !defined($self->get_conf('NUMBER_SECTIONS')))) { if ($command->{'cmdname'} eq 'appendix' and $command->{'level'} == 1) { $tree = $self->gdt('Appendix {number} {section_title}', {'number' => {'text' => $command->{'number'}}, 'section_title' => {'contents' => $command->{'args'}->[0]->{'contents'}}}); } else { $tree = $self->gdt('{number} {section_title}', {'number' => {'text' => $command->{'number'}}, 'section_title' => {'contents' => $command->{'args'}->[0]->{'contents'}}}); } } else { $tree = {'contents' => [@{$command->{'args'}->[0]->{'contents'}}]}; } $target->{'tree_nonumber'} = {'contents' => $command->{'args'}->[0]->{'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'}); if ($type eq 'string') { $tree = {'type' => '_string', 'contents' => [$tree]}; } if ($type =~ /^(.*)_nonumber$/) { $tree = $target->{'tree_nonumber'} if (defined($target->{'tree_nonumber'})); } $self->{'ignore_notice'}++; $target->{$type} = $self->_convert($tree, $explanation); $self->{'ignore_notice'}--; pop @{$self->{'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; return $self->{'labels'}->{$label}; } sub special_element($$) { my $self = shift; my $type = shift; return $self->{'special_elements_types'}->{$type}; } sub global_element($$) { my $self = shift; my $type = shift; return $self->{'global_target_elements'}->{$type}; } # it is considered 'top' only if element corresponds to @top or # element is a node sub element_is_top($$) { my $self = shift; my $element = shift; return ($self->{'global_target_elements'}->{'Top'} and $self->{'global_target_elements'}->{'Top'} eq $element and $element->{'extra'} and (($element->{'extra'}->{'section'} and $element->{'extra'}->{'section'}->{'cmdname'} eq 'top') or ($element->{'extra'}->{'element_command'} and $element->{'extra'}->{'element_command'}->{'cmdname'} eq 'node'))); } sub default_formatting_function($$) { my $self = shift; my $format = shift; return $self->{'default_formatting_functions'}->{$format}; } # used for customization only (in t2h_singular.init) sub get_value($$) { my $self = shift; my $value = shift; if (defined($self->{'parser'}) and exists ($self->{'parser'}->{'values'}->{$value})) { return $self->{'parser'}->{'values'}->{$value}; } else { return undef; } } # 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; if (defined($context_string)) { $self->_new_document_context($context_string); } if ($multiple_pass) { $self->{'ignore_notice'}++; push @{$self->{'multiple_pass'}}, $multiple_pass; } my $result = $self->convert_tree($tree); if (defined($context_string)) { pop @{$self->{'document_context'}}; } if ($multiple_pass) { $self->{'ignore_notice'}--; pop @{$self->{'multiple_pass'}}; } return $result; } # see http://www.w3.org/TR/REC-html40/types.html#type-links my %BUTTONS_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', ); my %BUTTONS_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', '', ); my %BUTTONS_EXAMPLE = ( 'Top', '   ', 'Contents', '   ', 'Overview', '   ', 'Index', '   ', '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', '   ', 'First', '1.', 'Last', '1.2.4', 'NextFile', '   ', 'PrevFile', '   ', ); # insert here name of icon images for buttons # Icons are used, if ICONS and resp. value are set my %ACTIVE_ICONS = ( 'Top', '', 'Contents', '', 'Overview', '', 'Index', '', 'This', '', 'Back', '', 'FastBack', '', 'Prev', '', 'Up', '', 'Next', '', 'NodeUp', '', 'NodeNext', '', 'NodePrev', '', 'NodeForward', '', 'NodeBack', '', 'Forward', '', 'FastForward', '', 'About' , '', 'First', '', 'Last', '', 'NextFile', '', 'PrevFile', '', ' ', '', ); # insert here name of icon images for these, if button is inactive my %PASSIVE_ICONS = ( 'Top', '', 'Contents', '', 'Overview', '', 'Index', '', 'This', '', 'Back', '', 'FastBack', '', 'Prev', '', 'Up', '', 'Next', '', 'NodeUp', '', 'NodeNext', '', 'NodePrev', '', 'NodeForward', '', 'NodeBack', '', 'Forward', '', 'FastForward', '', 'About', '', 'First', '', 'Last', '', 'NextFile', '', 'PrevFile', '', ); my (%BUTTONS_TEXT, %BUTTONS_GOTO, %BUTTONS_NAME, %SPECIAL_ELEMENTS_NAME); my %defaults = ( 'ENABLE_ENCODING' => 0, 'FORMAT_MENU' => 'sectiontoc', 'OUTPUT_ENCODING_NAME' => 'utf-8', 'OUTFILE' => undef, 'SUBDIR' => undef, 'USE_NODES' => 1, 'USE_NODE_DIRECTIONS' => undef, 'CONTENTS_OUTPUT_LOCATION' => 'after_top', 'SPLIT' => 'node', # if set style is added in attribute. 'INLINE_CSS_STYLE' => 0, # if set, no css is used. 'NO_CSS' => 0, 'JS_WEBLABELS' => 'generate', 'JS_WEBLABELS_FILE' => 'js_licenses.html', # no clash with node name 'OPEN_QUOTE_SYMBOL' => '‘', 'CLOSE_QUOTE_SYMBOL' => '’', 'USE_ISO' => 1, 'TOP_FILE' => 'index.html', 'EXTENSION' => 'html', 'TOP_NODE_FILE_TARGET' => 'index.html', 'TRANSLITERATE_FILE_NAMES' => 1, 'USE_LINKS' => 1, 'USE_NUMERIC_ENTITY' => 1, 'ENABLE_ENCODING_USE_ENTITY' => 1, 'DATE_IN_HEADER' => 0, 'AVOID_MENU_REDUNDANCY' => 0, 'HEADERS' => 1, 'DO_ABOUT' => 0, 'USE_ACCESSKEY' => 1, 'USE_REL_REV' => 1, 'NODE_NAME_IN_MENU' => 1, 'NODE_NAME_IN_INDEX' => 1, 'XREF_USE_NODE_NAME_ARG' => undef, 'XREF_USE_FLOAT_LABEL' => 0, 'OVERVIEW_LINK_TO_TOC' => 1, 'COMPLEX_FORMAT_IN_TABLE' => 0, 'WORDS_IN_PAGE' => 300, # _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'], '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'], 'misc_elements_targets' => { 'Overview' => 'SEC_Overview', 'Contents' => 'SEC_Contents', 'Footnotes' => 'SEC_Foot', 'About' => 'SEC_About', 'Top' => 'SEC_Top', }, 'misc_pages_file_string' => { 'Contents' => '_toc', 'Overview' => '_ovr', 'Footnotes' => '_fot', 'About' => '_abt', }, 'frame_pages_file_string' => { 'Frame' => '_frame', 'Toc_Frame' => '_toc_frame', }, 'misc_elements_order' => ['Footnotes', 'Contents', 'Overview', 'About'], 'DOCTYPE' => '', 'FRAMESET_DOCTYPE' => '', 'DEFAULT_RULE' => '
', 'BIG_RULE' => '
', 'MENU_SYMBOL' => '•', 'MENU_ENTRY_COLON' => ':', 'INDEX_ENTRY_COLON' => ':', 'BODYTEXT' => undef, 'documentlanguage' => 'en', 'xrefautomaticsectiontitle' => 'on', 'SHOW_TITLE' => 1, 'SECTION_NAME_IN_TITLE' => 0, 'USE_TITLEPAGE_FOR_TITLE' => 0, 'MONOLITHIC' => 1, 'CHAPTER_HEADER_LEVEL' => 2, 'MAX_HEADER_LEVEL' => 4, 'FOOTNOTE_END_HEADER_LEVEL' => 4, 'FOOTNOTE_SEPARATE_HEADER_LEVEL' => 4, 'BUTTONS_REL' => \%BUTTONS_REL, 'BUTTONS_ACCESSKEY' => \%BUTTONS_ACCESSKEY, 'BUTTONS_EXAMPLE' => \%BUTTONS_EXAMPLE, 'BUTTONS_GOTO' => \%BUTTONS_GOTO, 'BUTTONS_NAME' => \%BUTTONS_NAME, 'BUTTONS_TEXT' => \%BUTTONS_TEXT, 'ACTIVE_ICONS' => \%ACTIVE_ICONS, 'PASSIVE_ICONS' => \%PASSIVE_ICONS, 'SPECIAL_ELEMENTS_NAME' => \%SPECIAL_ELEMENTS_NAME, 'SPECIAL_ELEMENTS_CLASS' => { 'About' => 'about', 'Contents' => 'contents', 'Overview' => 'shortcontents', 'Footnotes' => 'footnotes', }, 'jslicenses' => {}, # for outputting licences file 'jslicenses_element' => {}, # scripts used in current output file 'jslicenses_math' => {}, # MathJax scripts 'jslicenses_infojs' => {}, # info.js scripts 'element_math' => 0, # whether math has been seen in current file 'COPIABLE_ANCHORS' => 1, 'output_format' => 'html', ); foreach my $buttons ('CHAPTER_BUTTONS', 'MISC_BUTTONS', 'TOP_BUTTONS') { $defaults{$buttons} = [@{$defaults{'SECTION_BUTTONS'}}]; } foreach my $buttons ('SECTION_FOOTER_BUTTONS', 'CHAPTER_FOOTER_BUTTONS') { $defaults{$buttons} = [@{$defaults{'SECTION_FOOTER_BUTTONS'}}]; } 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_element (keys %{$defaults{'SPECIAL_ELEMENTS_CLASS'}}) { $global_and_special_directions{$special_element} = 1; } foreach my $hash (\%BUTTONS_REL, \%BUTTONS_ACCESSKEY, \%ACTIVE_ICONS, \%PASSIVE_ICONS) { foreach my $button (grep {not exists($global_and_special_directions{$_}) and $_ ne ' '} keys %$hash) { $hash->{'FirstInFile'.$button} = $hash->{$button}; } } sub _translate_names($) { my $self = shift; #print STDERR "encoding_name: ".$self->get_conf('OUTPUT_ENCODING_NAME')." documentlanguage: ".$self->get_conf('documentlanguage')."\n"; %BUTTONS_TEXT = ( 'Top', $self->gdt('Top'), 'Contents', $self->gdt('Contents'), 'Overview', $self->gdt('Overview'), 'Index', $self->gdt('Index'), ' ', '   ', 'This', $self->gdt('current'), 'Back', ' < ', 'FastBack', ' << ', 'Prev', $self->gdt('Prev'), 'Up', $self->gdt(' Up '), 'Next', $self->gdt('Next'), #'NodeUp', $self->gdt('Node up'), 'NodeUp', $self->gdt('Up'), #'NodeNext', $self->gdt('Next node'), 'NodeNext', $self->gdt('Next'), #'NodePrev', $self->gdt('Previous node'), 'NodePrev', $self->gdt('Previous'), 'NodeForward', $self->gdt('Forward node'), 'NodeBack', $self->gdt('Back node'), 'Forward', ' > ', 'FastForward', ' >> ', 'About', ' ? ', 'First', ' |< ', 'Last', ' >| ', 'NextFile', $self->gdt('Next file'), 'PrevFile', $self->gdt('Previous file'), ); foreach my $button (grep {not exists($global_and_special_directions{$_}) and $_ ne ' '} keys %BUTTONS_TEXT) { $BUTTONS_TEXT{'FirstInFile'.$button} = $BUTTONS_TEXT{$button}; } #%BUTTONS_TEXT = %NAVIGATION_TEXT; %BUTTONS_GOTO = ( 'Top', $self->gdt('Cover (top) of document'), 'Contents', $self->gdt('Table of contents'), 'Overview', $self->gdt('Short table of contents'), 'Index', $self->gdt('Index'), 'This', $self->gdt('Current section'), 'Back', $self->gdt('Previous section in reading order'), 'FastBack', $self->gdt('Beginning of this chapter or previous chapter'), 'Prev', $self->gdt('Previous section on same level'), 'Up', $self->gdt('Up section'), 'Next', $self->gdt('Next section on same level'), 'NodeUp', $self->gdt('Up node'), 'NodeNext', $self->gdt('Next node'), 'NodePrev', $self->gdt('Previous node'), 'NodeForward', $self->gdt('Next node in node reading order'), 'NodeBack', $self->gdt('Previous node in node reading order'), 'Forward', $self->gdt('Next section in reading order'), 'FastForward', $self->gdt('Next chapter'), 'About' , $self->gdt('About (help)'), 'First', $self->gdt('First section in reading order'), 'Last', $self->gdt('Last section in reading order'), 'NextFile', $self->gdt('Forward section in next file'), 'PrevFile', $self->gdt('Back section in previous file'), ); foreach my $button (grep {not exists($global_and_special_directions{$_}) and $_ ne ' '} keys %BUTTONS_GOTO) { $BUTTONS_GOTO{'FirstInFile'.$button} = $BUTTONS_GOTO{$button}; } %BUTTONS_NAME = ( 'Top', $self->gdt('Top'), 'Contents', $self->gdt('Contents'), 'Overview', $self->gdt('Overview'), 'Index', $self->gdt('Index'), ' ', ' ', 'This', $self->gdt('This'), 'Back', $self->gdt('Back'), 'FastBack', $self->gdt('FastBack'), 'Prev', $self->gdt('Prev'), 'Up', $self->gdt('Up'), 'Next', $self->gdt('Next'), 'NodeUp', $self->gdt('NodeUp'), 'NodeNext', $self->gdt('NodeNext'), 'NodePrev', $self->gdt('NodePrev'), 'NodeForward', $self->gdt('NodeForward'), 'NodeBack', $self->gdt('NodeBack'), 'Forward', $self->gdt('Forward'), 'FastForward', $self->gdt('FastForward'), 'About', $self->gdt('About'), 'First', $self->gdt('First'), 'Last', $self->gdt('Last'), 'NextFile', $self->gdt('NextFile'), 'PrevFile', $self->gdt('PrevFile'), ); foreach my $button (grep {not exists($global_and_special_directions{$_}) and $_ ne ' '} keys %BUTTONS_NAME) { $BUTTONS_NAME{'FirstInFile'.$button} = $BUTTONS_NAME{$button}; } %SPECIAL_ELEMENTS_NAME = ( 'About' => $self->gdt('About This Document'), 'Contents' => $self->gdt('Table of Contents'), 'Overview' => $self->gdt('Short Table of Contents'), 'Footnotes' => $self->gdt('Footnotes'), ); # delete the tree and formatted results for special elements # such that they are redone with the new tree when needed. foreach my $special_element (keys (%SPECIAL_ELEMENTS_NAME)) { if ($self->{'special_elements_types'}->{$special_element} and $self->{'targets'}->{$self->{'special_elements_types'}->{$special_element}}) { my $target = $self->{'targets'}->{$self->{'special_elements_types'}->{$special_element}}; foreach my $key ('text', 'string', 'tree') { delete $target->{$key}; } } } foreach my $hash (\%BUTTONS_TEXT, \%BUTTONS_GOTO, \%BUTTONS_NAME) { foreach my $button (keys (%$hash)) { if (ref($hash->{$button})) { $hash->{$button} = $self->convert_tree_new_formatting_context( $hash->{$button}, "button $button"); } } } if ($self->{'commands_translation'}) { my %translated_commands; foreach my $context ('normal', 'preformatted', 'string') { foreach my $command (keys(%{$self->{'commands_translation'}->{$context}})) { $translated_commands{$command} = 1; delete $self->{'commands_formatting'}->{$context}->{$command}; if (defined($self->{'commands_translation'}->{$context}->{$command})) { $self->{'commands_formatting'}->{$context}->{$command} = $self->gdt($self->{'commands_translation'}->{$context}->{$command}, undef, 'translated_text'); } } } foreach my $command(keys(%translated_commands)) { $self->_complete_commands_formatting($command); } } } sub converter_defaults($$) { my $self = shift; my $conf = shift; if (defined($conf->{'TEXI2HTML'})) { _set_variables_texi2html(); } return %defaults; } my $NO_BULLET_LIST_STYLE = 'list-style: none'; my $NO_BULLET_LIST_CLASS = 'no-bullet'; my $NO_BULLET_LIST_ATTRIBUTE = ' class="'.$NO_BULLET_LIST_CLASS.'"'; my $MENU_PRE_STYLE = 'font-family: serif'; my %css_map = ( "ul.$NO_BULLET_LIST_CLASS" => "$NO_BULLET_LIST_STYLE", 'pre.menu-comment' => "$MENU_PRE_STYLE", 'pre.menu-preformatted' => "$MENU_PRE_STYLE", 'a.summary-letter' => 'text-decoration: none', 'pre.display' => 'font-family: inherit', 'span.sansserif' => 'font-family: sans-serif; font-weight: normal', 'span.roman' => 'font-family: initial; font-weight: normal', 'span.nolinebreak' => 'white-space: nowrap', 'kbd' => 'font-style: oblique', # 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-anchor' => 'visibility: hidden; ' .'text-decoration: none; line-height: 0em', 'span:hover a.copiable-anchor' => 'visibility: visible', ); $css_map{'pre.format'} = $css_map{'pre.display'}; 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_alias{$preformatted_command}) { $pre_class_commands{$preformatted_command} = $small_alias{$preformatted_command}; } else { $pre_class_commands{$preformatted_command} = $preformatted_command; } } $pre_class_commands{'menu'} = 'menu-preformatted'; $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_map{"div.$indented_format"} = 'margin-left: 3.2em'; } delete $css_map{"div.lisp"}; # output as div.example instead $css_map{"blockquote.indentedblock"} = 'margin-right: 0em'; # 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, ); # default specification of arguments formatting my %default_commands_args = ( 'email' => [['monospace', 'monospacestring'], ['normal']], 'anchor' => [['monospacestring']], 'uref' => [['monospacestring'], ['normal'], ['normal']], 'url' => [['monospacestring'], ['normal'], ['normal']], 'printindex' => [[]], 'sp' => [[]], 'inforef' => [['monospace'],['normal'],['monospacetext']], 'xref' => [['monospace'],['normal'],['normal'],['monospacetext'],['normal']], 'pxref' => [['monospace'],['normal'],['normal'],['monospacetext'],['normal']], 'ref' => [['monospace'],['normal'],['normal'],['monospacetext'],['normal']], 'image' => [['monospacetext'],['monospacetext'],['monospacetext'],['string', 'normal'],['monospacetext']], # FIXME shouldn't it better not to convert if later ignored? 'inlinefmt' => [['monospacetext'],['normal']], 'inlinefmtifelse' => [['monospacetext'],['normal'],['normal']], 'inlineraw' => [['monospacetext'],['raw']], 'inlineifclear' => [['monospacetext'],['normal']], 'inlineifset' => [['monospacetext'],['normal']], 'item' => [[]], 'itemx' => [[]], ); foreach my $explained_command (keys(%explained_commands)) { $default_commands_args{$explained_command} = [['normal'], ['string']]; } # 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_commands_conversion($cmdname)}($self, $cmdname, $command, $content) my %default_commands_conversion; sub default_commands_conversion($$) { my $self = shift; my $command = shift; return $default_commands_conversion{$command}; } my %kept_misc_commands; my @informative_global_commands = ('contents', 'shortcontents', 'summarycontents', 'allowcodebreaks', 'documentlanguage', 'footnotestyle', 'documentencoding', 'xrefautomaticsectiontitle', 'deftypefnnewline'); # taken from global # 'documentencoding' # 'novalidate' foreach my $misc_command(@informative_global_commands, 'verbatiminclude', 'insertcopying', 'printindex', 'listoffloats', 'author', 'subtitle', 'title', keys(%default_index_commands), keys(%formatting_misc_commands)) { $kept_misc_commands{$misc_command} = 1; } sub converter_global_commands($) { return @informative_global_commands; } foreach my $misc_command (keys(%misc_commands)) { $default_commands_conversion{$misc_command} = undef unless ($kept_misc_commands{$misc_command}); } foreach my $ignored_brace_commands ('caption', 'shortcaption', 'hyphenation', 'sortas') { $default_commands_conversion{$ignored_brace_commands} = undef; } # commands that leads to advancing the paragraph number. This is mostly # used to determine the first line, in fact. my %advance_paragraph_count_commands; foreach my $command (keys(%block_commands)) { next if ($menu_commands{$command} or $block_commands{$command} eq 'raw'); $advance_paragraph_count_commands{$command} = 1; } foreach my $ignored_block_commands ('ignore', 'macro', 'rmacro', '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_commands_formatting; foreach my $command (keys(%{$Texinfo::Convert::Converter::default_xml_commands_formatting{'normal'}})) { $default_commands_formatting{'normal'}->{$command} = $Texinfo::Convert::Converter::default_xml_commands_formatting{'normal'}->{$command}; } $default_commands_formatting{'normal'}->{' '} = ' '; $default_commands_formatting{'normal'}->{"\t"} = ' '; $default_commands_formatting{'normal'}->{"\n"} = ' '; my %default_commands_translation; # possible example of use, right now not used, as the generic # translated command with gdt tree is used. #$default_commands_translation{'normal'}->{'error'} = 'error-->'; ## This is used to have gettext pick up the chain to be translated #if (0) { # my $not_existing; # $not_existing->gdt('error-->'); #} $default_commands_formatting{'normal'}->{'enddots'} = '...'; $default_commands_formatting{'preformatted'}->{'enddots'} = '...'; $default_commands_formatting{'normal'}->{'*'} = '
'; $default_commands_formatting{'preformatted'}->{'*'} = "\n"; 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->{'commands_formatting'}->{'preformatted'}->{$click_cmdname}) or ($self->in_string() and $self->{'commands_formatting'}->{'string'}->{$click_cmdname}) or ($self->{'commands_formatting'}->{'normal'}->{$click_cmdname})) { $cmdname = $click_cmdname; } } if ($self->in_upper_case() and $letter_no_arg_commands{$cmdname} and $self->{'commands_formatting'}->{'normal'}->{uc($cmdname)}) { $cmdname = uc($cmdname); } my $result; if ($self->{'translated_commands'}->{$cmdname}) { return $self->convert_tree( $self->gdt($self->{'translated_commands'}->{$cmdname})); } if ($self->in_preformatted() or $self->in_math()) { $result = $self->{'commands_formatting'}->{'preformatted'}->{$cmdname}; } elsif ($self->in_string()) { $result = $self->{'commands_formatting'}->{'string'}->{$cmdname}; } else { $result = $self->{'commands_formatting'}->{'normal'}->{$cmdname}; } return $result; } foreach my $command(keys(%{$default_commands_formatting{'normal'}})) { $default_commands_conversion{$command} = \&_convert_no_arg_command; } sub _convert_today_command($$$) { my $self = shift; my $cmdname = shift; my $command = shift; my $tree = $self->Texinfo::Common::expand_today(); return $self->convert_tree($tree); } $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_attribute_commands; $style_attribute_commands{'normal'} = { 'b' => 'b', 'cite' => 'cite', 'code' => 'code', 'command' => 'code', 'dfn' => 'em', 'emph' => 'em', 'env' => 'code', 'file' => 'samp', 'headitemfont' => 'b', # not really that, in fact it is # in rather than 'i' => 'i', 'slanted' => 'i', 'sansserif' => 'span class="sansserif"', 'kbd' => 'kbd', 'option' => 'samp', 'r' => 'span class="roman"', 'samp' => 'samp', 'sc' => 'small', 'strong' => 'strong', 'sub' => 'sub', 'sup' => 'sup', 't' => 'tt', 'var' => 'var', 'verb' => 'tt', }; my %style_commands_formatting; # this weird construct does like uniq, it avoids duplicates. # it is required since math is not in the %style_commands as it is # in context command. my @all_style_commands = keys %{{ map { $_ => 1 } (keys(%style_commands), keys(%{$style_attribute_commands{'normal'}}), 'dmn') }}; foreach my $command(@all_style_commands) { # default is no attribute. if ($style_attribute_commands{'normal'}->{$command}) { $style_commands_formatting{'normal'}->{$command}->{'attribute'} = $style_attribute_commands{'normal'}->{$command}; $style_commands_formatting{'preformatted'}->{$command}->{'attribute'} = $style_attribute_commands{'normal'}->{$command}; } if ($style_attribute_commands{'preformatted'}->{$command}) { $style_commands_formatting{'preformatted'}->{$command}->{'attribute'} = $style_attribute_commands{'preformatted'}->{$command}; } if ($quoted_style_commands{$command}) { foreach my $context ('normal', 'string', 'preformatted') { $style_commands_formatting{$context}->{$command}->{'quote'} = 1; } } $default_commands_conversion{$command} = \&_convert_style_command; } delete $style_commands_formatting{'preformatted'}->{'sc'}->{'attribute'}; delete $style_commands_formatting{'preformatted'}->{'sc'}; 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 = $args->[0]->{'normal'}; if (!defined($text)) { # happens with bogus @-commands without argument, like @strong something #cluck "text not defined in _convert_style_command"; return ''; } # handle the effect of kbdinputstyle if ($cmdname eq 'kbd' and $command->{'extra'} and $command->{'extra'}->{'code'}) { $cmdname = 'code'; } 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})) { if (defined($attribute_hash->{$cmdname}->{'attribute'})) { my ($style, $class, $attribute_text) = _parse_attribute ($attribute_hash->{$cmdname}->{'attribute'}); my $open = $self->_attribute_class($style, $class); if ($open ne '') { $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 = $args->[0]->{'normal'}; 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' => $command->{'type'}})); } $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->{'monospace'}; $mail_string = $mail_arg->{'monospacestring'}; } my $text = ''; if (defined($text_arg)) { $text = $text_arg->{'normal'}; } $text = $mail unless ($text ne ''); return $text if ($mail eq ''); if ($self->in_string()) { return "$mail_string ($text)"; } else { return "
$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 = Texinfo::Convert::NodeNameNormalization::normalize_node( {'contents' => $command->{'args'}->[0]->{'contents'}}); if ($args->[1] and defined($args->[1]->{'string'}) and $args->[1]->{'string'} =~ /\S/) { $with_explanation = 1; $explanation_string = $args->[1]->{'string'}; # Convert the expanation 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'} ); $self->{'explained_commands'}->{$cmdname}->{$normalized_type} = $command->{'args'}->[1]->{'contents'}; } elsif ($command->{'extra'}->{'explanation_contents'}) { if (@{$command->{'extra'}->{'explanation_contents'}}) { $explanation_string = $self->convert_tree_new_formatting_context( {'type' => '_string', 'contents' => $command->{'extra'}->{'explanation_contents'}}, $cmdname, $cmdname); } } elsif ($self->{'explained_commands'}->{$cmdname}->{$normalized_type}) { $explanation_string = $self->convert_tree_new_formatting_context( {'type' => '_string', 'contents' => $self->{'explained_commands'} ->{$cmdname}->{$normalized_type}}, $cmdname, $cmdname); $command->{'extra'}->{'explanation_contents'} = $self->{'explained_commands'}->{$cmdname}->{$normalized_type}; } else { # Avoid ever giving an explanation for this element. This prevents # infinite recursion for a recursively-defined acronym, when an # @acronym within the explanation could end up referring to the # containing @acronym. $command->{'extra'}->{'explanation_contents'} = []; } my $result = $args->[0]->{'normal'}; if (!$self->in_string()) { if (defined($explanation_string)) { $result = "<$cmdname title=\"$explanation_string\">".$result; } else { $result = "<$cmdname>".$result; } $result .= ""; } if ($with_explanation) { $result = $self->convert_tree($self->gdt('{explained_string} ({explanation})', {'explained_string' => {'type' => '_converted', 'text' => $result}, 'explanation' => {'type' => '_converted', 'text' => $explanation_result}})); } 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->{'multiple_pass'}} and !$self->in_string()) { return ""; } return ''; } $default_commands_conversion{'anchor'} = \&_convert_anchor_command; my $foot_num; my $foot_lines; my $NO_NUMBER_FOOTNOTE_SYMBOL = '*'; my $footid_base = 'FOOT'; my $docid_base = 'DOCF'; # to avoid duplicate names, use a prefix that cannot happen in anchors my $target_prefix = "t_h"; my %footnote_id_numbers; sub _convert_footnote_command($$$$) { my $self = shift; my $cmdname = shift; my $command = shift; my $args = shift; my $number_in_doc; $foot_num++; if ($self->get_conf('NUMBER_FOOTNOTES')) { $number_in_doc = $foot_num; } else { $number_in_doc = $NO_NUMBER_FOOTNOTE_SYMBOL; } return "($number_in_doc)" if ($self->in_string()); #print STDERR "FOOTNOTE $command\n"; my $footid = $self->command_target($command); # happens for bogus footnotes if (!defined($footid)) { return ''; } # ID for linking back to the main text from the footnote. my $docid = $footid; $docid =~ s/^$footid_base/$docid_base/; my $document_filename; my $footnote_filename; if ($self->get_conf('footnotestyle') eq 'separate') { $footnote_filename = $self->command_filename($command); $document_filename = $self->{'current_filename'}; $footnote_filename = '' if (!defined($footnote_filename)); $document_filename = '' if (!defined($document_filename)); if ($document_filename eq $footnote_filename) { $document_filename = $footnote_filename = ''; } } else { $document_filename = $footnote_filename = ''; } my $footnote_text; if ($args->[0]) { $footnote_text = $args->[0]->{'normal'}; } else { $footnote_text = ''; } chomp ($footnote_text); $footnote_text .= "\n"; if (@{$self->{'multiple_pass'}}) { $footid = $target_prefix.$self->{'multiple_pass'}->[-1].'_'.$footid.'_'.$foot_num; $docid = $target_prefix.$self->{'multiple_pass'}->[-1].'_'.$docid.'_'.$foot_num; } else { if (!defined($footnote_id_numbers{$footid})) { $footnote_id_numbers{$footid} = $foot_num; } else { # This should rarely happen, except for @footnote is @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; } } $foot_lines .= '
' . "($number_in_doc)
\n" . $footnote_text; my $footnote_number_text; if ($self->in_preformatted()) { $footnote_number_text = "($number_in_doc)"; } else { $footnote_number_text = "$number_in_doc"; } return "$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, $text, $replacement); $url = $url_arg->{'monospacestring'} if defined($url_arg); $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 if (!defined($text) or $text eq ''); return $text if (!defined($url) or $url eq ''); return "$text ($url)" if ($self->in_string()); return "$text"; } $default_commands_conversion{'uref'} = \&_convert_uref_command; $default_commands_conversion{'url'} = \&_convert_uref_command; my @image_files_extensions = ('.png', '.jpg', '.jpeg', '.gif'); sub _convert_image_command($$$$) { my $self = shift; my $cmdname = shift; my $command = shift; my $args = shift; my @extensions = @image_files_extensions; if (defined($args->[0]->{'monospacetext'}) and $args->[0]->{'monospacetext'} ne '') { my $basefile = $args->[0]->{'monospacetext'}; return $basefile if ($self->in_string()); my $extension; if (defined($args->[4]) and defined($args->[4]->{'monospacetext'})) { $extension = $args->[4]->{'monospacetext'}; unshift @extensions, ("$extension", ".$extension"); } my $image_file; foreach my $extension (@extensions) { if ($self->Texinfo::Common::locate_include_file ($basefile.$extension)) { # use the basename and not the file found. It is agreed that it is # better, since in any case the files are moved. $image_file = $basefile.$extension; last; } } if (!defined($image_file) or $image_file eq '') { if (defined($extension) and $extension ne '') { $image_file = $basefile.$extension; } else { $image_file = "$basefile.jpg"; } #cluck "err ($self->{'ignore_notice'})"; $self->line_warn(sprintf( __("\@image file `%s' (for HTML) not found, using `%s'"), $basefile, $image_file), $command->{'line_nr'}); } 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 = $self->protect_text($basefile); } return "protect_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') { # MathJax won't handle tags in code # TODO: instead convert inside $command to LaTeX, when such a conversion # becomes possible if ($arg !~ /{'element_math'} = 1; return "\\($arg\\)"; } } return "$arg"; } $default_commands_conversion{'math'} = \&_convert_math_command; sub _convert_accent_command($$$$) { my $self = shift; my $cmdname = shift; my $command = shift; my $args = shift; return $self->xml_accents($command, $self->in_upper_case()); } foreach my $command (keys(%accent_commands)) { $default_commands_conversion{$command} = \&_convert_accent_command; } # key is formatted as code since it is in code_style_commands sub _convert_key_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 $text; } #return $self->protect_text('<') .$text .$self->protect_text('>'); my $class = $cmdname; if (!$self->in_code()) { return $self->_attribute_class('tt', $class).'>'.$text .'';; } else { my $open = $self->_attribute_class('span', $class); if ($open ne '') { return $open.'>'.$text.''; } else { return $text; } } } $default_commands_conversion{'key'} = \&_convert_key_command; # argument is formatted as code since indicateurl is in code_style_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').'' .$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->{'format_heading_text'}}($self, 'titlefont', $text, 0, $command); } $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 = $args->[0]->{'normal'}; my $res; if (defined($arg) && $arg) { # 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); } sub protect_text($$) { my $self = shift; my $text = shift; return &{$self->{'format_protect_text'}}($self, $text); } 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_format_heading_text($$$$$) { my $self = shift; my $cmdname = shift; my $text = shift; my $level = shift; my $command = shift; return '' if ($text !~ /\S/); # This should seldom happen. if ($self->in_string()) { $text .= "\n" unless ($cmdname eq 'titlefont'); return $text; } my $class; if ($cmdname eq 'node') { $class = 'node-heading'; } else { $class = $cmdname; } my $align = ''; $align = ' align="center"' if ($cmdname eq 'centerchap' or $cmdname eq 'settitle'); if ($level < 1) { $level = 1; } elsif ($level > $self->get_conf('MAX_HEADER_LEVEL')) { $level = $self->get_conf('MAX_HEADER_LEVEL'); } my $result = $self->_attribute_class("h$level", $class) ."$align>$text"; # 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 ($cmdname eq 'titlefont'); $result .= $self->get_conf('DEFAULT_RULE') . "\n" if ($cmdname eq 'part' and defined($self->get_conf('DEFAULT_RULE')) and $self->get_conf('DEFAULT_RULE') ne ''); return $result; } # 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 $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->_element_direction($self->{'current_element'}, $direction, 'href'); my $node; if ($self->get_conf('xrefautomaticsectiontitle') eq 'on') { $node = $self->_element_direction($self->{'current_element'}, $direction, 'section'); } if (!defined($node)) { $node = $self->_element_direction($self->{'current_element'}, $direction, 'node'); } my $anchor; if (defined($href) and defined($node) and $node =~ /\S/) { my $anchor_attributes = $omit_rel ? '' : $self->_direction_href_attributes($direction); $anchor = "$node"; } if (defined($anchor)) { # i18n $result = $self->get_conf('BUTTONS_TEXT')->{$direction}.": $anchor"; } # 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; return _default_panel_button_dynamic_direction($self, $direction, 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; return _default_panel_button_dynamic_direction($self, $direction, undef, 1); } # how to create IMG tag # this is only used in html, and only 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 qq{$alt}; } sub _direction_href_attributes($$) { my $self = shift; my $direction = shift; my $href_attributes = ''; if ($self->get_conf('USE_ACCESSKEY') and $self->get_conf('BUTTONS_ACCESSKEY')) { my $accesskey = $self->get_conf('BUTTONS_ACCESSKEY')->{$direction}; if (defined($accesskey) and ($accesskey ne '')) { $href_attributes = " accesskey=\"$accesskey\""; } } if ($self->get_conf('USE_REL_REV') and $self->get_conf('BUTTONS_REL')) { my $button_rel = $self->get_conf('BUTTONS_REL')->{$direction}; 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 ($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 $button_href = $button->[0]; # $button_href is simple text and $text is a reference if (defined($button_href) and !ref($button_href) and defined($text) and (ref($text) eq 'SCALAR') and defined($$text)) { # use given text my $href = $self->_element_direction($self->{'current_element'}, $button_href, 'href'); if ($href) { my $anchor_attributes = $self->_direction_href_attributes($button_href); $active = "$$text"; } else { $passive = $$text; } $need_delimiter = 1; # $button_href is simple text and $text is a reference on code } elsif (defined($button_href) and !ref($button_href) and defined($text) and (ref($text) eq 'CODE')) { ($active, $need_delimiter) = &$text($self, $button_href); # $button_href is simple text and $text is also a simple text } elsif (defined($button_href) and !ref($button_href) and defined($text) and !ref($text)) { if ($text =~ s/^->\s*//) { $active = $self->_element_direction($self->{'current_element'}, $button_href, $text); } else { my $href = $self->_element_direction($self->{'current_element'}, $button_href, 'href'); my $text_formatted = $self->_element_direction($self->{'current_element'}, $button_href, $text); if ($href) { my $anchor_attributes = $self->_direction_href_attributes($button_href); $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 = $self->get_conf('BUTTONS_NAME')->{$button}; $active = &{$self->{'format_button_icon_img'}}($self, $button_name, $self->get_conf('ACTIVE_ICONS')->{' '}); } else { $active = $self->get_conf('BUTTONS_TEXT')->{$button}; } $need_delimiter = 0; } else { my $href = $self->_element_direction($self->{'current_element'}, $button, 'href'); if ($href) { # button is active my $btitle = ''; if ($self->get_conf('BUTTONS_GOTO') and defined($self->get_conf('BUTTONS_GOTO')->{$button})) { $btitle = ' title="' . $self->get_conf('BUTTONS_GOTO')->{$button} . '"'; } if ($self->get_conf('USE_ACCESSKEY') and $self->get_conf('BUTTONS_ACCESSKEY')) { my $accesskey = $self->get_conf('BUTTONS_ACCESSKEY')->{$button}; if (defined($accesskey) and $accesskey ne '') { $btitle .= " accesskey=\"$accesskey\""; } } if ($self->get_conf('USE_REL_REV') and ($self->get_conf('BUTTONS_REL'))) { my $button_rel = $self->get_conf('BUTTONS_REL')->{$button}; 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') and $self->get_conf('BUTTONS_NAME')) { my $active_icon = $self->get_conf('ACTIVE_ICONS')->{$button}; my $button_name = $self->get_conf('BUTTONS_NAME')->{$button}; if (defined($active_icon) and $active_icon ne '' and defined($button_name)) { # use icon $active = "". &{$self->{'format_button_icon_img'}}($self, $button_name, $active_icon, $self->_element_direction($self->{'current_element'}, $button, 'string')) .""; $use_icon = 1; } } if (!$use_icon) { # use text $active = '[' . "". $self->get_conf('BUTTONS_TEXT')->{$button}."" . ']'; } } else { # button is passive my $use_icon; if ($self->get_conf('ICONS') and $self->get_conf('PASSIVE_ICONS') and $self->get_conf('BUTTONS_NAME')) { my $passive_icon = $self->get_conf('PASSIVE_ICONS')->{$button}; my $button_name = $self->get_conf('BUTTONS_NAME')->{$button}; if ($passive_icon and $passive_icon ne '') { $passive = &{$self->{'format_button_icon_img'}}($self, $button_name, $passive_icon, $self->_element_direction($self->{'current_element'}, $button, 'string')); $use_icon = 1; } } if (!$use_icon) { $passive = '[' . $self->get_conf('BUTTONS_TEXT')->{$button} . ']'; } } $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); } sub _default_format_navigation_header_panel($$$$;$) { my $self = shift; my $buttons = shift; my $cmdname = shift; my $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 $first_button = 1; my $result = ''; if ($self->get_conf('HEADER_IN_TABLE')) { $result .= $self->_attribute_class('table', 'header') .' cellpadding="1" cellspacing="1" border="0">'."\n"; $result .= "" unless $vertical; } else { $result .= $self->_attribute_class('div', 'header').">\n

\n"; } foreach my $button (@$buttons) { if ($self->get_conf('HEADER_IN_TABLE')) { $result .= qq{\n} if $vertical; $result .= qq{}; } my $direction; if (ref($button) eq 'ARRAY' and defined($button->[0]) and !ref($button->[0])) { $direction = $button->[0]; } elsif (defined($button) and !ref($button)) { $direction = $button; } my ($active, $passive, $need_delimiter) = &{$self->{'format_button'}}($self, $button); if ($self->get_conf('HEADER_IN_TABLE')) { if (defined($active)) { $first_button = 0 if ($first_button); $result .= $active; } elsif (defined($passive)) { $first_button = 0 if ($first_button); $result .= $passive; } $result .= "\n"; $result .= "\n" if $vertical; } 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 $command = shift; my $result = ''; if ($self->get_conf('VERTICAL_HEAD_NAVIGATION')) { $result .= ''; } return $self->_attribute_class('table', 'menu') ." border=\"0\" cellspacing=\"0\">${begin_row}\n" . $content . "${end_row}
'; } $result .= &{$self->{'format_navigation_header_panel'}}($self, $buttons, $cmdname, $command, $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; } sub _default_format_element_header($$$$) { my $self = shift; my $cmdname = shift; my $command = shift; my $element = shift; my $result = ''; print STDERR "Element $element (@{$element->{'contents'}}) ". Texinfo::Structuring::_print_element_command_texi($element) ."\n" if ($self->get_conf('DEBUG')); # Do the heading if the command is the first command in the element if (($element->{'contents'}->[0] eq $command or (!$element->{'contents'}->[0]->{'cmdname'} and $element->{'contents'}->[1] eq $command)) # and there is more than one element and ($element->{'element_next'} or $element->{'element_prev'})) { my $is_top = $self->element_is_top($element); my $first_in_page = (defined($element->{'filename'}) and $self->{'counter_in_file'}->{$element->{'filename'}} == 1); my $previous_is_top = ($element->{'element_prev'} and $self->element_is_top($element->{'element_prev'})); print STDERR "Header ($previous_is_top, $is_top, $first_in_page): " .Texinfo::Structuring::_print_root_command_texi($command)."\n" if ($self->get_conf('DEBUG')); if ($is_top) { # use TOP_BUTTONS for top. $result .= &{$self->{'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->{'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->{'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->{'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->{'format_navigation_header_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 $command = shift; my $args = shift; my $content = shift; my $result = ''; # not clear that it may really happen if ($self->in_string) { $result .= $self->command_string($command) ."\n" if ($cmdname ne 'node'); $result .= $content if (defined($content)); return $result; } my $element_id = $self->command_id($command); my $section; if ($cmdname eq 'node' and $command->{'extra'}->{'associated_section'}) { $section = $command->{'extra'}->{'associated_section'}; } elsif ($cmdname ne 'node' and not $command->{'extra'}->{'associated_node'} # to avoid *heading* @-commands and $Texinfo::Common::root_commands{$cmdname}) { $section = $command; } if ($section) { my $level = $section->{'level'}; $result .= join('', $self->close_registered_sections_level($level)); $self->register_opened_section_level($level, "\n"); $result .= '
" if (defined($element_id) and $element_id ne ''); } print STDERR "Process $command " .Texinfo::Structuring::_print_root_command_texi($command)."\n" if ($self->get_conf('DEBUG')); my $element; if ($Texinfo::Common::root_commands{$command->{'cmdname'}} and $command->{'parent'} and $command->{'parent'}->{'type'} and $command->{'parent'}->{'type'} eq 'element') { $element = $command->{'parent'}; } if ($element) { $result .= &{$self->{'format_element_header'}}($self, $cmdname, $command, $element); } my $heading_level; # node is used as heading if there is nothing else. if ($cmdname eq 'node') { if (!$element or (!$element->{'extra'}->{'section'} and $element->{'extra'}->{'node'} and $element->{'extra'}->{'node'} eq $command # bogus node may not have been normalized and defined($command->{'extra'}->{'normalized'}))) { if ($command->{'extra'}->{'normalized'} eq 'Top') { $heading_level = 0; } else { $heading_level = 3; } } } elsif (defined $command->{'level'}) { $heading_level = $command->{'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::Structuring::section_level($command); } my $heading = $self->command_text($command); # $heading not defined may happen if the command is a @node, for example # if there is an error in the node. if (defined($heading) and $heading ne '' and defined($heading_level)) { if ($self->get_conf('TOC_LINKS') and $Texinfo::Common::root_commands{$cmdname} and $Texinfo::Common::sectioning_commands{$cmdname}) { my $content_href = $self->command_contents_href($command, 'contents', $self->{'current_filename'}); if ($content_href) { $heading = "$heading"; } } if ($self->in_preformatted()) { $result .= ''.$heading.''."\n"; } else { # if the level was changed, set the command name right if ($cmdname ne 'node' and $heading_level ne $Texinfo::Common::command_structuring_level{$cmdname}) { $cmdname = $Texinfo::Common::level_to_structuring_command{$cmdname}->[$heading_level]; } $result .= &{$self->{'format_heading_text'}}($self, $cmdname, $heading, $heading_level +$self->get_conf('CHAPTER_HEADER_LEVEL') -1, $command); } } $result .= $content if (defined($content)); my $table_of_contents_was_output = 0.; if ($self->get_conf('CONTENTS_OUTPUT_LOCATION') eq 'after_top' and $cmdname eq 'top' and $self->{'structuring'} and $self->{'structuring'}->{'sectioning_root'} and scalar(@{$self->{'structuring'}->{'sections_list'}}) > 1) { foreach my $content_command_name ('contents', 'shortcontents') { if ($self->get_conf($content_command_name)) { my $contents_text = $self->_contents_inline_element($content_command_name, undef); if ($contents_text ne '') { $result .= $contents_text; #$result .= $contents_text . $self->get_conf('DEFAULT_RULE')."\n"; $table_of_contents_was_output = 1; } } } } if (not $table_of_contents_was_output and $self->get_conf('FORMAT_MENU') eq 'sectiontoc' and $sectioning_commands{$cmdname} and ($cmdname ne 'top' or (not ($self->_has_contents_or_shortcontents() and $self->get_conf('CONTENTS_OUTPUT_LOCATION') eq 'inline')))) { $result .= _mini_toc($self, $command); } return $result; } foreach my $command (keys(%sectioning_commands), 'node') { $default_commands_conversion{$command} = \&_convert_heading_command; } sub _convert_raw_command($$$$) { my $self = shift; my $cmdname = shift; my $command = shift; my $content = shift; if ($cmdname eq $self->{'output_format'}) { return $content; } $self->line_warn(sprintf(__("raw format %s is not converted"), $cmdname), $command->{'line_nr'}); return $self->protect_text($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->{'expanded_formats_hash'}->{$format}) { $arg_index = 1; } elsif ($self->{'expanded_formats_hash'}->{$format}) { $arg_index = 0; } } elsif (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 (keys(%inline_commands)) { $default_commands_conversion{$command} = \&_convert_inline_command; } sub _indent_with_table ($) { my $content = shift; return '
 '.$content."
\n"; } my $html_menu_entry_index = 0; sub _convert_preformatted_command($$$$) { my $self = shift; my $cmdname = shift; my $command = shift; my $content = shift; my $extra_classes; # this is mainly for classes as there are purprosely no classes # for small* my $main_cmdname; if ($small_alias{$cmdname}) { $main_cmdname = $small_alias{$cmdname}; } else { $main_cmdname = $cmdname; } if ($cmdname eq 'menu') { $html_menu_entry_index = 0; } elsif ($cmdname eq 'example') { if ($command->{'args'}) { $extra_classes = []; for my $example_arg (@{$command->{'args'}}) { # convert or remove all @-commands, using simple ascii and unicode # characters my $converted_arg = Texinfo::Convert::NodeNameNormalization::convert($example_arg); if ($converted_arg ne '') { push @$extra_classes, $converted_arg; } } } } elsif ($main_cmdname eq 'lisp') { $main_cmdname = 'example'; $extra_classes = ['lisp']; } if ($content ne '' and !$self->in_string()) { if ($self->get_conf('COMPLEX_FORMAT_IN_TABLE')) { if ($indented_preformatted_commands{$cmdname}) { return _indent_with_table ($content); } else { return $content."\n"; } } else { return $self->_attribute_class('div', $main_cmdname, $extra_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 $content = shift; # no class for @small* variants $cmdname = $small_alias{$cmdname} if $small_alias{$cmdname}; if ($content ne '' and !$self->in_string()) { if ($self->get_conf('COMPLEX_FORMAT_IN_TABLE')) { return _indent_with_table ($content); } else { return $self->_attribute_class('blockquote', $cmdname).">\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 $content = shift; if (!$self->in_string) { return $self->_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 $content = shift; if ($self->in_string) { return $content; } my $result = ''; $result .= $self->_attribute_class('div', 'displaymath').'>'; if ($self->get_conf('HTML_MATH') and $self->get_conf('HTML_MATH') eq 'mathjax') { $self->{'element_math'} = 1; $result .= $self->_attribute_class('em', 'tex2jax_process').'>' ."\\[$content\\]".''; } else { $result .= $self->_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 = $self->Texinfo::Common::expand_verbatiminclude($command); if (defined($verbatim_include_verbatim)) { return $self->convert_tree($verbatim_include_verbatim); } else { return ''; } } $default_commands_conversion{'verbatiminclude'} = \&_convert_verbatiminclude_command; sub _convert_command_noop($$$$) { my $self = shift; my $cmdname = shift; my $command = shift; my $content = shift; return $content; } $default_commands_conversion{'raggedright'} = \&_convert_command_noop; $default_commands_conversion{'flushleft'} = \&_convert_command_noop; $default_commands_conversion{'flushright'} = \&_convert_command_noop; $default_commands_conversion{'group'} = \&_convert_command_noop; sub _convert_sp_command($$$$) { my $self = shift; my $cmdname = shift; my $command = shift; my $args = shift; if (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 "
\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; # FIXME do something better with css and span? my $preformatted = $self->in_preformatted(); if ($self->in_preformatted() or $self->in_string()) { return $self->_convert_preformatted_type($cmdname, $command, $args->[0]->{'normal'} ."\n"); } else { # ignore alignment information return "

".$args->[0]->{'normal'} ."\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 $self->_convert_preformatted_type($cmdname, $command, $args->[0]->{'normal'}."\n"); } else { return "
".$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'}->{'titlepage'}); if (!$self->in_string()) { return "$args->[0]->{'normal'}
\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 "

$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 "

$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; if ($self->{'extra'} and $self->{'extra'}->{'copying'}) { return $self->convert_tree({'contents' => $self->{'extra'}->{'copying'}->{'contents'}}); } return ''; } $default_commands_conversion{'insertcopying'} = \&_convert_insertcopying_command; sub _convert_listoffloats_command($$$$) { my $self = shift; my $cmdname = shift; my $command = shift; my $args = shift; if (!$self->in_string() and $command->{'extra'} and $command->{'extra'}->{'type'} and defined($command->{'extra'}->{'type'}->{'normalized'}) and $self->{'floats'} and $self->{'floats'}->{$command->{'extra'}->{'type'}->{'normalized'}} and @{$self->{'floats'}->{$command->{'extra'}->{'type'}->{'normalized'}}}) { my $listoffloats_name = $command->{'extra'}->{'type'}->{'normalized'}; my $result = $self->_attribute_class('dl', 'listoffloats').">\n" ; foreach my $float (@{$self->{'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; if ($float->{'extra'}->{'shortcaption'}) { $caption = $float->{'extra'}->{'shortcaption'}; } elsif ($float->{'extra'}->{'caption'}) { $caption = $float->{'extra'}->{'caption'}; } my $caption_text; if ($caption) { $caption_text = $self->convert_tree_new_formatting_context( $caption->{'args'}->[0], $cmdname, 'listoffloats'); } else { $caption_text = ''; } $result .= '
'.$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 $content = shift; return $content if ($cmdname eq 'detailmenu'); $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; } my $begin_row = ''; my $end_row = ''; if ($self->_in_preformatted_in_menu()) { $begin_row = '
'; $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::Common::float_name_caption($self, $command); my $caption_text = ''; my $prepended_text; if ($self->in_string()) { if ($prepended) { $prepended_text = $self->convert_tree_new_formatting_context( $prepended, 'float prepended'); } else { $prepended_text = ''; } if ($caption) { $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 $label; if (defined($id) and $id ne '') { $label = ""; } else { $label = ''; } if ($prepended) { if ($caption) { # prepend the prepended tree to the first paragraph my @caption_original_contents = @{$caption->{'args'}->[0]->{'contents'}}; my @caption_contents; my $new_paragraph; while (@caption_original_contents) { my $content = shift @caption_original_contents; if ($content->{'type'} and $content->{'type'} eq 'paragraph') { %{$new_paragraph} = %{$content}; $new_paragraph->{'contents'} = [@{$content->{'contents'}}]; unshift (@{$new_paragraph->{'contents'}}, {'cmdname' => 'strong', 'args' => [{'type' => 'brace_command_arg', 'contents' => [$prepended]}]}); push @caption_contents, $new_paragraph; last; } else { push @caption_contents, $content; } } push @caption_contents, @caption_original_contents; if ($new_paragraph) { $caption_text = $self->convert_tree_new_formatting_context( {'contents' => \@caption_contents}, 'float caption'); $prepended_text = ''; } } if ($caption_text eq '') { $prepended_text = $self->convert_tree_new_formatting_context( $prepended, 'float prepended'); if ($prepended_text ne '') { $prepended_text = '

'.$prepended_text.'

'; } } } else { $prepended_text = ''; } if ($caption and $caption_text eq '') { $caption_text = $self->convert_tree_new_formatting_context( $caption->{'args'}->[0], 'float caption'); } if ($prepended_text.$caption_text ne '') { $prepended_text = $self->_attribute_class('div','float-caption'). '>' . $prepended_text; $caption_text .= ''; } return $self->_attribute_class('div','float'). '>' .$label."\n".$content. $prepended_text.$caption_text . ''; } $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; #$cmdname = $small_alias{$cmdname} # if $small_alias{$cmdname}; my $attribution = ''; if ($command->{'extra'} and $command->{'extra'}->{'authors'}) { foreach my $author (@{$command->{'extra'}->{'authors'}}) { my $centered_author = $self->gdt("\@center --- \@emph{{author}}\n", {'author' => $author->{'args'}->[0]->{'contents'}}); $centered_author->{'parent'} = $command; $attribution .= $self->convert_tree($centered_author); } } if (!$self->in_string()) { return "
\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 $content = shift; if ($content =~ /\S/ and !$self->in_string()) { return $self->_attribute_class('table', 'cartouche') ." border=\"1\">\n". $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 $content = shift; if ($self->in_string()) { return $content; } if ($command->{'extra'}->{'command_as_argument'} and $command->{'extra'}->{'command_as_argument'}->{'cmdname'} eq 'bullet') { return "\n"; } else { return $self->_attribute_class('ul',$NO_BULLET_LIST_CLASS).">\n" . $content . "\n"; } } $default_commands_conversion{'itemize'} = \&_convert_itemize_command; sub _convert_enumerate_command($$$$) { my $self = shift; my $cmdname = shift; my $command = shift; my $content = shift; if ($self->in_string()) { return $content; } if ($content eq '') { return ''; } 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'; } if (defined $type and defined $start) { return "
    \n" . $content . "
\n"; } elsif (defined $start) { return "
    \n" . $content . "
\n"; } } return "
    \n" . $content . "
\n"; } $default_commands_conversion{'enumerate'} = \&_convert_enumerate_command; sub _convert_multitable_command($$$$) { my $self = shift; my $cmdname = shift; my $command = shift; my $content = shift; if ($self->in_string()) { return $content; } if ($content =~ /\S/) { return "\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 $content = shift; if ($self->in_string()) { return $content; } if ($content ne '') { return "
\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 $content = shift; if ($self->in_string()) { return $content; } if ($command->{'parent'}->{'cmdname'} and $command->{'parent'}->{'cmdname'} eq 'itemize') { my $prepend ; my $itemize = $command->{'parent'}; if ($itemize->{'extra'}->{'command_as_argument'} and $itemize->{'extra'}->{'command_as_argument'}->{'cmdname'} eq 'bullet') { $prepend = ''; } else { # Setting multiple expansion should not be needed, except in # case of invalid constructs $prepend = $self->convert_tree_new_formatting_context( $itemize->{'args'}->[0], $command->{'cmdname'}, 'item_prepended'); } if ($content =~ /\S/) { return '
  • ' . $prepend .' '. $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') { # FIXME instead use the code of Plaintext or DocBook. my $args = $content; if ($args->[0]) { my $tree = $self->_table_item_content_tree($command, [$args->[0]->{'tree'}]); my $result = $self->convert_tree ($tree); foreach my $command_name (reverse($self->commands_stack())) { if ($preformatted_code_commands{$command_name}) { $result = '' .$result. ''; last; } } my $index_id = $self->command_id ($command); my $anchor; if (defined($index_id)) { $anchor = $self->_get_copiable_anchor($index_id); $index_id = " id='$index_id'"; } else { $anchor = ''; $index_id = ''; } return "$result$anchor\n"; } else { return ''; } } elsif ($command->{'parent'}->{'type'} and $command->{'parent'}->{'type'} eq 'row') { return $self->_convert_tab_command ($cmdname, $command, $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 $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 'inforef' and defined($args->[2]->{'normal'}) and $args->[2]->{'normal'} ne '') { $name = $args->[2]->{'normal'}; } elsif (defined($args->[1]->{'normal'}) and $args->[1]->{'normal'} ne '') { $name = $args->[1]->{'normal'} } if ($cmdname eq 'inforef') { $args->[3] = $args->[2]; $args->[2] = undef; } my $file_arg_tree; my $file = ''; if (defined($args->[3]->{'monospacetext'}) and $args->[3]->{'monospacetext'} ne '') { $file_arg_tree = $args->[3]->{'tree'}; $file = $args->[3]->{'monospacetext'}; } my $book = ''; $book = $args->[4]->{'normal'} if (defined($args->[4]->{'normal'})); # internal reference if ($cmdname ne 'inforef' and $book eq '' and $file eq '' and $root->{'extra'}->{'node_argument'} and defined($root->{'extra'}->{'node_argument'}->{'normalized'}) and !$root->{'extra'}->{'node_argument'}->{'manual_content'} and $self->{'labels'} and $self->{'labels'}->{$root->{'extra'}->{'node_argument'}->{'normalized'}}) { my $node = $self->label_command($root->{'extra'}->{'node_argument'}->{'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_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'}->{'associated_section'}) { $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 = "$name" if ($href ne '' and !$self->in_string()); # maybe use {'extra'}->{'node_argument'}? 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' or $cmdname eq 'inforef') { $tree = $self->gdt('See {reference_name}', { 'reference_name' => {'type' => '_converted', 'text' => $reference} }); } elsif ($cmdname eq 'ref') { $tree = $self->gdt('{reference_name}', { 'reference_name' => {'type' => '_converted', 'text' => $reference} }); } } else { # external reference my $node_entry = {}; $node_entry->{'node_content'} = $root->{'extra'}->{'node_argument'}->{'node_content'} if ($root->{'extra'}->{'node_argument'} and $root->{'extra'}->{'node_argument'}->{'node_content'}); $node_entry->{'normalized'} = $root->{'extra'}->{'node_argument'}->{'normalized'} if ($root->{'extra'}->{'node_argument'} and exists($root->{'extra'}->{'node_argument'}->{'normalized'})); # file argument takes precedence over the file in the node (file)node entry if (defined($file_arg_tree) and $file ne '') { $node_entry->{'manual_content'} = $file_arg_tree->{'contents'}; } elsif ($root->{'extra'}->{'node_argument'} and $root->{'extra'}->{'node_argument'}->{'manual_content'}) { $node_entry->{'manual_content'} = $root->{'extra'}->{'node_argument'}->{'manual_content'}; my $file_with_node_tree = {'type' => '_code', 'contents' => [@{$node_entry->{'manual_content'}}]}; $file = $self->convert_tree($file_with_node_tree, 'node file in ref'); } my $href = $self->command_href($node_entry, undef, $root); if ($book eq '') { if (!defined($name)) { my $node_name = $self->command_text($node_entry); $name = $node_name; } elsif ($file ne '') { $name = "($file)$name"; } } elsif (!defined($name) and $node_entry->{'node_content'}) { my $node_no_file_tree = {'type' => '_code', 'contents' => [@{$node_entry->{'node_content'}}]}; my $node_name = $self->convert_tree($node_no_file_tree, 'node in ref'); if (defined($node_name) and ($self->get_conf('KEEP_TOP_EXTERNAL_REF') or $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 ($self->get_conf('KEEP_TOP_EXTERNAL_REF') or $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 $attribute = ''; if ($file) { $attribute = "data-manual=\"".$self->protect_text($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 { 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); } foreach my $command(keys(%ref_commands)) { $default_commands_conversion{$command} = \&_convert_xref_commands; } sub _convert_index_command($$$$) { my $self = shift; my $cmdname = shift; my $command = shift; my $args = shift; my $index_id = $self->command_id($command); if (defined($index_id) and $index_id ne '' and !@{$self->{'multiple_pass'}} and !$self->in_string()) { my $result = ""; $result .= "\n" unless ($self->in_preformatted()); return $result; } return ''; } $default_commands_conversion{'cindex'} = \&_convert_index_command; my %formatted_index_entries; 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 ''; } if (!$self->{'index_entries_by_letter'} or !$self->{'index_entries_by_letter'}->{$index_name} or !@{$self->{'index_entries_by_letter'}->{$index_name}}) { return ''; } #foreach my $letter_entry (@{$self->{'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()); $self->_new_document_context($cmdname); my $result = ''; # First do the summary letters linking to the letters done below my %letter_id; my @non_alpha = (); my @alpha = (); # collect the links my $symbol_idx = 0; foreach my $letter_entry (@{$self->{'index_entries_by_letter'}->{$index_name}}) { my $letter = $letter_entry->{'letter'}; my $index_element_id = $self->_element_direction($self->{'current_element'}, 'This', 'target'); if (!defined($index_element_id)) { $index_element_id = $target_prefix; } my $is_symbol = $letter !~ /^[[:alpha:]]/; 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; my $summary_letter_link = $self->_attribute_class('a', 'summary-letter') ." href=\"#$identifier\">".$self->protect_text($letter).''; if ($is_symbol) { push @non_alpha, $summary_letter_link; } else { push @alpha, $summary_letter_link; } } # Format the summary letters my $join = ''; my $non_alpha_text = ''; my $alpha_text = ''; $join = "   \n
    \n" if (@non_alpha and @alpha); if (@non_alpha) { $non_alpha_text = join("\n   \n", @non_alpha) . "\n"; } if (@alpha) { $alpha_text = join("\n   \n", @alpha) . "\n   \n"; } # format the summary my $summary = "
    " . $self->convert_tree($self->gdt('Jump to')) .":   " . $non_alpha_text . $join . $alpha_text . "
    \n"; $result .= $summary; # now format the index entries $result .= $self->_attribute_class('table', "index-$index_name") ." border=\"0\">\n" . "" . $self->convert_tree($self->gdt('Index Entry')) . "  " . $self->convert_tree($self->gdt('Section')) ."\n" . " ".$self->get_conf('DEFAULT_RULE') ."\n"; foreach my $letter_entry (@{$self->{'index_entries_by_letter'}->{$index_name}}) { my $letter = $letter_entry->{'letter'}; my $entries_text = ''; foreach my $index_entry_ref (@{$letter_entry->{'entries'}}) { # to avoid double error messages set ignore_notice if an entry was # already formatted once, for example if there are multiple printindex. my $already_formatted; if (!$formatted_index_entries{$index_entry_ref}) { $formatted_index_entries{$index_entry_ref} = 1; } else { $already_formatted = 1; $self->{'ignore_notice'}++; } my $entry; if ($index_entry_ref->{'in_code'}) { $entry = $self->convert_tree({'type' => '_code', 'contents' => $index_entry_ref->{'content'}}); } else { $entry = $self->convert_tree({'contents' => $index_entry_ref->{'content'}}); } $entry .= $self->convert_index_subentries($index_entry_ref); if ($already_formatted) { $self->{'ignore_notice'}--; } next if ($entry !~ /\S/); $entry = '' .$entry .'' if ($index_entry_ref->{'in_code'}); my $entry_href = $self->command_href($index_entry_ref->{'command'}); my $associated_command; if ($self->get_conf('NODE_NAME_IN_INDEX')) { $associated_command = $index_entry_ref->{'node'}; if (!defined($associated_command)) { $associated_command = $self->command_node($index_entry_ref->{'command'}); } } if (!$associated_command) { $associated_command = $self->command_element_command($index_entry_ref->{'command'}); if (!$associated_command) { # Use Top if not associated command found $associated_command = $self->element_command($self->global_element('Top')); } } 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 .= '' . "$entry" . $self->get_conf('INDEX_ENTRY_COLON') . ' '; $entries_text .= "$associated_command_text" if ($associated_command_href); $entries_text .= "\n"; } # a letter and associated indice entries $result .= '' . "".$self->protect_text($letter) . "\n" . $entries_text . " ".$self->get_conf('DEFAULT_RULE')."\n"; } $result .= "\n"; pop @{$self->{'document_context'}}; return $result .$summary; } $default_commands_conversion{'printindex'} = \&_convert_printindex_command; sub _contents_inline_element($$$) { my $self = shift; my $cmdname = shift; my $command = shift; my $content = &{$self->{'format_contents'}}($self, $cmdname, $command); if ($content) { my $element_name = $contents_command_element_name{$cmdname}; my $special_element = $self->special_element($element_name); my $heading; my $result = "
    command_id($special_element); if ($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"; $heading = $self->convert_tree ($self->get_conf('SPECIAL_ELEMENTS_NAME')->{$element_name}); } $result .= ">\n"; my $class = $self->get_conf('SPECIAL_ELEMENTS_CLASS')->{$element_name}; $result .= &{$self->{'format_heading_text'}}($self, $class.'-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()); $cmdname = 'shortcontents' if ($cmdname eq 'summarycontents'); $self->_informative_command($command); if ($self->get_conf('CONTENTS_OUTPUT_LOCATION') eq 'inline' and ($cmdname eq 'contents' or $cmdname eq 'shortcontents') and $self->get_conf($cmdname) and $self->{'structuring'} and $self->{'structuring'}->{'sectioning_root'} and scalar(@{$self->{'structuring'}->{'sections_list'}}) > 1) { return $self->_contents_inline_element($cmdname, $command); } if ($cmdname eq 'documentlanguage') { $self->_translate_names(); } return ''; } foreach my $informative_command (@informative_global_commands) { $default_commands_conversion{$informative_command} = \&_convert_informative_command; } # associate same formatting function for @small* command # as for the associated @-command foreach my $small_command (keys(%small_alias)) { $default_commands_conversion{$small_command} = $default_commands_conversion{$small_alias{$small_command}}; } # Keys are tree element types, values are function references to convert # elements of that type. Can be overridden with # Texinfo::Config::texinfo_types_conversion, setup by # Texinfo::Config::texinfo_register_type_formatting() my %default_types_conversion; sub default_types_conversion($$) { my $self = shift; my $type = shift; return $default_types_conversion{$type}; } # Ignored commands foreach my $type ('empty_line_after_command', 'preamble', 'preamble_before_setfilename', 'empty_spaces_after_command', 'spaces_at_end', 'empty_spaces_before_argument', 'empty_spaces_before_paragraph', 'empty_spaces_after_close_brace') { $default_types_conversion{$type} = undef; } my %paragraph_style = ( 'center' => 'center', 'flushleft' => 'left', 'flushright' => 'right', ); sub _quotation_arg_to_prepend($$) { my $self = shift; my $command = shift; if ($command->{'parent'} and $command->{'parent'}->{'cmdname'} and ($command->{'parent'}->{'cmdname'} eq 'quotation' or $command->{'parent'}->{'cmdname'} eq 'smallquotation') and $command->{'parent'}->{'args'} and $command->{'parent'}->{'args'}->[0] and $command->{'parent'}->{'args'}->[0]->{'contents'} and @{$command->{'parent'}->{'args'}->[0]->{'contents'}}) { return $self->convert_tree($self->gdt('@b{{quotation_arg}:} ', {'quotation_arg' => $command->{'parent'}->{'args'}->[0]->{'contents'}})); } return undef; } sub _convert_paragraph_type($$$$) { my $self = shift; my $type = shift; my $command = shift; my $content = shift; if ($self->paragraph_number() == 1) { my $in_format = $self->top_format(); 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; } else { my $prepended = $self->_quotation_arg_to_prepend($command); $content = $prepended.$content if (defined($prepended)); } } } return $content if ($self->in_string()); if ($content =~ /\S/) { my $align = $self->in_align(); if ($align and $paragraph_style{$align}) { return "

    ".$content."

    "; } else { return "

    ".$content."

    "; } } else { return ''; } } $default_types_conversion{'paragraph'} = \&_convert_paragraph_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-preformatted' to override # 'menu-preformatted' with 'menu-comment'? $pre_class = $class unless ($pre_class and $preformatted_code_commands{$pre_class} and !($preformatted_code_commands{$class} or $class eq 'menu-preformatted')); } return $pre_class; } sub _convert_preformatted_type($$$$) { my $self = shift; my $type = shift; my $command = shift; my $content = shift; if (!defined($content)) { cluck "content undef in _convert_preformatted_type " .Texinfo::Common::_print_current($command); } my $current = $command; # !defined preformatted_number may happen if there is something before the # first preformatted. For example an @exdent. if ($self->preformatted_number() and $self->preformatted_number() == 1) { my $prepended = $self->_quotation_arg_to_prepend($command); $content = $prepended.$content if (defined($prepended)); } return '' if ($content eq ''); return $content if ($type eq 'rawpreformatted'); my $pre_class = $self->_preformatted_class(); if ($self->top_format() 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. # # However, 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 here, not
      if ($command->{'parent'}->{'type'} 
          and $command->{'parent'}->{'type'} eq 'menu_entry_description'
          and !$self->_in_preformatted_in_menu()) {
        return $content;
      }
    
      if ($self->in_string()) {
        return $content;
      }
      $content =~ s/^\n/\n\n/; # a newline immediately after a 
     is ignored.
      my $result = $self->_attribute_class('pre', $pre_class).">".$content."
    "; # this may happen with lines without textual content # between a def* and def*x. if ($command->{'parent'}->{'cmdname'} and $command->{'parent'}->{'cmdname'} =~ /^def/) { $result = '
    '.$result.'
    '; } return $result; } $default_types_conversion{'preformatted'} = \&_convert_preformatted_type; $default_types_conversion{'rawpreformatted'} = \&_convert_preformatted_type; sub _convert_bracketed_type($$$$) { my $self = shift; my $type = shift; my $command = shift; my $content = shift; #print STDERR "$self $type $command $content\n"; return '{'.$content.'}'; } $default_types_conversion{'bracketed'} = \&_convert_bracketed_type; sub _convert_definfoenclose_type($$$$) { my $self = shift; my $type = shift; my $command = shift; my $content = shift; return $self->protect_text($command->{'extra'}->{'begin'}) . $content .$self->protect_text($command->{'extra'}->{'end'}); } $default_types_conversion{'definfoenclose_command'} = \&_convert_definfoenclose_type; sub _convert_text($$$) { my $self = shift; my $type = shift; my $command = shift; my $text = shift; if ($self->in_verbatim()) { return $self->protect_text($text); } return $text if ($self->in_raw()); $text = uc($text) if ($self->in_upper_case()); $text = $self->protect_text($text); if ($self->get_conf('ENABLE_ENCODING') and !$self->get_conf('ENABLE_ENCODING_USE_ENTITY') and $self->get_conf('OUTPUT_ENCODING_NAME') and $self->get_conf('OUTPUT_ENCODING_NAME') eq 'utf-8') { $text = Texinfo::Convert::Unicode::unicode_text($text, ($self->in_code() or $self->in_math())); } elsif (!$self->in_code() and !$self->in_math()) { if ($self->get_conf('USE_ISO')) { $text =~ s/---/\&mdash\;/g; $text =~ s/--/\&ndash\;/g; $text =~ s/``/\&ldquo\;/g; $text =~ s/''/\&rdquo\;/g; $text =~ s/'/\&rsquo\;/g; $text =~ s/`/\&lsquo\;/g; } else { $text =~ s/``/"/g; $text =~ s/''/"/g; $text =~ s/---/\x{1F}/g; $text =~ s/--/-/g; $text =~ s/\x{1F}/--/g; } } $text = $self->_protect_space($text); return $text; } $default_types_conversion{'text'} = \&_convert_text; sub _simplify_text_for_comparison($) { my $text = shift; $text =~ s/[^\w]//g; return $text; } sub _convert_row_type($$$$) { my $self = shift; my $type = shift; my $command = shift; my $content = shift; return $content if ($self->in_string()); if ($content =~ /\S/) { my $row_cmdname = $command->{'contents'}->[0]->{'cmdname'}; if ($row_cmdname eq 'headitem') { return '' . $content . '' . "\n"; } else { return '' . $content . '' . "\n"; } } else { return ''; } } $default_types_conversion{'row'} = \&_convert_row_type; sub _convert_menu_entry_type($$$) { my $self = shift; my $type = shift; my $command = shift; my $href; my $rel = ''; my $section; my $node_entry = $command->{'extra'}->{'menu_entry_node'}; # external node my $external_node; if ($node_entry->{'manual_content'}) { $href = $self->command_href($node_entry, undef, $command); $external_node = 1; } else { my $node = $self->label_command($node_entry->{'normalized'}); # if !NODE_NAME_IN_MENU, we pick the associated section, except if # the node is the element command if ($node->{'extra'}->{'associated_section'} and !$self->get_conf('NODE_NAME_IN_MENU') and !($self->command_element_command($node) eq $node)) { $section = $node->{'extra'}->{'associated_section'}; $href = $self->command_href($section, undef, $command); } else { $href = $self->command_href($node, undef, $command); } if ($node->{'extra'}->{'isindex'}) { # Mark the target as an index. See # http://microformats.org/wiki/existing-rel-values#HTML5_link_type_extensions $rel = ' rel="index"'; } } $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'); if ($self->_in_preformatted_in_menu() or $self->in_string()) { my $result = ''; my $i = 0; my @args = @{$command->{'args'}}; while (@args) { last if ($args[0]->{'type'} and $args[0]->{'type'} eq 'menu_entry_description'); my $arg = shift @args; if ($arg->{'type'} and $arg->{'type'} eq 'menu_entry_node') { my $name = $self->convert_tree( {'type' => '_code', 'contents' => $arg->{'contents'}}); if ($href ne '' and !$self->in_string()) { $result .= "".$name.""; } else { $result .= $name; } } elsif ($arg->{'type'} and $arg->{'type'} eq 'menu_entry_leading_text') { my $text = $arg->{'text'}; $text =~ s/\*/$MENU_SYMBOL/; $result .= $text; } else { $result .= $self->convert_tree($arg, "menu_arg preformatted [$i]"); } $i++; } my $description = ''; foreach my $arg (@args) { $description .= $self->convert_tree($arg, "menu_arg preformatted [$i]"); $i++; } if (!$self->get_conf('SIMPLE_MENU')) { $description =~ s/^]*>//; $description =~ s/<\/pre>$//; } $result = $result . $description; if (!$self->get_conf('SIMPLE_MENU')) { my $pre_class = $self->_preformatted_class(); $result = $self->_attribute_class('pre', $pre_class).">".$result."
    "; } return $result; } 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 ($command->{'extra'}->{'menu_entry_name'}) { $name = $self->convert_tree($command->{'extra'}->{'menu_entry_name'}); } if (!defined($name) or $name eq '') { if ($node_entry->{'manual_content'}) { $name = $self->command_text($node_entry); } else { $name = $self->convert_tree({'type' => '_code', 'contents' => $node_entry->{'node_content'}}, "menu_arg name"); } } $name =~ s/^\s*//; $name_no_number = $name; if ($href ne '') { $name = "".$name.""; } $name = "$MENU_SYMBOL ".$name; } my $description = ''; if ($command->{'extra'}->{'menu_entry_description'}) { $description = $self->convert_tree ($command->{'extra'}->{'menu_entry_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)); } } return "$name$MENU_ENTRY_COLON  $description\n"; } $default_types_conversion{'menu_entry'} = \&_convert_menu_entry_type; sub _convert_menu_comment_type($$$$) { my $self = shift; my $type = shift; my $command = shift; my $content = shift; if ($self->_in_preformatted_in_menu() or $self->in_string()) { return $content; } else { return "".$content .""; } } $default_types_conversion{'menu_comment'} = \&_convert_menu_comment_type; sub _convert_before_item_type($$$$) { my $self = shift; my $type = shift; my $command = shift; my $content = shift; return '' if ($content !~ /\S/); return $content if ($self->in_string()); my $top_format = $self->top_format(); if ($top_format eq 'itemize' or $top_format eq 'enumerate') { return '
  • '. $content .'
  • '; } elsif ($top_format eq 'table' or $top_format eq 'vtable' or $top_format eq 'ftable') { return '
    '. $content .'
    '."\n"; } elsif ($top_format eq 'multitable') { $content =~ s/^\s*//; $content =~ s/\s*$//; return ''.$content.''."\n"; } } $default_types_conversion{'before_item'} = \&_convert_before_item_type; sub _convert_def_line_type($$$$) { my $self = shift; my $type = shift; my $command = shift; my $content = shift; if ($self->in_string()) { return $self->protect_text(Texinfo::Convert::Text::convert( $command, Texinfo::Common::_convert_text_options($self))); } my $index_label = ''; my $index_id = $self->command_id($command); if (defined($index_id) and $index_id ne '' and !@{$self->{'multiple_pass'}}) { $index_label = " id=\"$index_id\""; } my $arguments = Texinfo::Common::definition_arguments_content($command); if (!$self->get_conf('DEF_TABLE')) { my $tree; my $command_name; if ($Texinfo::Common::def_aliases{$command->{'extra'}->{'def_command'}}) { $command_name = $Texinfo::Common::def_aliases{$command->{'extra'}->{'def_command'}}; } else { $command_name = $command->{'extra'}->{'def_command'}; } my $name; if ($command->{'extra'}->{'def_parsed_hash'}->{'name'}) { $name = $command->{'extra'}->{'def_parsed_hash'}->{'name'}; } else { $name = ''; } my $category = $command->{'extra'}->{'def_parsed_hash'}->{'category'}; my $category_result = ''; my $category_tree; if ($category) { $category_tree = {'type' => '_code', 'contents'=>[$self->gdt("{category}: ", {'category' => $category})] }; # NB perhaps the category shouldn't be in_code. } else { $category = ''; } # no type if ($command_name eq 'deffn' or $command_name eq 'defvr' or $command_name eq 'deftp' or (($command_name eq 'deftypefn' or $command_name eq 'deftypevr') and !$command->{'extra'}->{'def_parsed_hash'}->{'type'}) or (($command_name eq 'defop' or ($command_name eq 'deftypeop' and !$command->{'extra'}->{'def_parsed_hash'}->{'type'}) or $command_name eq 'defcv' or ($command_name eq 'deftypecv' and !$command->{'extra'}->{'def_parsed_hash'}->{'type'})) and !$command->{'extra'}->{'def_parsed_hash'}->{'class'})) { $category_result = $self->convert_tree($category_tree); if ($arguments) { $tree = $self->gdt("\@strong{{name}} \@emph{{arguments}}", { 'name' => $name, 'arguments' => $arguments}); } else { $tree = $self->gdt("\@strong{{name}}", {'name' => $name}); } # with a type } elsif ($command_name eq 'deftypefn' or $command_name eq 'deftypevr' or (($command_name eq 'deftypeop' or $command_name eq 'deftypecv') and !$command->{'extra'}->{'def_parsed_hash'}->{'class'})) { if ($arguments) { my $strings = { 'name' => $name, 'type' => $command->{'extra'}->{'def_parsed_hash'}->{'type'}, 'arguments' => $arguments}; if ($self->get_conf('deftypefnnewline') eq 'on') { $category_tree = {'type' => '_code', 'contents' => [$self->gdt("{category}:\@* ", {'category' => $category})] }; $tree = $self->gdt("\@emph{{type}}\@* \@strong{{name}} \@emph{{arguments}}", $strings); } else { $tree = $self->gdt("\@emph{{type}} \@strong{{name}} \@emph{{arguments}}", $strings); } } else { my $strings = { 'type' => $command->{'extra'}->{'def_parsed_hash'}->{'type'}, 'name' => $name}; if ($self->get_conf('deftypefnnewline') eq 'on') { $category_tree = {'type' => '_code', 'contents' => [$self->gdt("{category}:\@* ", {'category' => $category})] }; } else { $tree = $self->gdt("\@emph{{type}} \@strong{{name}}", $strings); } } $category_result = $self->convert_tree($category_tree); # with a class, no type } elsif ($command_name eq 'defcv' or ($command_name eq 'deftypecv' and !$command->{'extra'}->{'def_parsed_hash'}->{'type'})) { if ($arguments) { $tree = $self->gdt("{category} of {class}: \@strong{{name}} \@emph{{arguments}}", { 'category' => $category, 'name' => $name, 'class' => $command->{'extra'}->{'def_parsed_hash'}->{'class'}, 'arguments' => $arguments}); } else { $tree = $self->gdt("{category} of {class}: \@strong{{name}}", { 'category' => $category, 'class' => $command->{'extra'}->{'def_parsed_hash'}->{'class'}, 'name' => $name}); } } elsif ($command_name eq 'defop' or ($command_name eq 'deftypeop' and !$command->{'extra'}->{'def_parsed_hash'}->{'type'})) { if ($arguments) { $tree = $self->gdt("{category} on {class}: \@strong{{name}} \@emph{{arguments}}", { 'category' => $category, 'name' => $name, 'class' => $command->{'extra'}->{'def_parsed_hash'}->{'class'}, 'arguments' => $arguments}); } else { $tree = $self->gdt("{category} on {class}: \@strong{{name}}", { 'category' => $category, 'class' => $command->{'extra'}->{'def_parsed_hash'}->{'class'}, 'name' => $name}); } # with a class and a type } elsif ($command_name eq 'deftypeop') { if ($arguments) { my $strings = { 'category' => $category, 'name' => $name, 'class' => $command->{'extra'}->{'def_parsed_hash'}->{'class'}, 'type' => $command->{'extra'}->{'def_parsed_hash'}->{'type'}, 'arguments' => $arguments}; if ($self->get_conf('deftypefnnewline') eq 'on') { $tree = $self->gdt("{category} on {class}:\@* \@emph{{type}}\@* \@strong{{name}} \@emph{{arguments}}", $strings); } else { $tree = $self->gdt("{category} on {class}: \@emph{{type}} \@strong{{name}} \@emph{{arguments}}", $strings); } } else { my $strings = { 'category' => $category, 'type' => $command->{'extra'}->{'def_parsed_hash'}->{'type'}, 'class' => $command->{'extra'}->{'def_parsed_hash'}->{'class'}, 'name' => $name}; if ($self->get_conf('deftypefnnewline') eq 'on') { $tree = $self->gdt("{category} on {class}:\@* \@emph{{type}}\@* \@strong{{name}}", $strings); } else { $tree = $self->gdt("{category} on {class}: \@emph{{type}} \@strong{{name}}", $strings); } } } elsif ($command_name eq 'deftypecv') { if ($arguments) { my $strings = { 'category' => $category, 'name' => $name, 'class' => $command->{'extra'}->{'def_parsed_hash'}->{'class'}, 'type' => $command->{'extra'}->{'def_parsed_hash'}->{'type'}, 'arguments' => $arguments}; if ($self->get_conf('deftypefnnewline') eq 'on') { $tree = $self->gdt("{category} of {class}:\@* \@emph{{type}}\@* \@strong{{name}} \@emph{{arguments}}", $strings); } else { $tree = $self->gdt("{category} of {class}: \@emph{{type}} \@strong{{name}} \@emph{{arguments}}", $strings); } } else { my $strings = { 'category' => $category, 'type' => $command->{'extra'}->{'def_parsed_hash'}->{'type'}, 'class' => $command->{'extra'}->{'def_parsed_hash'}->{'class'}, 'name' => $name}; if ($self->get_conf('deftypefnnewline') eq 'on') { $tree = $self->gdt("{category} of {class}:\@* \@emph{{type}}\@* \@strong{{name}}", $strings); } else { $tree = $self->gdt("{category} of {class}: \@emph{{type}} \@strong{{name}}", $strings); } } } if ($category_result ne '') { $category_result = $self->_attribute_class('span', 'category') .">$category_result"; } my $anchor = $self->_get_copiable_anchor($index_id); return "".$category_result ."".$self->convert_tree({'type' => '_code', 'contents' => [$tree]}) . "$anchor\n"; } else { my $category_prepared = ''; if ($command->{'extra'} and $command->{'extra'}->{'def_parsed_hash'} and %{$command->{'extra'}->{'def_parsed_hash'}}) { my $parsed_definition_category = Texinfo::Common::definition_category ($self, $command); if ($parsed_definition_category) { $category_prepared = $self->convert_tree({'type' => '_code', 'contents' => [$parsed_definition_category]}); } } my $arguments_text = ''; if ($arguments) { $arguments_text = $self->convert_tree({'type' => '_code', 'contents' => $arguments}); $arguments_text = ' ' . $arguments_text . '' if ($arguments_text =~ /\S/); } my $def_type = ''; my $type_name = ''; if ($command->{'extra'}->{'def_parsed_hash'}->{'type'}) { $def_type = $self->convert_tree({'type' => '_code', 'contents' => [$command->{'extra'}->{'def_parsed_hash'}->{'type'}]}); } $type_name = " $def_type" if ($def_type ne ''); my $name = ''; if ($command->{'extra'}->{'def_parsed_hash'}->{'name'}) { $name = $self->convert_tree({'type' => '_code', 'contents' => [$command->{'extra'}->{'def_parsed_hash'}->{'name'}]}); } $type_name .= ' ' . $name . '' if ($name ne ''); $type_name .= $arguments_text; return "" . $type_name . "" . $category_prepared . "\n"; } } sub _get_copiable_anchor { my ($self, $id) = @_; my $result = ''; if ($id and $self->get_conf('COPIABLE_ANCHORS')) { $result = ""; } return $result; } $default_types_conversion{'def_line'} = \&_convert_def_line_type; sub _convert_def_item_type($$$$) { my $self = shift; my $type = shift; my $command = 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 $content = shift; return $content if ($self->in_string()); if (!$self->get_conf('DEF_TABLE')) { return $self->_attribute_class('dl', 'def').">\n". $content ."\n"; } else { return "\n" . $content . "
    \n"; } } foreach my $command (keys(%def_commands)) { $default_commands_conversion{$command} = \&_convert_def_command; } sub _convert_table_item_type($$$$) { my $self = shift; my $type = shift; my $command = shift; my $content = shift; return $content if ($self->in_string()); if ($content =~ /\S/) { return '
    ' . $content . '
    '."\n"; } } $default_types_conversion{'table_item'} = \&_convert_table_item_type; $default_types_conversion{'inter_item'} = \&_convert_table_item_type; # This type is the only one present if there are no elements. It is # therefore used to do the formatting of the element in case there are no # element. sub _convert_root_text_type($$$$) { my $self = shift; my $type = shift; my $command = shift; my $content = shift; my $result = $content; #$result =~ s/^\s*//; # if there is no element, the parent should not be an element if (!$command->{'parent'} or !$command->{'parent'}->{'type'} or $command->{'parent'}->{'type'} ne 'element') { $result .= &{$self->{'format_footnotes_text'}}($self); $result .= $self->get_conf('DEFAULT_RULE') ."\n", if ($self->get_conf('PROGRAM_NAME_IN_FOOTER') and defined($self->get_conf('DEFAULT_RULE')) and !$self->in_string()); } return $result; } $default_types_conversion{'text_root'} = \&_convert_root_text_type; sub _contents_shortcontents_in_title($) { my $self = shift; my $result = ''; if ($self->{'structuring'} and $self->{'structuring'}->{'sectioning_root'} and scalar(@{$self->{'structuring'}->{'sections_list'}}) > 1 and $self->get_conf('CONTENTS_OUTPUT_LOCATION') eq 'after_title') { foreach my $command ('contents', 'shortcontents') { if ($self->get_conf($command)) { my $contents_text = $self->_contents_inline_element($command, 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; if ($self->{'extra'}->{'titlepage'}) { $titlepage_text = $self->convert_tree({'contents' => $self->{'extra'}->{'titlepage'}->{'contents'}}); } elsif ($self->{'simpletitle_tree'}) { my $title_text = $self->convert_tree_new_formatting_context( $self->{'simpletitle_tree'}, 'simpletitle_string'); $titlepage_text = &{$self->{'format_heading_text'}}($self, 'settitle', $title_text, 0, {'cmdname' => 'settitle', 'contents' => $self->{'simpletitle_tree'}->{'contents'}}); } my $result = ''; $result .= $titlepage_text.$self->get_conf('DEFAULT_RULE')."\n" if (defined($titlepage_text)); $result .= $self->_contents_shortcontents_in_title(); return $result; } sub _print_title($) { my $self = shift; my $result = ''; if ($self->get_conf('SHOW_TITLE')) { if ($self->get_conf('USE_TITLEPAGE_FOR_TITLE')) { $result .= &{$self->{'format_titlepage'}}($self); } else { if ($self->{'simpletitle_tree'}) { my $title_text = $self->convert_tree_new_formatting_context( $self->{'simpletitle_tree'}, 'simpletitle_string'); $result .= &{$self->{'format_heading_text'}}($self, 'settitle', $title_text, 0, {'cmdname' => 'settitle', 'contents' => $self->{'simpletitle_tree'}->{'contents'}}); } $result .= $self->_contents_shortcontents_in_title(); } } return $result; } # Function for converting the top-level elements in the conversion: a section # or a node. $ELEMENT was created in this module (in _prepare_elements), with # type 'element' (it's not a tree element created by the parser). $CONTENT # is the contents of the node/section, already converted. sub _convert_element_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 $special_element; if ($element->{'extra'}->{'special_element'}) { $special_element = $element->{'extra'}->{'special_element'}; $result .= join('', $self->close_registered_sections_level(0)); my $id = $self->command_id($element); $result .= "
    get_conf('HEADERS') # first in page or $self->{'counter_in_file'}->{$element->{'filename'}} == 1) { $result .= &{$self->{'format_navigation_header'}}($self, $self->get_conf('MISC_BUTTONS'), undef, $element); } my $heading = $self->command_text($element); my $element_name = $element->{'extra'}->{'special_element'}; my $class = $self->get_conf('SPECIAL_ELEMENTS_CLASS')->{$element_name}; my $level = $self->get_conf('CHAPTER_HEADER_LEVEL'); if ($element_name eq 'Footnotes') { $level = $self->get_conf('FOOTNOTE_SEPARATE_HEADER_LEVEL'); } $result .= &{$self->{'format_heading_text'}}($self, $class.'-heading', $heading, $level)."\n"; my $special_element_body .= &{$self->{'format_special_element_body'}} ($self, $special_element, $element); # This may happen with footnotes in regions that are not expanded, # like @copying or @titlepage if ($special_element_body eq '') { return ''; } $result .= $special_element_body . '
    '; } elsif (!$element->{'element_prev'}) { $result .= $self->_print_title(); if (!$element->{'element_next'}) { # only one element $result .= $content; $result .= &{$self->{'format_footnotes_text'}}($self); $result .= $self->get_conf('DEFAULT_RULE'); $result .= join('', $self->close_registered_sections_level(0)); return $result; } } $result .= $content unless ($special_element); $result .= &{$self->{'format_element_footer'}}($self, $type, $element, $content); return $result; } sub _default_format_element_footer($$$$) { my $self = shift; my $type = shift; my $element = shift; my $content = shift; my $result = ''; my $is_top = $self->element_is_top($element); my $next_is_top = ($element->{'element_next'} and $self->element_is_top($element->{'element_next'})); my $next_is_special = (defined($element->{'element_next'}) and $element->{'element_next'}->{'extra'}->{'special_element'}); my $end_page = (!$element->{'element_next'} or (defined($element->{'filename'}) and $element->{'filename'} ne $element->{'element_next'}->{'filename'} and $self->{'file_counters'}->{$element->{'filename'}} == 1)); my $is_special = $element->{'extra'}->{'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')) { my @cnt = split(/\W*\s+\W*/, $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 end_page # except that the file counter needs not be 1 if ((!$element->{'element_next'} or (defined($element->{'filename'}) and $element->{'filename'} ne $element->{'element_next'}->{'filename'})) and $self->get_conf('footnotestyle') eq 'end') { $result .= &{$self->{'format_footnotes_text'}}($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) { $result .= &{$self->{'format_navigation_header_panel'}}($self, $buttons, undef, $element); } return $result; } $default_types_conversion{'element'} = \&_convert_element_type; sub _new_document_context($$) { my $self = shift; my $cmdname = shift; push @{$self->{'document_context'}}, {'cmdname' => $cmdname, 'formatting_context' => [{'cmdname' => $cmdname}], 'composition_context' => ['raggedright'], 'formats' => [], 'monospace' => [0], }; } # Functions accessed with e.g. 'format_heading_text'. my %default_formatting_references = ( 'format_heading_text' => \&_default_format_heading_text, 'format_comment' => \&_default_format_comment, 'format_protect_text' => \&_default_format_protect_text, 'format_css_lines' => \&_default_format_css_lines, 'format_begin_file' => \&_default_format_begin_file, 'format_node_redirection_page' => \&_default_format_node_redirection_page, 'format_end_file' => \&_default_format_end_file, 'format_special_element_body' => \&_default_format_special_element_body, 'format_footnotes_text' => \&_default_format_footnotes_text, 'format_program_string' => \&_default_format_program_string, 'format_titlepage' => \&_default_format_titlepage, 'format_navigation_header' => \&_default_format_navigation_header, 'format_navigation_header_panel' => \&_default_format_navigation_header_panel, 'format_element_header' => \&_default_format_element_header, 'format_element_footer' => \&_default_format_element_footer, 'format_button' => \&_default_format_button, 'format_button_icon_img' => \&_default_format_button_icon_img, 'format_contents' => \&_default_format_contents, 'format_frame_files' => \&_default_format_frame_files, ); sub _use_entity_is_entity($$) { my $self = shift; my $text = shift; return 0 if (!$self->get_conf('ENABLE_ENCODING_USE_ENTITY')); return 1 if ($text =~ /^&/ and $text =~ /;$/); } sub _complete_commands_formatting($$) { my $self = shift; my $command = shift; if (!defined ($self->{'commands_formatting'}->{'normal'}->{$command})) { $self->{'commands_formatting'}->{'normal'}->{$command} = ''; } if (!defined ($self->{'commands_formatting'}->{'preformatted'}->{$command})) { $self->{'commands_formatting'}->{'preformatted'}->{$command} = $self->{'commands_formatting'}->{'normal'}->{$command}; } if (!defined ($self->{'commands_formatting'}->{'string'}->{$command})) { $self->{'commands_formatting'}->{'string'}->{$command} = $self->{'commands_formatting'}->{'preformatted'}->{$command}; } } my %htmlxref_entries = ( 'node' => [ 'node', 'section', 'chapter', 'mono' ], 'section' => [ 'section', 'chapter','node', 'mono' ], 'chapter' => [ 'chapter', 'section', 'node', 'mono' ], 'mono' => [ 'mono', 'chapter', 'section', 'node' ], ); sub _parse_htmlxref_files($$) { my $self = shift; my $files = shift; my $htmlxref; foreach my $file (@$files) { my ($fname) = $file; if ($self->get_conf('TEST')) { $fname =~ s/([^\/]+\/)*//; # strip directories for out-of-source builds } print STDERR "html refs config file: $file\n" if ($self->get_conf('DEBUG')); unless (open (HTMLXREF, $file)) { $self->document_warn( sprintf(__("could not open html refs config file %s: %s"), $file, $!)); 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->file_line_warn(__("missing type"), $fname, $line_nr); next; } elsif (!defined($htmlxref_entries{$split_or_mono})) { $self->file_line_warn(sprintf(__("unrecognized type: %s"), $split_or_mono), $fname, $line_nr); next; } my $href = shift @htmlxref; next if (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}->{$split_or_mono} = $href; } if (!close (HTMLXREF)) { $self->document_warn(sprintf(__( "error on closing html refs config file %s: %s"), $file, $!)); } } return $htmlxref; } sub _load_htmlxref_files { my ($self) = @_; 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'); my $input_directory = $self->{'info'}->{'input_directory'}; if (defined($input_directory) and $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, '.'; my @texinfo_htmlxref_files; my $init_file_from_conf = $self->get_conf('HTMLXREF'); if ($init_file_from_conf) { if (!$self->get_conf('TEST')) { @texinfo_htmlxref_files = ( $init_file_from_conf ); } else { @texinfo_htmlxref_files = Texinfo::Common::locate_init_file ($init_file_from_conf, \@htmlxref_dirs, 1); } } elsif (!$self->get_conf('TEST')) { @texinfo_htmlxref_files = Texinfo::Common::locate_init_file ('htmlxref.cnf', \@htmlxref_dirs, 1); } $self->{'htmlxref_files'} = \@texinfo_htmlxref_files; $self->{'htmlxref'} = {}; if ($self->{'htmlxref_files'}) { $self->{'htmlxref'} = _parse_htmlxref_files($self, $self->{'htmlxref_files'}); } } sub converter_initialize($) { my $self = shift; $foot_num = 0; $foot_lines = ''; %formatted_index_entries = (); %footnote_id_numbers = (); %{$self->{'css_map'}} = %css_map; _load_htmlxref_files($self); foreach my $type (keys(%default_types_conversion)) { if (exists($Texinfo::Config::texinfo_types_conversion{$type})) { $self->{'types_conversion'}->{$type} = $Texinfo::Config::texinfo_types_conversion{$type}; } else { $self->{'types_conversion'}->{$type} = $default_types_conversion{$type}; } } # FIXME API with a function call? Used in cvs.init. foreach my $type (keys(%default_code_types)) { $self->{'code_types'}->{$type} = $default_code_types{$type}; } if ($Texinfo::Config::texinfo_code_types) { foreach my $type (keys(%$Texinfo::Config::texinfo_code_types)) { $self->{'code_types'}->{$type} = $Texinfo::Config::texinfo_code_types->{$type}; } } # FIXME put value in a category in Texinfo::Common? foreach my $command (keys(%misc_commands), keys(%brace_commands), keys (%block_commands), keys(%no_brace_commands), 'value') { if (exists($Texinfo::Config::texinfo_commands_conversion{$command})) { $self->{'commands_conversion'}->{$command} = $Texinfo::Config::texinfo_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}; if ($command eq 'menu' and $self->get_conf('SIMPLE_MENU')) { $self->{'commands_conversion'}->{$command} = $default_commands_conversion{'example'}; } } } } foreach my $context ('normal', 'preformatted', 'string') { foreach my $command (keys(%{$default_commands_formatting{'normal'}})) { if (exists ($Texinfo::Config::commands_formatting{$context}->{$command})) { $self->{'commands_formatting'}->{$context}->{$command} = $Texinfo::Config::commands_formatting{$context}->{$command}; } else { if (defined($default_commands_formatting{$context}->{$command})) { if ($self->get_conf('ENABLE_ENCODING') and Texinfo::Convert::Unicode::unicode_for_brace_no_arg_command( $command, $self->get_conf('OUTPUT_ENCODING_NAME')) and !$self->_use_entity_is_entity($default_commands_formatting{$context}->{$command})) { $self->{'commands_formatting'}->{$context}->{$command} = Texinfo::Convert::Unicode::unicode_for_brace_no_arg_command( $command, $self->get_conf('OUTPUT_ENCODING_NAME')); } else { $self->{'commands_formatting'}->{$context}->{$command} = $default_commands_formatting{$context}->{$command}; } } } if (exists ($Texinfo::Config::commands_translation{$context}->{$command})) { $self->{'commands_translation'}->{$context}->{$command} = $Texinfo::Config::commands_translation{$context}->{$command}; delete $self->{'translated_commands'}->{$command}; } elsif (defined($default_commands_translation{$context}->{$command})) { $self->{'commands_translation'}->{$context}->{$command} = $default_commands_translation{$context}->{$command}; delete $self->{'translated_commands'}->{$command}; } } } # set sane defaults in case there is none and the default formatting # function is used foreach my $command (keys(%{$default_commands_formatting{'normal'}})) { if ($self->{'commands_conversion'}->{$command} and $self->{'commands_conversion'}->{$command} eq $default_commands_conversion{$command}) { $self->_complete_commands_formatting($command); } } foreach my $context (keys(%style_commands_formatting)) { foreach my $command (keys(%{$style_commands_formatting{$context}})) { if (exists ($Texinfo::Config::style_commands_formatting{$context}->{$command})) { $self->{'style_commands_formatting'}->{$context}->{$command} = $Texinfo::Config::style_commands_formatting{$context}->{$command}; } elsif (exists($style_commands_formatting{$context}->{$command})) { $self->{'style_commands_formatting'}->{$context}->{$command} = $style_commands_formatting{$context}->{$command}; } } } foreach my $command (keys %{$self->{'commands_conversion'}}) { if (exists($Texinfo::Config::commands_args{$command})) { $self->{'commands_args'}->{$command} = $Texinfo::Config::commands_args{$command}; } elsif (exists($default_commands_args{$command})) { $self->{'commands_args'}->{$command} = $default_commands_args{$command}; } } foreach my $formatting_reference (keys(%default_formatting_references)) { $self->{'default_formatting_functions'}->{$formatting_reference} = $default_formatting_references{$formatting_reference}; if (defined($Texinfo::Config::texinfo_formatting_references{$formatting_reference})) { $self->{$formatting_reference} = $Texinfo::Config::texinfo_formatting_references{$formatting_reference}; } else { $self->{$formatting_reference} = $default_formatting_references{$formatting_reference}; } } $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 $element = shift; my $explanation = shift; return $self->_convert($element, $explanation); } 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; return if ($self->get_conf('NO_CSS')); my $css_refs = $self->get_conf('CSS_REFS'); return if (!@{$self->{'css_import_lines'}} and !@{$self->{'css_rule_lines'}} and !keys(%{$self->{'css_map'}}) and !@$css_refs); my $css_text = "\n"; foreach my $ref (@$css_refs) { $css_text .= "\n"; } $self->set_conf('CSS_LINES', $css_text); } sub _process_css_file($$$) { my $self = shift; my $fh =shift; my $file = shift; my $in_rules = 0; my $in_comment = 0; my $in_import = 0; my $in_string = 0; my $rules = []; my $imports = []; my $line_nr = 0; while (my $line = <$fh>) { $line_nr++; #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; } } } #file_line_warn (__("string not closed in css file"), $file) if ($in_string); #file_line_warn (__("--css-file ended in comment"), $file) if ($in_comment); #file_line_warn (__("\@import not finished in css file"), $file) if ($in_import and !$in_comment and !$in_string); $self->file_line_warn(sprintf(__("string not closed in css file"), $file, $line_nr)) if ($in_string); $self->file_line_warn(sprintf(__("--css-include ended in comment"), $file, $line_nr)) if ($in_comment); $self->file_line_warn(sprintf(__("\@import not finished in css file"), $file, $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)) { $self->document_warn(sprintf( __("CSS file %s not found"), $file)); next; } # FIXME use open_out? unless (open (CSSFILE, $css_file)) { $self->document_warn(sprintf(__( "could not open --include-file %s: %s"), $css_file, $!)); 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)) { $self->document_warn(sprintf(__("error on closing CSS file %s: %s"), $css_file, $!)); } 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 node, as well as the anchor within # that file to link to that node. Argument is the 'extra' value on # an element hash, or something that looks like it. sub _node_id_file($$) { my $self = shift; my $node_info = shift; my $target; my $normalized; if ($node_info->{'normalized'}) { $normalized = $node_info->{'normalized'}; } elsif ($node_info->{'node_content'}) { $normalized = Texinfo::Convert::NodeNameNormalization::normalize_node ( { 'contents' => $node_info->{'node_content'} }); } if (defined($normalized)) { $target = _normalized_to_id($normalized); } else { $target = ''; } # to find out the Top node, one could check $node_info->{'normalized'} if (defined($Texinfo::Config::node_target_name)) { $target = &$Texinfo::Config::node_target_name($node_info, $target); } my $filename = $self->_node_filename($node_info); return ($filename, $target); } sub _new_sectioning_command_target($$) { my $self = shift; my $command = shift; my ($normalized_name, $filename) = $self->_sectioning_command_normalized_filename($command); my $target_base = _normalized_to_id($normalized_name); if ($target_base !~ /\S/ and $command->{'cmdname'} eq 'top' and defined($self->{'misc_elements_targets'}->{'Top'})) { $target_base = $self->{'misc_elements_targets'}->{'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 ($Texinfo::Common::sectioning_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($Texinfo::Config::sectioning_command_target_name)) { ($target, $target_contents, $target_shortcontents, $filename) = &$Texinfo::Config::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 2 unrelated things. # * The targets and id of sectioning elements # * the target, id and normalized filename of 'labels', ie everything that # may be the target of a ref, like @node, @float, @anchor... # conversion to HTML is done on-demand, upon call to command_text. # Note that 'node_filename', which is set here for Top too, is not # used later for Top, see the NOTE below. sub _set_root_commands_targets_node_files($$) { my $self = shift; my $elements = 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 $root_command (values(%{$self->{'labels'}})) { my ($filename, $target) = $self->_node_id_file($root_command->{'extra'}); $filename .= $extension; if (defined($Texinfo::Config::node_file_name)) { $filename = &$Texinfo::Config::node_file_name($self, $root_command, $filename); } if ($self->get_conf('DEBUG')) { print STDERR "Register label($root_command) $target, $filename\n"; } $self->{'targets'}->{$root_command} = {'target' => $target, 'node_filename' => $filename}; $self->{'seen_ids'}->{$target} = 1; } } if ($elements) { foreach my $element (@$elements) { foreach my $root_command(@{$element->{'contents'}}) { # this happens for type 'text_root' which precedes the # root commands. The target may also already be set for top node. next if (!defined($root_command->{'cmdname'}) or $self->{'targets'}->{$root_command}); if ($Texinfo::Common::sectioning_commands{$root_command->{'cmdname'}}) { $self->_new_sectioning_command_target($root_command); } } } } } sub _get_element($$;$); # If $find_container is set, the element that holds the command is found, # otherwise the element that holds the command content is found. This is # mostly relevant for footnote only. sub _get_element($$;$) { my $self = shift; my $command = shift; my $find_container = shift; my $current = $command; my ($element, $root_command); while (1) { if ($current->{'type'}) { if ($current->{'type'} eq 'element') { return ($current, $root_command); } } if ($current->{'cmdname'}) { if ($root_commands{$current->{'cmdname'}}) { $root_command = $current; return ($element, $root_command) if defined($element); } elsif ($region_commands{$current->{'cmdname'}}) { if ($current->{'cmdname'} eq 'copying' and $self->{'extra'} and $self->{'extra'}->{'insertcopying'}) { foreach my $insertcopying(@{$self->{'extra'}->{'insertcopying'}}) { my ($element, $root_command) = $self->_get_element($insertcopying, $find_container); return ($element, $root_command) if (defined($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->{'elements'}->[0]) { return ($self->{'elements'}->[0], $self->{'elements'}->[0]->{'extra'}->{'element_command'}); } die "Problem $element, $root_command" if (defined($element) or defined($root_command)); return (undef, undef); } elsif ($current->{'cmdname'} eq 'footnote' and $self->{'special_elements_types'}->{'Footnotes'} and $find_container) { # in that case there is no root_command $element = $self->{'special_elements_types'}->{'Footnotes'}; return ($element); } } if ($current->{'parent'}) { $current = $current->{'parent'}; } else { return ($element, $root_command); } } } sub _set_pages_files($$) { my $self = shift; my $elements = shift; my $special_elements = shift; # Ensure that the document has pages return undef if (!defined($elements) or !@$elements); my $extension = ''; $extension = '.'.$self->get_conf('EXTENSION') if (defined($self->get_conf('EXTENSION')) and $self->get_conf('EXTENSION') ne ''); if (!$self->get_conf('SPLIT')) { foreach my $element (@$elements) { if (!defined($element->{'filename'})) { $element->{'filename'} = $self->{'output_filename'}; $element->{'out_filename'} = $self->{'output_file'}; } } } else { my $node_top; #my $section_top; $node_top = $self->{'labels'}->{'Top'} if ($self->{'labels'}); #$section_top = $self->{'extra'}->{'top'} if ($self->{'extra'}); my $top_node_filename = $self->_top_node_filename(); # first determine the top node file name. if ($node_top and defined($top_node_filename)) { my ($node_top_element) = $self->_get_element($node_top); die "BUG: No element for top node" if (!defined($node_top)); $self->_set_element_file($node_top_element, $top_node_filename); } my $file_nr = 0; my $previous_page; foreach my $element(@$elements) { # For Top node. next if (defined($element->{'filename'})); if (!$element->{'extra'}->{'first_in_page'}) { cluck ("No first_in_page for $element\n"); } if (!defined($element->{'extra'}->{'first_in_page'}->{'filename'})) { my $file_element = $element->{'extra'}->{'first_in_page'}; foreach my $root_command (@{$file_element->{'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; } else { if (!defined($self->{'targets'}->{$root_command}) or !defined($self->{'targets'}->{$root_command}->{'node_filename'})) { # Could have been a double node, thus use equivalent node. # However since double nodes are not normalized, in fact it # never happens. $root_command = $self->{'labels'}->{$root_command->{'extra'}->{'normalized'}}; } $node_filename = $self->{'targets'}->{$root_command}->{'node_filename'}; } $self->_set_element_file($file_element, $node_filename); last; } } if (!defined($file_element->{'filename'})) { # use section to do the file name if there is no node my $command = $self->element_command($file_element); if ($command) { if ($command->{'cmdname'} eq 'top' and !$node_top and defined($top_node_filename)) { $self->_set_element_file($file_element, $top_node_filename); } else { $self->_set_element_file($file_element, $self->{'targets'}->{$command}->{'section_filename'}); } } else { # when everything else has failed if ($file_nr == 0 and !$node_top and defined($top_node_filename)) { $self->_set_element_file($file_element, $top_node_filename); } else { my $filename = $self->{'document_name'} . "_$file_nr"; $filename .= $extension; $self->_set_element_file($element, $filename); } $file_nr++; } } } $element->{'filename'} = $element->{'extra'}->{'first_in_page'}->{'filename'}; $element->{'out_filename'} = $element->{'extra'}->{'first_in_page'}->{'out_filename'}; } } foreach my $element (@$elements) { if (defined($Texinfo::Config::element_file_name)) { # NOTE the information that it is associated with @top or @node Top # may be determined with $self->element_is_top($element); my $filename = &$Texinfo::Config::element_file_name($self, $element, $element->{'filename'}); $self->_set_element_file($element, $filename) if (defined($filename)); } $self->{'file_counters'}->{$element->{'filename'}}++; print STDERR "Page $element ".Texinfo::Structuring::_print_element_command_texi($element).": $element->{'filename'}($self->{'file_counters'}->{$element->{'filename'}})\n" if ($self->get_conf('DEBUG')); } if ($special_elements) { my $previous_element = $elements->[-1]; foreach my $element (@$special_elements) { my $filename = $self->{'targets'}->{$element}->{'misc_filename'}; if (defined($filename)) { $self->_set_element_file($element, $filename); $self->{'file_counters'}->{$element->{'filename'}}++; print STDERR "Special page $element: $element->{'filename'}($self->{'file_counters'}->{$element->{'filename'}})\n" if ($self->get_conf('DEBUG')); } $element->{'element_prev'} = $previous_element; $previous_element->{'element_next'} = $element; $previous_element = $element; } } } # $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. sub _prepare_elements($$) { my $self = shift; my $root = shift; my $elements; # do that now to have it available for formatting # NOTE this calls Convert::Converter::_informative_command on all the # @informative_global commands. # Thus sets among others language and encodings. $self->_set_global_multiple_commands(-1); $self->_translate_names(); if ($self->get_conf('USE_NODES')) { $elements = Texinfo::Structuring::split_by_node($root); } else { $elements = Texinfo::Structuring::split_by_section($root); } $self->{'elements'} = $elements if (defined($elements)); # This may be done as soon as elements are available. $self->_prepare_global_targets($elements); # Do that before the other elements, to be sure that special page ids # are registered before elements id are. my $special_elements = $self->_prepare_special_elements($elements); $self->{'special_elements'} = $special_elements if (defined($special_elements)); #if ($elements) { # foreach my $element(@{$elements}) { # print STDERR "ELEMENT $element->{'type'}: $element\n"; # } #} $self->_set_root_commands_targets_node_files($elements); return ($elements, $special_elements); } sub _prepare_special_elements($$) { my $self = shift; my $elements = shift; my %do_special; # FIXME let the user decide how @*contents are treated? if ($self->{'structuring'} and $self->{'structuring'}->{'sectioning_root'} and scalar(@{$self->{'structuring'}->{'sections_list'}}) > 1) { foreach my $cmdname ('contents', 'shortcontents') { my $type = $contents_command_element_name{$cmdname}; if ($self->get_conf($cmdname)) { if ($self->get_conf('CONTENTS_OUTPUT_LOCATION') eq 'separate_element') { $do_special{$type} = 1; } } } } if ($self->{'extra'}->{'footnote'} and $self->get_conf('footnotestyle') eq 'separate' and $elements and scalar(@$elements) > 1) { $do_special{'Footnotes'} = 1; } if ((!defined($self->get_conf('DO_ABOUT')) and $elements and scalar(@$elements) > 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 = []; foreach my $type (@{$self->{'misc_elements_order'}}) { next unless ($do_special{$type}); my $element = {'type' => 'element', 'extra' => {'special_element' => $type, }}; $element->{'extra'}->{'directions'}->{'This'} = $element; $self->{'special_elements_types'}->{$type} = $element; push @$special_elements, $element; my $target = $self->{'misc_elements_targets'}->{$type}; my $default_filename; if ($self->get_conf('SPLIT') or !$self->get_conf('MONOLITHIC')) { $default_filename = $self->{'document_name'}. $self->{'misc_pages_file_string'}->{$type}; $default_filename .= '.'.$extension if (defined($extension)); } else { $default_filename = undef; } my $filename; if (defined($Texinfo::Config::special_element_target_file_name)) { ($target, $filename) = &$Texinfo::Config::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 $element $type: target $target,\n". " filename $fileout\n" } if ($self->get_conf('SPLIT') or !$self->get_conf('MONOLITHIC') or (defined($filename) ne defined($default_filename)) or (defined($filename) and $filename ne $default_filename)) { $self->_set_element_file($element, $filename); print STDERR "NEW page for $type ($filename)\n" if ($self->get_conf('DEBUG')); } $self->{'targets'}->{$element} = {'target' => $target, 'misc_filename' => $filename, }; $self->{'seen_ids'}->{$target} = 1; } if ($self->get_conf('FRAMES')) { foreach my $type (keys(%{$self->{'frame_pages_file_string'}})) { my $default_filename; $default_filename = $self->{'document_name'}. $self->{'frame_pages_file_string'}->{$type}; $default_filename .= '.'.$extension if (defined($extension)); my $element = {'type' => 'element', 'extra' => {'special_element' => $type, }}; # only the filename is used my ($target, $filename); if (defined($Texinfo::Config::special_element_target_file_name)) { ($target, $filename) = &$Texinfo::Config::special_element_target_file_name( $self, $element, $target, $default_filename); } $filename = $default_filename if (!defined($filename)); $self->{'frame_pages_filenames'}->{$type} = $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 $type = $contents_command_element_name{$cmdname}; if ($self->get_conf($cmdname)) { my $default_filename; if ($self->get_conf('CONTENTS_OUTPUT_LOCATION') eq 'after_title') { if ($self->{'elements'}) { $default_filename = $self->{'elements'}->[0]->{'filename'}; } } elsif ($self->get_conf('CONTENTS_OUTPUT_LOCATION') eq 'after_top') { my $section_top = undef; if ($self->{'extra'} and $self->{'extra'}->{'top'}) { $section_top = $self->{'extra'}->{'top'}; $default_filename = $self->command_filename($section_top) } } elsif ($self->get_conf('CONTENTS_OUTPUT_LOCATION') eq 'inline') { if ($self->{'extra'} and $self->{'extra'}->{$cmdname}) { foreach my $command(@{$self->{'extra'}->{$cmdname}}) { my ($element, $root_command) = $self->_get_element($command); if (defined($element)) { $default_filename = $element->{'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 $element = {'type' => 'element', 'extra' => {'special_element' => $type}}; $self->{'special_elements_types'}->{$type} = $element; my $target = $self->{'misc_elements_targets'}->{$type}; my $filename; if (defined($Texinfo::Config::special_element_target_file_name)) { ($target, $filename) = &$Texinfo::Config::special_element_target_file_name( $self, $element, $target, $default_filename); } $filename = $default_filename if (!defined($filename)); print STDERR "Add content $element $type: target $target,\n". " filename $filename\n" if ($self->get_conf('DEBUG')); $self->{'targets'}->{$element} = {'target' => $target, 'misc_filename' => $filename, 'filename' => $filename, }; } } } } # Associate elements with the global targets, First, Last, Top, Index. sub _prepare_global_targets($$) { my $self = shift; my $elements = shift; $self->{'global_target_elements'}->{'First'} = $elements->[0]; $self->{'global_target_elements'}->{'Last'} = $elements->[-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->{'extra'} and $self->{'extra'}->{'printindex'}) { my ($element, $root_command) = $self->_get_element($self->{'extra'}->{'printindex'}->[0]); if (defined($element)) { if ($root_command and $root_command->{'cmdname'} eq 'node' and $element->{'extra'}->{'section'}) { $root_command = $element->{'extra'}->{'section'}; } if ($root_command and $root_command->{'cmdname'} ne 'node') { while ($root_command->{'level'} > 1 and $root_command->{'section_up'} and $root_command->{'section_up'}->{'parent'}) { $root_command = $root_command->{'section_up'}; $element = $root_command->{'parent'}; } } $self->{'global_target_elements'}->{'Index'} = $element; } } my $node_top; $node_top = $self->{'labels'}->{'Top'} if ($self->{'labels'}); my $section_top; $section_top = $self->{'extra'}->{'top'} if ($self->{'extra'}); if ($section_top) { $self->{'global_target_elements'}->{'Top'} = $section_top->{'parent'}; } elsif ($node_top) { my $element_top = $node_top->{'parent'}; if (!$element_top) { die "No parent for node_top: ".Texinfo::Common::_print_current($node_top); } $self->{'global_target_elements'}->{'Top'} = $element_top; } else { $self->{'global_target_elements'}->{'Top'} = $elements->[0]; } if ($self->get_conf('DEBUG')) { print STDERR "GLOBAL DIRECTIONS:\n"; foreach my $global_direction (@global_directions) { if (defined($self->{'global_target_elements'}->{$global_direction})) { print STDERR "$global_direction($self->{'global_target_elements'}->{$global_direction}): ". Texinfo::Structuring::_print_element_command_texi( $self->{'global_target_elements'}->{$global_direction})."\n"; } } } } sub _prepare_index_entries($) { my $self = shift; if ($self->{'parser'}) { my $no_unidecode; $no_unidecode = 1 if (defined($self->get_conf('USE_UNIDECODE')) and !$self->get_conf('USE_UNIDECODE')); my $index_names = $self->{'parser'}->indices_information(); $self->{'index_names'} = $index_names; my $merged_index_entries = Texinfo::Structuring::merge_indices($index_names); $self->{'index_entries_by_letter'} = Texinfo::Structuring::sort_indices_by_letter ($self->{'parser'}, $merged_index_entries, $index_names); $self->{'index_entries'} = $merged_index_entries; foreach my $index_name (sort(keys(%$index_names))) { foreach my $index_entry (@{$index_names->{$index_name}->{'index_entries'}}) { my $region = ''; $region = "$index_entry->{'region'}->{'cmdname'}-" if (defined($index_entry->{'region'})); my @contents = @{$index_entry->{'content_normalized'}}; my $trimmed_contents = Texinfo::Common::trim_spaces_comment_from_content(\@contents); my $normalized_index = Texinfo::Convert::NodeNameNormalization::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; $self->{'targets'}->{$index_entry->{'command'}} = {'target' => $target, }; } } } } sub _prepare_footnotes($) { my $self = shift; if ($self->{'extra'}->{'footnote'}) { my $footnote_nr = 0; foreach my $footnote (@{$self->{'extra'}->{'footnote'}}) { $footnote_nr++; my $nr = $footnote_nr; my $footid = $footid_base.$nr; 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 }; print STDERR "Enter footnote $footnote: target $footid, nr $footnote_nr\n" .Texinfo::Convert::Texinfo::convert($footnote)."\n" if ($self->get_conf('DEBUG')); } } } # TODO this encapsulates some information. # The encapsulation and API should be more consistent for # the overall module. sub _htmlxref($$) { my $self = shift; my $file = shift; return $self->{'htmlxref'}->{$file}; } sub _external_node_href($$$$) { my $self = shift; my $external_node = shift; my $filename = shift; my $link_command = shift; #print STDERR "external_node: ".join('|', keys(%$external_node))."\n"; my ($target_filebase, $target) = $self->_node_id_file($external_node); my $xml_target = _normalized_to_id($target); my $default_target_split = $self->get_conf('EXTERNAL_CROSSREF_SPLIT'); my $external_file_extension = '.html'; my $target_split; my $file; if ($external_node->{'manual_content'}) { my $manual_name = Texinfo::Convert::Text::convert( {'contents' => $external_node->{'manual_content'}}, { 'code' => 1, Texinfo::Common::_convert_text_options($self)}); 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 = $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($link_command) and $link_command->{'line_nr'}) { $self->line_warn(sprintf(__( "no htmlxref.cnf entry found for `%s'"), $manual_name), $link_command->{'line_nr'}); } elsif (!$self->{'check_htmlxref_already_warned'}->{$manual_name}) { $self->document_warn(sprintf(__( "no htmlxref.cnf entry found for `%s'"), $manual_name), ); } $self->{'check_htmlxref_already_warned'}->{$manual_name} = 1; } } if ($target_split) { if (defined($href)) { $file = $href; } elsif (defined($self->get_conf('EXTERNAL_DIR'))) { $file = $self->get_conf('EXTERNAL_DIR')."/$manual_base"; } elsif ($self->get_conf('SPLIT')) { $file = "../$manual_base"; } $file .= "/"; } else {# target not split if (defined($href)) { $file = $href; } else { if (defined($self->get_conf('EXTERNAL_DIR'))) { $file = $self->get_conf('EXTERNAL_DIR')."/$manual_base"; } elsif ($self->get_conf('SPLIT')) { $file = "../$manual_base"; } else { $file = $manual_base; } $file .= $external_file_extension; } } } 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;# . '#Top'; } } 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; } } my %valid_types = ( 'href' => 1, 'string' => 1, 'text' => 1, 'tree' => 1, 'target' => 1, 'node' => 1, 'section' => 1 ); foreach my $no_number_type ('text', 'tree', 'string') { $valid_types{$no_number_type .'_nonumber'} = 1; } # sub _element_direction($SELF, $ELEMENT, $DIRECTION, $TYPE, $FILENAME) # # Return text used for linking from $ELEMENT in direction $DIRECTION. The # text returned depends on $TYPE. # # $element can be undef. # $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 _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 _element_direction($$$$;$) { my $self = shift; my $element = shift; my $direction = shift; my $type = shift; my $filename = shift; my $element_target; my $command; my $target; $filename = $self->{'current_filename'} if (!defined($filename)); if (!$valid_types{$type}) { print STDERR "Incorrect type $type in _element_direction call\n"; return undef; } if ($self->{'global_target_elements'}->{$direction}) { $element_target = $self->{'global_target_elements'}->{$direction}; } elsif ($element and $element->{'extra'} and $element->{'extra'}->{'directions'} and $element->{'extra'}->{'directions'}->{$direction}) { $element_target = $element->{'extra'}->{'directions'}->{$direction}; # 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($element) or ($element and $self->element_is_top($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 ''; } } if ($element_target) { ######## debug if (!$element_target->{'type'}) { die "No type for element_target $direction $element_target: " . Texinfo::Common::_print_current_keys($element_target) . "directions :". Texinfo::Structuring::_print_directions($element); } ######## if ($element_target->{'type'} eq 'external_node') { my $external_node = $element_target->{'extra'}; if ($type eq 'href') { return $self->command_href($external_node, $filename); } 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') { $command = $element_target->{'extra'}->{'node'}; $target = $self->{'targets'}->{$command} if ($command); $type = 'text'; } elsif ($type eq 'section') { $command = $element_target->{'extra'}->{'section'}; $target = $self->{'targets'}->{$command} if ($command); $type = 'text_nonumber'; } else { if ($element_target->{'extra'}->{'special_element'}) { $command = $element_target; } else { $command = $element_target->{'extra'}->{'element_command'}; } if ($type eq 'href') { if (defined($command)) { return $self->command_href($command, $filename); } else { return ''; } } $target = $self->{'targets'}->{$command} if ($command); } } elsif ($self->special_element($direction)) { $element_target = $self->special_element($direction); $command = $element_target; if ($type eq 'href') { return $self->command_href($element_target, $filename); } $target = $self->{'targets'}->{$element_target}; } else { return undef; } if (exists($target->{$type})) { return $target->{$type}; } elsif ($type eq 'target') { return undef; } elsif ($command) { return $self->command_text($command, $type); } } # Output a list of the nodes immediately below this one sub _mini_toc { my ($self, $command) = @_; my $filename = $self->{'current_filename'}; my $result = ''; my $entry_index = 0; my $accesskey; if ($command->{'section_childs'} and @{$command->{'section_childs'}}) { $result .= $self->_attribute_class('ul', 'section-toc').">\n"; foreach my $section (@{$command->{'section_childs'}}) { my $tree = $self->command_text($section, 'tree_nonumber'); my $text = $self->_convert($tree); $entry_index++; $accesskey = ''; $accesskey = " accesskey=\"$entry_index\"" if ($self->get_conf('USE_ACCESSKEY') and $entry_index < 10); my $href = $self->command_href($section, $filename); 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->{'current_filename'} if (!defined($filename)); return '' if (!$self->{'structuring'} or !$self->{'structuring'}->{'sectioning_root'}); my $section_root = $self->{'structuring'}->{'sectioning_root'}; my $contents; $contents = 1 if ($cmdname eq 'contents'); my $min_root_level = $section_root->{'section_childs'}->[0]->{'level'}; my $max_root_level = $section_root->{'section_childs'}->[0]->{'level'}; foreach my $top_section(@{$section_root->{'section_childs'}}) { $min_root_level = $top_section->{'level'} if ($top_section->{'level'} < $min_root_level); $max_root_level = $top_section->{'level'} if ($top_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 $ul_class = ''; $ul_class = $NO_BULLET_LIST_CLASS 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_OVERVIEW')))) { $result .= $self->_attribute_class('div', $cmdname).">\n"; } elsif($contents) { $result .= $self->get_conf('BEFORE_TOC_LINES'); } else { $result .= $self->get_conf('BEFORE_OVERVIEW'); } my $toplevel_contents; if (@{$section_root->{'section_childs'}} > 1) { # or $section_root->{'section_childs'}->[0]->{'cmdname'} ne 'top') { $result .= $self->_attribute_class('ul', $ul_class) .">\n"; $toplevel_contents = 1; } foreach my $top_section (@{$section_root->{'section_childs'}}) { my $section = $top_section; SECTION: while ($section) { if ($section->{'cmdname'} ne 'top') { my $text = $self->command_text($section); my $href; if (!$contents and $self->get_conf('OVERVIEW_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->{'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'}->{'isindex'}) { $rel = ' rel="index"'; } $result .= "
  • $text"; } else { $result .= "
  • $text"; } } } elsif ($section->{'section_childs'} and @{$section->{'section_childs'}} and $toplevel_contents) { $result .= "
  • "; } # for shortcontents don't do child if child is not toplevel if ($section->{'section_childs'} and ($contents or $section->{'level'} < $max_root_level)) { # no indenting for shortcontents $result .= "\n". ' ' x (2*($section->{'level'} - $min_root_level)) if ($contents); $result .= $self->_attribute_class('ul', $ul_class) .">\n"; $section = $section->{'section_childs'}->[0]; } elsif ($section->{'section_next'} and $section->{'cmdname'} ne 'top') { $result .= "
  • \n"; last if ($section eq $top_section); $section = $section->{'section_next'}; } else { #last if ($section eq $top_section); if ($section eq $top_section) { $result .= "\n" unless ($section->{'cmdname'} eq 'top'); last; } while ($section->{'section_up'}) { $section = $section->{'section_up'}; $result .= "\n". ' ' x (2*($section->{'level'} - $min_root_level)) . ""; if ($section eq $top_section) { $result .= "\n" if ($toplevel_contents); last SECTION; } if ($section->{'section_next'}) { $result .= "\n"; $section = $section->{'section_next'}; last; } } } } } if (@{$section_root->{'section_childs'}} > 1) { # or $section_root->{'section_childs'}->[0]->{'cmdname'} ne 'top') { $result .= "\n"; } if ($contents and !defined($self->get_conf('AFTER_TOC_LINES')) or (!$contents and !defined($self->get_conf('AFTER_OVERVIEW')))) { $result .= "\n\n"; } elsif($contents) { $result .= $self->get_conf('AFTER_TOC_LINES'); } else { $result .= $self->get_conf('AFTER_OVERVIEW'); } 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 $program_text = ''; if ($self->get_conf('PROGRAM_NAME_IN_FOOTER')) { my $program_string = &{$self->{'format_program_string'}}($self); $program_text .= "

    $program_string

    "; } my $pre_body_close = $self->get_conf('PRE_BODY_CLOSE'); $pre_body_close = '' if (!defined($pre_body_close)); my $setting = $self->get_conf('JS_WEBLABELS'); my $path = $self->get_conf('JS_WEBLABELS_FILE'); if ($setting and $path and ($setting eq 'generate' or $setting eq 'reference') and %{$self->{'jslicenses_element'}}) { $pre_body_close .= "" .$self->convert_tree($self->gdt('JavaScript license information')) .''; } return "${program_text} $pre_body_close "; } # 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_informations($$) { my $self = shift; my $command = shift; my $title; if ($command) { my $command_string = $self->command_text($command, 'string'); if (defined($command_string) and $command_string ne $self->{'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'); } my $title_tree = $self->gdt('{element_text} ({title})', { 'title' => $self->{'title_tree'}, 'element_text' => $element_tree }); $title = $self->convert_tree_new_formatting_context( {'type' => '_string', 'contents' => [$title_tree]}, $command->{'cmdname'}, 'element_title'); } } $title = $self->{'title_string'} if (!defined($title)); my $description; if ($self->{'documentdescription_string'}) { $description = $self->{'documentdescription_string'}; } else { $description = $title; } $description = "" if ($description ne ''); my $encoding = ''; $encoding = "" if (defined($self->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 = "\n"; } my $css_lines; if (defined($self->get_conf('CSS_LINES'))) { $css_lines = $self->get_conf('CSS_LINES'); } else { $css_lines = ''; } my $doctype = $self->get_conf('DOCTYPE'); my $bodytext = $self->get_conf('BODYTEXT'); if ($self->{'element_math'} and $self->get_conf('HTML_MATH')) { if ($self->get_conf('HTML_MATH') eq 'mathjax') { $bodytext .= ' class="tex2jax_ignore"'; } } my $copying_comment = ''; $copying_comment = $self->{'copying_comment'} if (defined($self->{'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 = "\n"; } if (defined($self->get_conf('INFO_JS_DIR'))) { if (!$self->get_conf('SPLIT')) { $self->document_error( 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 .= ' '; } for my $key (keys %{$self->{'jslicenses_infojs'}}) { $self->{'jslicenses_element'}->{$key} = $self->{'jslicenses_infojs'}->{$key}; } } if (($self->{'element_math'} or !$self->get_conf('SPLIT')) and defined($self->get_conf('HTML_MATH')) and $self->get_conf('HTML_MATH') eq 'mathjax') { my $mathjax_script = $self->get_conf('MATHJAX_SCRIPT'); $extra_head .= "" .''; for my $key (keys %{$self->{'jslicenses_math'}}) { $self->{'jslicenses_element'}->{$key} = $self->{'jslicenses_math'}->{$key}; } } return ($title, $description, $encoding, $date, $css_lines, $doctype, $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 $links = ''; if ($self->get_conf('USE_LINKS')) { my $link_buttons = $self->get_conf('LINKS_BUTTONS'); foreach my $link (@$link_buttons) { my $link_href = $self->_element_direction($element, $link, 'href', $filename); #print STDERR "$link -> $link_href \n"; if ($link_href and $link_href ne '') { my $link_string = $self->_element_direction($element, $link, 'string'); my $link_title = ''; $link_title = " title=\"$link_string\"" if (defined($link_string)); my $rel = ''; $rel = " rel=\"".$self->get_conf('BUTTONS_REL')->{$link}.'"' if (defined($self->get_conf('BUTTONS_REL')->{$link})); $links .= "\n"; } } } return $links; } sub _default_format_begin_file($$$) { my $self = shift; my $filename = shift; my $element = shift; my $command; if ($element and $self->get_conf('SPLIT')) { $command = $self->element_command($element); } my ($title, $description, $encoding, $date, $css_lines, $doctype, $bodytext, $copying_comment, $after_body_open, $extra_head, $program_and_version, $program_homepage, $program, $generator) = $self->_file_header_informations($command); my $links = $self->_get_links ($filename, $element); my $result = "$doctype $encoding $copying_comment$title $description ${generator}$date ${links}$css_lines $extra_head $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, $bodytext, $copying_comment, $after_body_open, $extra_head, $program_and_version, $program_homepage, $program, $generator) = $self->_file_header_informations($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 ${generator}$date $css_lines $extra_head $after_body_open

    $string

    "; return $result; } sub _default_format_footnotes_text($) { my $self = shift; return '' if (!$foot_lines); my $result = $self->_attribute_class('div', 'footnote').">\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 = $self->convert_tree ($self->get_conf('SPECIAL_ELEMENTS_NAME')->{'Footnotes'}); my $class = $self->get_conf('SPECIAL_ELEMENTS_CLASS')->{'Footnotes'}; my $level = $self->get_conf('FOOTNOTE_END_HEADER_LEVEL'); $result .= &{$self->{'format_heading_text'}}($self, $class.'-heading', $footnote_heading, $level)."\n"; $result .= &{$self->{'format_special_element_body'}}($self, 'Footnotes', $self->{'current_element'}); $result .= "\n"; return $result; } sub _default_format_special_element_body($$$) { my $self = shift; my $special_type = shift; my $element = shift; if ($special_type eq 'About') { my $about = "

    \n"; my $PRE_ABOUT = $self->get_conf('PRE_ABOUT'); if (defined($PRE_ABOUT)) { if (ref($PRE_ABOUT) eq 'CODE') { $about .= &$PRE_ABOUT($self, $element); } else { $about .= $PRE_ABOUT; } } else { $about .= ' '.&{$self->{'format_program_string'}}($self) ."\n"; } $about .= <

    EOT $about .= $self->convert_tree($self->gdt(' The buttons in the navigation panels have the following meaning:')) . "\n"; $about .= < EOT $about .= ' \n" . ' \n" . ' \n" . ' \n" . " \n"; foreach my $button (@{$self->get_conf('SECTION_BUTTONS')}) { next if ($button eq ' ' or ref($button) eq 'CODE' or ref($button) eq 'SCALAR' or ref($button) eq 'ARRAY'); my $button_name = $self->get_conf('BUTTONS_NAME')->{$button}; $about .= " \n \n"; $about .= " "; } $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 $about .= '
    • 1. ' . $self->convert_tree($self->gdt('Section One')) . "\n" . "
        \n" . '
      • 1.1 ' . $self->convert_tree($self->gdt('Subsection One-One')) . "\n"; $about .= <
      • ...
    • EOT $about .= '
    • 1.2 ' . $self->convert_tree($self->gdt('Subsection One-Two')) . "\n" . "
        \n" . '
      • 1.2.1 ' . $self->convert_tree($self->gdt('Subsubsection One-Two-One')) . "
      • \n" . '
      • 1.2.2 ' . $self->convert_tree($self->gdt('Subsubsection One-Two-Two')) . "
      • \n" . '
      • 1.2.3 ' . $self->convert_tree($self->gdt('Subsubsection One-Two-Three')) . "    \n" . ' <== ' . $self->convert_tree($self->gdt('Current Position')) . "
      • \n" . '
      • 1.2.4 ' . $self->convert_tree($self->gdt('Subsubsection One-Two-Four')) . "
      • \n" . "
      \n" . "
    • \n" . '
    • 1.3 ' . $self->convert_tree($self->gdt('Subsection One-Three')) . "\n"; $about .= <
    • ...
    EOT $about .= '
  • 1.4 ' . $self->convert_tree($self->gdt('Subsection One-Four')) . "
  • \n"; my $AFTER_ABOUT = ''; if (defined($self->get_conf('AFTER_ABOUT'))) { $AFTER_ABOUT = $self->get_conf('AFTER_ABOUT'); } $about .= < $AFTER_ABOUT EOT return $about; } elsif ($special_type eq 'Contents') { return &{$self->{'format_contents'}}($self, 'contents', undef); } elsif ($special_type eq 'Overview') { return &{$self->{'format_contents'}}($self, 'shortcontents', undef); } elsif ($special_type eq 'Footnotes') { my $result = $foot_lines; $foot_lines = ''; return $result; } } sub _do_jslicenses_file { my $self = 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 $a = ''; $a .= ' 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')) . "
    "; $about .= ($self->get_conf('ICONS') && $self->get_conf('ACTIVE_ICONS')->{$button} ? &{$self->{'format_button_icon_img'}}($self, $button_name, $self->get_conf('ACTIVE_ICONS')->{$button}) : ' [' . $self->get_conf('BUTTONS_TEXT')->{$button} . '] '); $about .= "".$button_name." ".$self->get_conf('BUTTONS_GOTO')->{$button}." ".$self->get_conf('BUTTONS_EXAMPLE')->{$button}."
    '; my $h = $self->{'jslicenses'}; for my $file (keys %{$self->{'jslicenses'}}) { $a .= "\n"; $a .= "\n"; $a .= "\n"; $a .= "\n"; $a .= "\n"; } $a .= "\n"; if (File::Spec->file_name_is_absolute($path) or $path =~ /^[A-Za-z]*:/) { $self->document_warn(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($self->{'destination_directory'}, $path); my $fh = $self->Texinfo::Common::open_out($license_file); if (defined($fh)) { print $fh $a; $self->register_close_file($license_file); if (!close ($fh)) { $self->document_error(sprintf(__("error on closing %s: %s"), $license_file, $!)); } } else { $self->document_error(sprintf(__("could not open %s for writing: %s"), $license_file, $!)); } } sub _default_format_frame_files($) { my $self = shift; my $frame_file = $self->{'frame_pages_filenames'}->{'Frame'}; my $frame_outfile; if (defined($self->{'destination_directory'}) and $self->{'destination_directory'} ne '') { $frame_outfile = File::Spec->catfile($self->{'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($self->{'destination_directory'}) and $self->{'destination_directory'} ne '') { $toc_frame_outfile = File::Spec->catfile($self->{'destination_directory'}, $toc_frame_file); } else { $toc_frame_outfile = $toc_frame_file; } my $frame_fh = $self->Texinfo::Common::open_out($frame_outfile); if (defined($frame_fh)) { my $doctype = $self->get_conf('FRAMESET_DOCTYPE'); my $top_file = ''; if ($self->global_element('Top')) { my $top_element = $self->global_element('Top'); $top_file = $top_element->{'filename'}; } my $title = $self->{'title_string'}; print $frame_fh < $title EOT $self->register_close_file($frame_outfile); if (!close ($frame_fh)) { $self->document_error(sprintf(__("error on closing frame file %s: %s"), $frame_outfile, $!)); return 0; } } else { $self->document_error(sprintf(__("could not open %s for writing: %s"), $frame_outfile, $!)); return 0; } my $toc_frame_fh = $self->Texinfo::Common::open_out($toc_frame_outfile); if (defined($toc_frame_fh)) { my $header = &{$self->{'format_begin_file'}}($self, $toc_frame_file, undef); print $toc_frame_fh $header; print $toc_frame_fh '

    Content

    '."\n"; my $shortcontents = &{$self->{'format_contents'}}($self, 'shortcontents', undef); $shortcontents =~ s/\bhref=/target="main" href=/g; print $toc_frame_fh $shortcontents; print $toc_frame_fh "\n"; $self->register_close_file($toc_frame_outfile); if (!close ($toc_frame_fh)) { $self->document_error(sprintf(__("error on closing TOC frame file %s: %s"), $toc_frame_outfile, $!)); return 0; } } else { $self->document_error(sprintf(__("could not open %s for writing: %s"), $toc_frame_outfile, $!)); return 0; } return 1; } sub _has_contents_or_shortcontents($) { my $self = shift; foreach my $cmdname ('contents', 'shortcontents') { if ($self->{'extra'} and $self->{'extra'}->{$cmdname}) { return 1; } } return 0; } sub convert($$) { my $self = shift; my $root = shift; my $result = ''; # This should return undef if called on a tree without node or sections. my ($elements, $special_elements) = $self->_prepare_elements($root); $self->_prepare_index_entries(); $self->_prepare_footnotes(); if (!defined($elements)) { $result = $self->_convert($root); } else { foreach my $element (@$elements) { my $element_text = $self->_convert($element); $result .= $element_text; } } return $result; } # This is called from the main program on the converter. sub output_internal_links($) { my $self = shift; my $out_string = ''; if ($self->{'elements'}) { foreach my $element (@{$self->{'elements'}}) { my $text; my $href; my $command = $self->element_command($element); 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($tree, {Texinfo::Common::_convert_text_options($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"; } } } if ($self->{'parser'}) { foreach my $index_name (sort(keys (%{$self->{'index_entries_by_letter'}}))) { foreach my $letter_entry (@{$self->{'index_entries_by_letter'}->{$index_name}}) { foreach my $index_entry (@{$letter_entry->{'entries'}}) { my $href; my $key; $href = $self->command_href($index_entry->{'command'}, ''); $key = $index_entry->{'key'}; if (defined($key) and $key =~ /\S/) { $out_string .= $href if (defined($href)); $out_string .= "\t$index_name\t"; $out_string .= $key; $out_string .= "\n"; } } } } } if ($out_string ne '') { return $out_string; } else { return undef; } } my @possible_stages = ('setup', 'structure', 'init', 'finish'); my %possible_stages; foreach my $stage (@possible_stages) { $possible_stages{$stage} = 1; } sub run_stage_handlers($$$) { my $converter = shift; my $root = shift; my $stage = shift; die if (!$possible_stages{$stage}); return 1 if (!defined($Texinfo::Config::texinfo_default_stage_handlers{$stage})); my @sorted_priorities = sort keys(%{$Texinfo::Config::texinfo_default_stage_handlers{$stage}}); foreach my $priority (@sorted_priorities) { foreach my $handler (@{$Texinfo::Config::texinfo_default_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) { #if ($converter->get_conf('VERBOSE')) { # print STDERR "Handler $handler of $stage($priority) failed\n"; #} $converter->document_error(sprintf(__( "handler %s of stage %s priority %s failed"), $handler, $stage, $priority)); return $status; } } } return 1; } my $default_priority = 'default'; { package Texinfo::Config; # Note that these variables are available for the Texinfo modules # but, in general should not be accessed directly by the users who # customize formatting and should use the associated functions, # such as texinfo_register_handler(), texinfo_register_formatting_function(), # texinfo_register_command_formatting() or texinfo_register_type_formatting(). use vars qw(%texinfo_default_stage_handlers %texinfo_formatting_references %texinfo_commands_conversion %texinfo_types_conversion); sub texinfo_register_handler($$;$) { my $stage = shift; my $handler = shift; my $priority = shift; if (!$possible_stages{$stage}) { carp ("Unknown stage $stage\n"); return 0; } $priority = $default_priority if (!defined($priority)); push @{$texinfo_default_stage_handlers{$stage}->{$priority}}, $handler; return 1; } sub texinfo_register_formatting_function($$) { my $thing = shift; my $handler = shift; if (!$default_formatting_references{$thing}) { carp ("Unknown formatting type $thing\n"); return 0; } $texinfo_formatting_references{$thing} = $handler; } sub texinfo_register_command_formatting($$) { my $command = shift; my $reference = shift; $texinfo_commands_conversion{$command} = $reference; } sub texinfo_register_type_formatting($$) { my $command = shift; my $reference = shift; $texinfo_types_conversion{$command} = $reference; } } # 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; # 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')); my $setup_status = $self->run_stage_handlers($root, 'setup'); return undef unless($setup_status); $self->_prepare_css(); # this sets OUTFILE, to be used if not split, but also # 'destination_directory' and 'output_filename' that are useful when split. $self->_set_outfile(); return undef unless $self->_create_destination_directory(); # 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 ($elements, $special_elements) = $self->_prepare_elements($root); Texinfo::Structuring::split_pages($elements, $self->get_conf('SPLIT')); # determine file names associated with the different pages, and setup # the counters for special element pages. if ($self->{'output_file'} ne '') { $self->_set_pages_files($elements, $special_elements); } $self->_prepare_contents_elements(); # do element directions. Texinfo::Structuring::elements_directions($self, $elements); # 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($self, $elements); # Associate the special elements that have no page with the main page. # This may only happen if not split. if ($special_elements and $elements and $elements->[0] and defined($elements->[0]->{'filename'})) { foreach my $special_element (@$special_elements) { if (!defined($special_element->{'filename'})) { $special_element->{'filename'} = $elements->[0]->{'filename'}; $special_element->{'out_filename'} = $elements->[0]->{'out_filename'}; $self->{'file_counters'}->{$special_element->{'filename'}}++; } } } $self->_prepare_index_entries(); $self->_prepare_footnotes(); # '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}; } my $structure_status = $self->run_stage_handlers($root, 'structure'); return undef unless($structure_status); &{$self->{'format_css_lines'}}($self); $self->set_conf('BODYTEXT', 'lang="' . $self->get_conf('documentlanguage') . '"'); # 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->{'extra'}->{$fulltitle_command}) { my $command = $self->{'extra'}->{$fulltitle_command}; next if (!$command->{'args'} or (!$command->{'args'}->[0]->{'contents'} or $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->{'extra'}->{'titlefont'} and $self->{'extra'}->{'titlefont'}->[0]->{'args'} and defined($self->{'extra'}->{'titlefont'}->[0]->{'args'}->[0]) and @{$self->{'extra'}->{'titlefont'}->[0]->{'args'}->[0]->{'contents'}}) { $fulltitle = $self->{'extra'}->{'titlefont'}->[0]; } # prepare simpletitle foreach my $simpletitle_command('settitle', 'shorttitlepage') { if ($self->{'extra'}->{$simpletitle_command}) { my $command = $self->{'extra'}->{$simpletitle_command}; next if ($command->{'extra'} and $command->{'extra'}->{'missing_argument'}); $self->{'simpletitle_tree'} = {'contents' => $command->{'args'}->[0]->{'contents'}}; 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->file_line_warn(__( "must specify a title with a title command or \@top"), $self->{'info'}->{'input_file_name'}); } else { $self->{'title_string'} = $html_title_string; } # copying comment if ($self->{'extra'}->{'copying'}) { my $copying_comment = Texinfo::Convert::Text::convert( {'contents' => $self->{'extra'}->{'copying'}->{'contents'}}, {Texinfo::Common::_convert_text_options($self)}); if ($copying_comment ne '') { $self->{'copying_comment'} = &{$self->{'format_comment'}}($self, $copying_comment); } } # documentdescription if (defined($self->get_conf('documentdescription'))) { $self->{'documentdescription_string'} = $self->get_conf('documentdescription'); } elsif ($self->{'extra'}->{'documentdescription'}) { $self->{'documentdescription_string'} = $self->convert_tree_new_formatting_context( {'type' => '_string', 'contents' => $self->{'extra'}->{'documentdescription'}->{'contents'}}, 'documentdescription'); chomp($self->{'documentdescription_string'}); } my $init_status = $self->run_stage_handlers($root, 'init'); return undef unless($init_status); if ($self->get_conf('FRAMES')) { my $status = &{$self->{'format_frame_files'}}($self); return undef if (!$status); } 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, $mathjax_source); $mathjax_script = $self->get_conf('MATHJAX_SCRIPT'); if (!$mathjax_script) { $mathjax_script = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js'; $self->set_conf('MATHJAX_SCRIPT', $mathjax_script); } $mathjax_source = $self->get_conf('MATHJAX_SOURCE'); if (!$mathjax_source) { $mathjax_source = 'http://docs.mathjax.org/en/latest/web/hosting.html#getting-mathjax-via-git'; $self->set_conf('MATHJAX_SOURCE', $mathjax_source); } $self->{'jslicenses_math'}->{$mathjax_script} = [ 'Apache License, Version 2.0.', 'https://www.apache.org/licenses/LICENSE-2.0', $mathjax_source ]; # append to hash %{$self->{'jslicenses'}} = ( %{$self->{'jslicenses'}}, %{$self->{'jslicenses_math'}} ); } 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' ]; $self->{'jslicenses_infojs'}->{'js/modernizr.js'} = [ 'Expat', 'http://www.jclark.com/xml/copying.txt', 'js/modernizr.js' ]; %{$self->{'jslicenses'}} = ( %{$self->{'jslicenses'}}, %{$self->{'jslicenses_infojs'}} ); } # FIXME here call _unset_global_multiple_commands? Problem is # that some conversion, for instance for page header requires # that the correct language is set, for instance. The @-command # will necessarily appear later on -- even if it appears a the # beginning of the file. my $fh; my $output = ''; if (!$elements or !defined($elements->[0]->{'filename'})) { # no page my $outfile; if ($self->{'output_file'} ne '') { if ($self->get_conf('SPLIT')) { $outfile = $self->_top_node_filename(); if (defined($self->{'destination_directory'}) and $self->{'destination_directory'} ne '') { $outfile = File::Spec->catfile($self->{'destination_directory'}, $outfile); } } else { $outfile = $self->{'output_file'}; } $fh = $self->Texinfo::Common::open_out($outfile); if (!$fh) { $self->document_error(sprintf(__("could not open %s for writing: %s"), $outfile, $!)); return undef; } } $self->{'current_filename'} = $self->{'output_filename'}; my $body = ''; if ($elements and @$elements) { foreach my $element (@$elements) { my $element_text = $self->_convert($element); $body .= $element_text; } } else { $body .= $self->_print_title(); $body .= $self->_convert($root); } my $header = &{$self->{'format_begin_file'}}($self, $self->{'output_filename'}, undef); $output .= $self->_output_text($header, $fh); $output .= $self->_output_text($body, $fh); $output .= $self->_output_text(&{$self->{'format_end_file'}}($self), $fh); # NOTE do not close STDOUT now to avoid a perl warning. if ($fh and $outfile ne '-') { $self->register_close_file($outfile); if (!close($fh)) { $self->document_error(sprintf(__("error on closing %s: %s"), $outfile, $!)); } } return $output if ($self->{'output_file'} eq ''); } else { # output with pages print STDERR "DO Elements with filenames\n" if ($self->get_conf('DEBUG')); my %files; # Now do the output, converting each member in @$elements in turn. $special_elements = [] if (!defined($special_elements)); foreach my $element (@$elements, @$special_elements) { my $file_fh; $self->{'current_filename'} = $element->{'filename'}; $self->{'counter_in_file'}->{$element->{'filename'}}++; if ($self->{'counter_in_file'}->{$element->{'filename'}} == 1) { $self->{'jslicenses_element'} = {}; $self->{'element_math'} = 0; } # First do the special pages, to avoid outputting these if they are # empty. my $special_element_content; if ($element->{'extra'} and $element->{'extra'}->{'special_element'}) { $special_element_content .= $self->_convert($element); 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 { $body = $self->_convert($element); } if (!$files{$element->{'filename'}}->{'fh'}) { $file_fh = $self->Texinfo::Common::open_out($element->{'out_filename'}); if (!$file_fh) { $self->document_error(sprintf(__("could not open %s for writing: %s"), $element->{'out_filename'}, $!)); return undef; } print $file_fh "".&{$self->{'format_begin_file'}}($self, $element->{'filename'}, $element); $files{$element->{'filename'}}->{'fh'} = $file_fh; } else { $file_fh = $files{$element->{'filename'}}->{'fh'}; } print $file_fh $body; $self->{'file_counters'}->{$element->{'filename'}}--; if ($self->{'file_counters'}->{$element->{'filename'}} == 0) { # end file print $file_fh "". &{$self->{'format_end_file'}}($self); # NOTE do not close STDOUT here to avoid a perl warning if ($element->{'out_filename'} ne '-') { $self->register_close_file($element->{'out_filename'}); if (!close($file_fh)) { $self->document_error(sprintf(__("error on closing %s: %s"), $element->{'out_filename'}, $!)); return undef; } } } } if ($self->get_conf('INFO_JS_DIR')) { my $jsdir = File::Spec->catdir($self->{'destination_directory'}, $self->get_conf('INFO_JS_DIR')); if (!-d $jsdir) { if (-f $jsdir) { $self->document_error( sprintf(__("%s already exists but is not a directory"), $jsdir)); } else { mkdir $jsdir; } } if (-d $jsdir) { my $jssrcdir; if (!$Texinfo::ModulePath::texinfo_uninstalled) { $jssrcdir = File::Spec->catdir( $Texinfo::ModulePath::lib_dir, '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( sprintf(__("error on copying %s into %s"), $from, $jsdir)); } } } } } if (%{$self->{'jslicenses'}}) { $self->_do_jslicenses_file(); } my $finish_status = $self->run_stage_handlers($root, 'finish'); return undef unless($finish_status); 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 $self->{'output_file'} ne '') { foreach my $label (sort(keys (%{$self->{'labels'}}))) { my $node = $self->{'labels'}->{$label}; my $target = $self->_get_target($node); # filename may not be defined in case of an @anchor or similar in # @titlepage, and @titlepage is not used. my $filename = $self->command_filename($node); my $node_filename; # NOTE 'node_filename' is not used for Top, so the other manual # must use the same convention to get it right. We avoid doing # also 'node_filename' to avoid unneeded redirection files. if ($node->{'extra'} and $node->{'extra'}->{'normalized'} and $node->{'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_page = &{$self->{'format_node_redirection_page'}}($self, $node); my $out_filename; if (defined($self->{'destination_directory'}) and $self->{'destination_directory'} ne '') { $out_filename = File::Spec->catfile($self->{'destination_directory'}, $node_filename); } else { $out_filename = $node_filename; } my $file_fh = $self->Texinfo::Common::open_out($out_filename); if (!$file_fh) { $self->document_error(sprintf(__( "could not open %s for writing: %s"), $out_filename, $!)); } else { print $file_fh $redirection_page; $self->register_close_file($out_filename); if (!close ($file_fh)) { $self->document_error(sprintf(__( "error on closing redirection node file %s: %s"), $out_filename, $!)); return undef; } } } } } return undef; } # Convert the 'contents' of a tree element. sub _convert_contents($$$) { my $self = shift; my $root = shift; my $command_type = shift; my $content_formatted = ''; if (ref($root->{'contents'}) ne 'ARRAY') { cluck "for $root contents not an array: $root->{'contents'}"; print STDERR Texinfo::Common::_print_current($root); } my $content_idx = 0; foreach my $content (@{$root->{'contents'}}) { my $new_content = $self->_convert($content, "$command_type [$content_idx]"); if (!defined($new_content)) { cluck "content not defined for $command_type [$content_idx]\n"; print STDERR "root is: ".Texinfo::Common::_print_current ($root); print STDERR "content is: ".Texinfo::Common::_print_current ($content); } else { $content_formatted .= $new_content; } $content_idx++; } return $content_formatted; } #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; return $self->protect_text($class_name); } # $extra_classes should be an array reference or undef sub _attribute_class($$$;$) { my $self = shift; my $element = shift; my $class = shift; my $extra_classes = shift; if (!defined($class) or $class eq '' or $self->get_conf('NO_CSS')) { if ($element eq 'span') { return ''; } else { return "<$element"; } } my $style = ''; if ($self->get_conf('INLINE_CSS_STYLE') and defined($self->{'css_map'}->{"$element.$class"})) { $style = ' style="'.$self->{'css_map'}->{"$element.$class"}.'"'; } my $extra_class_str = ''; if (defined($extra_classes)) { my $extra_class_conversion = join(' ', map {$self->_protect_class_name($_)} @$extra_classes); if ($extra_class_conversion ne '') { $extra_class_str = ' '.$extra_class_conversion; } } return "<$element class=\"$class$extra_class_str\"$style"; } sub _protect_space($$) { my $self = shift; my $text = shift; return $text if ($self->in_preformatted()); if ($self->in_space_protected()) { my $open = $self->_attribute_class('span', 'nolinebreak'); if ($open ne '') { $open .= '>'; # Protect spaces in the html leading attribute in case we are in 'w' $open =~ s/ /\x{1F}/g; # Special span to avoid breaking at _- $text =~ s/(\S*[_-]\S*)/${open}$1<\/span>/g; } $text .= ' ' if (chomp($text)); # Protect spaces within text $text =~ s/ / /g; # Revert protected spaces in leading html attribute $text =~ s/\x{1F}/ /g; } return $text; } # Convert tree element $ROOT, and return HTML text for the output files. sub _convert($$;$); sub _convert($$;$) { my $self = shift; my $root = shift; # only used for debug my $explanation = shift; # to help debug and trace my $command_type = ''; if ($root->{'cmdname'}) { $command_type = "\@$root->{'cmdname'} "; } if (defined($root->{'type'})) { $command_type .= $root->{'type'}; } if ($self->get_conf('DEBUG')) { $explanation = 'NO EXPLANATION' if (!defined($explanation)); print STDERR "ROOT($explanation):$root (".join('|',@{$self->{'document_context'}->[-1]->{'formatting_context'}})."), ->"; print STDERR " cmd: $root->{'cmdname'}," if ($root->{'cmdname'}); print STDERR " type: $root->{'type'}" if ($root->{'type'}); my $text = $root->{'text'}; if (defined($text)) { $text =~ s/\n/\\n/; print STDERR " text: $text"; } print STDERR "\n"; } if (ref($root) ne 'HASH') { cluck "_convert: root not a HASH\n"; return ''; } if (($root->{'type'} and exists ($self->{'types_conversion'}->{$root->{'type'}}) and !defined($self->{'types_conversion'}->{$root->{'type'}})) or ($root->{'cmdname'} and exists($self->{'commands_conversion'}->{$root->{'cmdname'}}) and !defined($self->{'commands_conversion'}->{$root->{'cmdname'}}))) { if ($self->get_conf('DEBUG')) { my $string = 'IGNORED'; $string .= " \@$root->{'cmdname'}" if ($root->{'cmdname'}); $string .= " $root->{'type'}" if ($root->{'type'}); print STDERR "$string\n"; } return ''; } # Process text if (defined($root->{'text'})) { # already converted to html, keep it as is if ($root->{'type'} and $root->{'type'} eq '_converted') { return $root->{'text'}; } if ($root->{'type'} and $root->{'type'} eq 'untranslated') { my $translated = $self->gdt($root->{'text'}); my $result = $self->_convert($translated); return $result; } my $result = &{$self->{'types_conversion'}->{'text'}} ($self, $root->{'type'}, $root, $root->{'text'}); print STDERR "DO TEXT => `$result'\n" if ($self->get_conf('DEBUG')); return $result; } if ($root->{'extra'} and $root->{'extra'}->{'missing_argument'} and (!$root->{'contents'} or !@{$root->{'contents'}})) { print STDERR "MISSING_ARGUMENT\n" if ($self->get_conf('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 ($root->{'cmdname'} and !($root->{'type'} and $root->{'type'} eq 'def_line' or $root->{'type'} and $root->{'type'} eq 'definfoenclose_command')) { my $command_name = $root->{'cmdname'}; # use the same command name for all the index entry commands if ($root->{'extra'} and $root->{'extra'}->{'index_entry'} and $root->{'cmdname'} and $root->{'cmdname'} =~ /index$/) { $command_name = 'cindex'; } if ($root_commands{$command_name}) { $self->{'current_root_command'} = $root; } if (exists($self->{'commands_conversion'}->{$command_name})) { if (exists($context_brace_commands{$command_name})) { $self->_new_document_context($command_name); } push @{$self->{'document_context'}->[-1]->{'commands'}}, $root->{'cmdname'}; if (exists($format_context_commands{$command_name})) { push @{$self->{'document_context'}->[-1]->{'formatting_context'}}, {'cmdname' => $command_name}; } if (exists($block_commands{$command_name})) { push @{$self->{'document_context'}->[-1]->{'formats'}}, $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 'verb' or $command_name eq 'verbatim') { $self->{'document_context'}->[-1]->{'verbatim'}++; } if ($code_style_commands{$command_name} or $preformatted_code_commands{$command_name}) { push @{$self->{'document_context'}->[-1]->{'monospace'}}, 1; } elsif ($regular_font_style_commands{$command_name}) { 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'}++; } elsif ($command_name eq 'w') { $self->{'document_context'}->[-1]->{'formatting_context'}->[-1]->{'space_protected'}++; } my $content_formatted; if ($root->{'contents'}) { $content_formatted = $self->_convert_contents($root, $command_type); } my $args_formatted; if ($brace_commands{$command_name} or ($misc_commands{$command_name} and $misc_commands{$command_name} eq 'line') or (($command_name eq 'item' or $command_name eq 'itemx') and ($root->{'parent'}->{'type'} and $root->{'parent'}->{'type'} eq 'table_term')) or ($command_name eq 'quotation' or $command_name eq 'smallquotation') or ($command_name eq 'float')) { $args_formatted = []; if ($root->{'args'}) { my @args_specification; @args_specification = @{$self->{'commands_args'}->{$command_name}} if (defined($self->{'commands_args'}->{$command_name})); my $arg_idx = 0; foreach my $arg (@{$root->{'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 \[$arg_idx\]$arg_type"; if ($arg_type eq 'normal') { $arg_formatted->{'normal'} = $self->_convert($arg, $explanation); } elsif ($arg_type eq 'monospace') { push @{$self->{'document_context'}->[-1]->{'monospace'}}, 1; #$self->{'document_context'}->[-1]->{'code'}++; $arg_formatted->{$arg_type} = $self->_convert($arg, $explanation); #$self->{'document_context'}->[-1]->{'code'}--; 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); pop @{$self->{'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); pop @{$self->{'document_context'}}; } elsif ($arg_type eq 'monospacetext') { $arg_formatted->{$arg_type} = Texinfo::Convert::Text::convert($arg, {'code' => 1, Texinfo::Common::_convert_text_options($self)}); } 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 ($code_style_commands{$command_name} or $preformatted_code_commands{$command_name} or $regular_font_style_commands{$command_name}) { #$self->{'document_context'}->[-1]->{'code'}--; 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'}--; } elsif ($command_name eq 'w') { $self->{'document_context'}->[-1]->{'formatting_context'}->[-1]->{'space_protected'}--; } if ($format_raw_commands{$command_name}) { $self->{'document_context'}->[-1]->{'raw'}--; } elsif ($command_name eq 'verb' or $command_name eq 'verbatim') { $self->{'document_context'}->[-1]->{'verbatim'}--; } if (exists($block_commands{$command_name})) { pop @{$self->{'document_context'}->[-1]->{'formats'}}; } if (exists($format_context_commands{$command_name})) { pop @{$self->{'document_context'}->[-1]->{'formatting_context'}}; } pop @{$self->{'document_context'}->[-1]->{'commands'}}; if (exists($context_brace_commands{$command_name})) { pop @{$self->{'document_context'}}; } if ($root->{'cmdname'} eq 'node') { $self->{'current_node'} = $root; } elsif ($root->{'cmdname'} eq 'menu' and $self->{'current_node'}) { $self->{'seenmenus'}->{$self->{'current_node'}} = 1; } # args are formatted, now format the command itself my $result; if ($args_formatted) { if (!defined($self->{'commands_conversion'}->{$command_name})) { print STDERR "No command_conversion for $command_name\n"; $result = ''; } else { $result = &{$self->{'commands_conversion'}->{$command_name}}($self, $command_name, $root, $args_formatted, $content_formatted); } } else { $result = &{$self->{'commands_conversion'}->{$command_name}}($self, $command_name, $root, $content_formatted); } return $result; } else { print STDERR "Unknown command `$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 ($root->{'type'}) { push @{$self->{'document_context'}->[-1]->{'commands'}}, $root->{'cmdname'} if ($root->{'cmdname'}); if ($root->{'type'} eq 'paragraph') { $self->{'document_context'}->[-1]->{'formatting_context'}->[-1]->{'paragraph_number'}++; } elsif ($root->{'type'} eq 'preformatted' or $root->{'type'} eq 'rawpreformatted') { $self->{'document_context'}->[-1]->{'formatting_context'}->[-1]->{'preformatted_number'}++; } elsif ($root->{'type'} eq 'element') { $self->{'current_element'} = $root; $self->{'current_filename'} = $root->{'filename'}; } elsif ($pre_class_types{$root->{'type'}}) { push @{$self->{'document_context'}->[-1]->{'preformatted_classes'}}, $pre_class_types{$root->{'type'}}; push @{$self->{'document_context'}->[-1]->{'composition_context'}}, $root->{'type'}; } if ($self->{'code_types'}->{$root->{'type'}}) { #$self->{'document_context'}->[-1]->{'code'}++; push @{$self->{'document_context'}->[-1]->{'monospace'}}, 1; } if ($root->{'type'} eq '_string') { $self->{'document_context'}->[-1]->{'string'}++; } my $content_formatted; if ($root->{'type'} eq 'definfoenclose_command') { if ($root->{'args'}) { $content_formatted = $self->_convert($root->{'args'}->[0]); } } elsif ($root->{'contents'}) { $content_formatted = $self->_convert_contents($root, $command_type); } my $result = ''; if (exists($self->{'types_conversion'}->{$root->{'type'}})) { $result = &{$self->{'types_conversion'}->{$root->{'type'}}} ($self, $root->{'type'}, $root, $content_formatted); #print STDERR "Converting type $root->{'type'} -> $result\n"; } elsif (defined($content_formatted)) { $result = $content_formatted; } if ($self->{'code_types'}->{$root->{'type'}}) { #$self->{'document_context'}->[-1]->{'code'}--; pop @{$self->{'document_context'}->[-1]->{'monospace'}}; } if ($root->{'type'} eq '_string') { $self->{'document_context'}->[-1]->{'string'}--; } if ($root->{'type'} eq 'element') { delete $self->{'current_element'}; delete $self->{'current_filename'}; } elsif ($pre_class_types{$root->{'type'}}) { pop @{$self->{'document_context'}->[-1]->{'preformatted_classes'}}; pop @{$self->{'document_context'}->[-1]->{'composition_context'}}; } print STDERR "DO type ($root->{'type'}) => `$result'\n" if ($self->get_conf('DEBUG')); pop @{$self->{'document_context'}->[-1]->{'commands'}} if ($root->{'cmdname'}); return $result; # no type, no cmdname, but contents. } elsif ($root->{'contents'}) { # this happens inside accents, for section/node names, for @images. my $content_formatted = ''; my $i = 0; foreach my $content (@{$root->{'contents'}}) { $content_formatted .= $self->_convert($content, "$command_type [$i]"); $i++; } print STDERR "UNNAMED HOLDER => `$content_formatted'\n" if ($self->get_conf('DEBUG')); return $content_formatted; } else { print STDERR "UNNAMED empty\n" if ($self->get_conf('DEBUG')); if ($self->{'types_conversion'}->{''}) { return &{$self->{'types_conversion'}->{''}} ($self, $root); } else { return ''; } } print STDERR "DEBUG: HERE!($root)\n"; } sub _set_variables_texi2html() { my @texi2html_options = ( ['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], ['OVERVIEW_LINK_TO_TOC', 0], ['USE_UP_NODE_FOR_ELEMENT_UP', 1], ['USE_REL_REV', 0], ['USE_LINKS', 0], ['USE_NODES', 0], ['USE_NUMERIC_ENTITY', 1], ['SPLIT', ''], ['PROGRAM_NAME_IN_FOOTER', 1], ['HEADER_IN_TABLE', 1], ['USE_TITLEPAGE_FOR_TITLE', 1], ['MENU_ENTRY_COLON', ''], ['INDEX_ENTRY_COLON', ''], ['ENABLE_ENCODING_USE_ENTITY', 1], ['DO_ABOUT', undef], ['NODE_NAME_IN_INDEX', 0], ['CHAPTER_HEADER_LEVEL', 1], ['BIG_RULE', '
    '], ['FOOTNOTE_END_HEADER_LEVEL', 3], ['FOOTNOTE_SEPARATE_HEADER_LEVEL', 1], ['KEEP_TOP_EXTERNAL_REF', 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]; } } 1; __END__ # $Id$ # 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 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 parser object may be associated with the I key. The other options should be configuration options described in the Texinfo manual. Those options, when appropriate, override the document content. See L for more informations. =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> or tree portion 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() Returns text representing the links in the document. The format should follow the C<--internal-links> option of the texi2any/makeinfo specification. This is only supported in (and relevant for) HTML. =back =head1 AUTHOR Patrice Dumas, Epertusus@free.frE =cut
    $file{$file}->[1]\">$h->{$file}->[0]{$file}->[2]\">$h->{$file}->[2]