| # HTML.pm: output tree as HTML. XS loading and functions only implemented |
| # in Perl, without XS override |
| # |
| # Copyright 2011-2026 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 <https://www.gnu.org/licenses/>. |
| # |
| # |
| # The documentation of the HTML customization API which is both |
| # used and implemented in the current file and in HTMLNonXS.pm is in the |
| # texi2any_api manual. |
| # |
| # Formatting and conversion functions that can be replaced by user-defined |
| # functions should only use documented functions to pass information |
| # and formatted content, such that users can overrides them independently |
| # without risking unwanted results. Also in formatting functions, the state of |
| # the converter should only be accessed through functions, such as in_math, |
| # in_preformatted_context, preformatted_classes_stack and similar functions. |
| # A few cases of direct access to converter hash keys are possible as |
| # described in the customization API manual (for example |
| # for no_arg_commands_formatting). |
| # |
| # Original author: Patrice Dumas <pertusus@free.fr> |
| |
| # ALTIMP C/convert/*html*.[ch] |
| |
| |
| package Texinfo::Convert::HTML; |
| |
| use 5.008; |
| |
| # 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. |
| # Also follows unicode rules for uc() and lc (). |
| use if $] >= 5.012, feature => 'unicode_strings'; |
| |
| use if $] >= 5.014, re => '/a'; # ASCII-only character classes in regexes |
| |
| use strict; |
| use warnings; |
| |
| # To check if there is no erroneous autovivification |
| #no autovivification qw(fetch delete exists store strict); |
| |
| use Carp qw(cluck confess); |
| |
| use Encode qw(encode); |
| |
| use Texinfo::XSLoader; |
| |
| use Texinfo::Commands; |
| use Texinfo::Options; |
| use Texinfo::CommandsValues; |
| use Texinfo::UnicodeData; |
| use Texinfo::HTMLData; |
| use Texinfo::HTMLDataCSS; |
| |
| use Texinfo::TreeElement; |
| |
| use Texinfo::Common; |
| use Texinfo::Indices; |
| |
| use Texinfo::Config; |
| use Texinfo::Convert::Unicode; |
| use Texinfo::Convert::Texinfo; |
| use Texinfo::Convert::Utils; |
| use Texinfo::Convert::Text; |
| use Texinfo::Convert::NodeNameNormalization; |
| use Texinfo::ManipulateTree; |
| use Texinfo::Structuring; |
| use Texinfo::OutputUnits; |
| # for index_entry_first_letter_text_or_command |
| use Texinfo::Indices; |
| |
| use Texinfo::Convert::Converter; |
| |
| # used to convert Texinfo to LaTeX math in @math and @displaymath |
| # for further conversion by softwares that only convert LaTeX. |
| # NOTE mathjax does not implement some constructs output by the |
| # Texinfo::Convert::LaTeX converter. Examples in 2022: |
| # \mathord{\text{}} \textsl{} \copyright{} \mathsterling{} |
| use Texinfo::Convert::LaTeX; |
| |
| require Exporter; |
| |
| our @ISA = qw(Texinfo::Convert::Converter); |
| |
| our $VERSION = '7.3dev'; |
| |
| our %HTML_align_commands; |
| |
| # Load XS interface or interface Perl implementation |
| BEGIN { |
| # needs to be defined before loading Texinfo::Convert::HTMLNonXS |
| foreach my $align_command |
| ('raggedright', 'flushleft', 'flushright', 'center') { |
| $HTML_align_commands{$align_command} = 1; |
| } |
| |
| # Main conversion XS and NonXS interfaces loading |
| my $shared_library_name = "ConvertHTMLXS"; |
| if (!Texinfo::XSLoader::XS_convert_enabled()) { |
| undef $shared_library_name; |
| } |
| Texinfo::XSLoader::init ( |
| "Texinfo::Convert::HTML", |
| "Texinfo::Convert::HTMLNonXS", |
| $shared_library_name, |
| undef, |
| ['texinfo', 'texinfoxs', 'texinfo-convert', 'texinfo-convertxs'], |
| ); |
| } |
| |
| # Small functions override that do not depend on the Texinfo tree |
| # being parsed with native code C implementation. |
| my %XS_overrides = ( |
| "Texinfo::Convert::HTML::_default_format_protect_text" |
| => "Texinfo::MiscXS::default_format_protect_text", |
| "Texinfo::Convert::HTML::_entity_text" |
| => "Texinfo::MiscXS::entity_text", |
| ); |
| |
| # Extra initialization for functions override for the first time this |
| # module is loaded. |
| my $module_loaded = 0; |
| sub import { |
| if (!$module_loaded) { |
| foreach my $sub (keys %XS_overrides) { |
| Texinfo::XSLoader::override($sub, $XS_overrides{$sub}); |
| } |
| $module_loaded = 1; |
| } |
| # The usual import method |
| goto &Exporter::import; |
| } |
| |
| # How converter state is accessed. Information on function API to set and/or |
| # get, and whether interface is available as XS (which means nonXS |
| # implementation in HTMLNonXS.pm). Details in customization API manual. |
| # |
| # commands_init_conf |
| # API XS Converter set_global_document_commands |
| # Get through converter set_global_document_commands with 'before'. Set |
| # based on commands associated customization values set at the beginning |
| # of conversion. |
| # |
| # shared_conversion_state |
| # API partial XS shared_conversion_state functions |
| # |
| # converter_info |
| # API XS get get_info |
| # list of available_converter_info in HTMLNonXS |
| # |
| # sorted index_entries |
| # sorted index_entries_by_letter |
| # API XS Converter get get_converter_indices_sorted_by_index |
| # get_converter_indices_sorted_by_letter |
| # |
| # stage_handlers |
| # API set Texinfo::Config |
| # |
| # files_css_import_lines |
| # files_css_rule_lines |
| # command-line |
| # |
| # css_element_class_styles |
| # css_import_lines |
| # css_rule_lines |
| # API XS css_add_info, css_set_selector_style... |
| # |
| # file_id_setting |
| # commands_conversion |
| # commands_open |
| # types_conversion |
| # types_open |
| # API set Texinfo::Config get HTML functions default_command_conversion |
| # command_conversion... |
| # |
| # customized_no_arg_commands_formatting # used for no_arg_commands_formatting |
| # API set Texinfo::Config |
| # no_arg_commands_formatting |
| # direct user access |
| # style_commands_formatting |
| # API set Texinfo::Config direct user access |
| # |
| # code_types |
| # pre_class_types |
| # API set Texinfo::Config direct internal access |
| # |
| # current_filename |
| # current_output_unit |
| # document_context |
| # pending_closes |
| # pending_footnotes |
| # pending_inline_content |
| # associated_inline_content |
| # multiple_pass |
| # document_global_context_css |
| # page_css |
| # files_information |
| # referred_command_stack |
| # check_htmlxref_already_warned |
| # API XS _open_command_update_context _pop_document_context |
| # associate_pending_formatted_inline_content, |
| # html_attribute_class... |
| # |
| # targets |
| # special_targets |
| # API set Texinfo::Config get partial XS footnote_location_target |
| # command_filename... |
| # |
| # global_units_directions # defaults set before conversion |
| # global_texts_directions # defaults set before conversion |
| # API Texinfo::Config set XS get global_direction_text global_direction_unit |
| # |
| # customized_direction_strings |
| # API set Texinfo::Config |
| # translated_direction_strings # based on customized_direction_strings |
| # directions_strings # based on *_direction_strings |
| # API get direction_string |
| # |
| # special_unit_info # set in parallel in C |
| # API get XS set Texinfo::Config |
| # |
| # translated_special_unit_info_texinfo |
| # API set Texinfo::Config |
| # translated_special_unit_info_tree # based on translated_special_unit_info_texinfo |
| # API get XS _internal_command_text special_unit_info_text output_internal_links |
| # |
| # elements_in_file_count # the number of output units in file |
| # file_counters # begin at elements_in_file_count decrease |
| # # each time the unit is closed |
| # API XS get count_elements_in_filename set before conversion and in output |
| # |
| # |
| # all_directions # determined parallelly in C |
| # direct user access |
| # deprecated_config_directories # passed from main program |
| # direct user access |
| # |
| # document_units |
| # out_filepaths (partially common with Texinfo::Converter) |
| # seen_ids |
| # options_latex_math |
| # htmlxref |
| # direct access internal |
| |
| |
| my %nobrace_commands = %Texinfo::Commands::nobrace_commands; |
| my %line_commands = %Texinfo::Commands::line_commands; |
| my %nobrace_symbol_text = %Texinfo::CommandsValues::nobrace_symbol_text; |
| my %sectioning_heading_commands = %Texinfo::Commands::sectioning_heading_commands; |
| my %def_commands = %Texinfo::Commands::def_commands; |
| my %ref_commands = %Texinfo::Commands::ref_commands; |
| my %brace_commands = %Texinfo::Commands::brace_commands; |
| my %block_commands = %Texinfo::Commands::block_commands; |
| my %root_commands = %Texinfo::Commands::root_commands; |
| my %preformatted_commands = %Texinfo::Commands::preformatted_commands; |
| my %math_commands = %Texinfo::Commands::math_commands; |
| my %preformatted_code_commands = %Texinfo::Commands::preformatted_code_commands; |
| my %letter_no_arg_commands = %Texinfo::Commands::letter_no_arg_commands; |
| |
| my %formatted_line_commands = %Texinfo::Commands::formatted_line_commands; |
| my %formatted_nobrace_commands = %Texinfo::Commands::formatted_nobrace_commands; |
| my %formattable_line_commands = %Texinfo::Commands::formattable_line_commands; |
| my %explained_commands = %Texinfo::Commands::explained_commands; |
| my %inline_format_commands = %Texinfo::Commands::inline_format_commands; |
| my %default_index_commands = %Texinfo::Commands::default_index_commands; |
| my %small_block_associated_command = %Texinfo::Common::small_block_associated_command; |
| |
| foreach my $def_command (keys(%def_commands)) { |
| $formatted_line_commands{$def_command} = 1 |
| if (exists($line_commands{$def_command})); |
| } |
| |
| my %format_raw_commands; |
| foreach my $block_command (keys(%block_commands)) { |
| if ($block_commands{$block_command} eq 'format_raw') { |
| $format_raw_commands{$block_command} = 1; |
| } |
| } |
| |
| |
| # values for integer and string options in code generated from |
| # Texinfo/Convert/converters_defaults.txt |
| my $regular_defaults = Texinfo::Options::get_regular_options('html_converter'); |
| |
| our %defaults = ( |
| # Customization option variables |
| %{$regular_defaults}, |
| |
| # Non-string customization variables |
| # _default_panel_button_dynamic_direction use nodes direction based on USE_NODE_DIRECTIONS |
| # or USE_NODES if USE_NODE_DIRECTIONS is undefined |
| 'SECTION_BUTTONS' => [[ 'Next', \&_default_panel_button_dynamic_direction ], |
| [ 'Prev', \&_default_panel_button_dynamic_direction ], |
| [ 'Up', \&_default_panel_button_dynamic_direction ], 'Space', |
| 'Contents', 'Index', 'About'], |
| 'SECTION_FOOTER_BUTTONS' => [[ 'Next', \&_default_panel_button_dynamic_direction_section_footer ], |
| [ 'Prev', \&_default_panel_button_dynamic_direction_section_footer ], |
| [ 'Up', \&_default_panel_button_dynamic_direction_section_footer ], 'Space', |
| 'Contents', 'Index'], |
| '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 ], |
| 'Space', 'Contents', 'Index'], |
| |
| 'LINKS_DIRECTIONS' => ['Top', 'Index', 'Contents', 'About', |
| 'NodeUp', 'NodeNext', 'NodePrev'], |
| |
| 'ACTIVE_ICONS' => undef, |
| 'PASSIVE_ICONS' => undef, |
| ); |
| |
| foreach my $buttons ('CHAPTER_BUTTONS', 'TOP_BUTTONS') { |
| $defaults{$buttons} = [@{$defaults{'SECTION_BUTTONS'}}]; |
| } |
| |
| $defaults{'MISC_BUTTONS'} = ['Top', 'Contents', 'Index', 'About']; |
| |
| foreach my $buttons ('CHAPTER_FOOTER_BUTTONS', 'TOP_FOOTER_BUTTONS') { |
| $defaults{$buttons} = [@{$defaults{'SECTION_FOOTER_BUTTONS'}}]; |
| } |
| |
| |
| |
| # API for html formatting |
| |
| sub close_html_lone_element($$) { |
| my ($self, $html_element) = @_; |
| |
| if ($self->get_conf('USE_XML_SYNTAX')) { |
| return $html_element . '/>'; |
| } |
| return $html_element .'>'; |
| } |
| |
| my $xml_named_entity_nbsp = ' '; |
| |
| my $html_default_entity_nbsp = $xml_named_entity_nbsp; |
| |
| sub substitute_html_non_breaking_space($$) { |
| my ($self, $text) = @_; |
| |
| my $non_breaking_space = $self->get_info('non_breaking_space'); |
| # using \Q \E on the substitution leads to spurious \ |
| $text =~ s/\Q$html_default_entity_nbsp\E/$non_breaking_space/g; |
| return $text; |
| } |
| |
| my @image_files_extensions = ('.png', '.jpg', '.jpeg', '.gif'); |
| |
| # this can be used in init files to get the path of the image |
| # files. In general the result of image formatting cannot |
| # be used to get an image file name path, as the path is not |
| # used in the output. |
| sub html_image_file_location_name($$$$$) { |
| my ($self, $cmdname, $command, $image_basefile, $args) = @_; |
| |
| my @extensions = @image_files_extensions; |
| |
| my $image_file; |
| my $image_extension; |
| # this variable is bytes encoded in the filesystem encoding |
| my ($image_path, $image_path_encoding); |
| my $extension; |
| # NOTE should be consistent with $image_basefile formatting |
| if (defined($args->[4]) and defined($args->[4]->{'filenametext'})) { |
| $extension = $args->[4]->{'filenametext'}; |
| unshift @extensions, ("$extension", ".$extension"); |
| } |
| foreach my $tried_extension (@extensions) { |
| my ($file_name, $file_name_encoding) |
| = $self->encoded_input_file_name($image_basefile.$tried_extension); |
| my $located_image_path |
| = Texinfo::Common::locate_include_file($file_name, |
| $self->get_conf('INCLUDE_DIRECTORIES')); |
| if (defined($located_image_path) and $located_image_path ne '') { |
| $image_path = $located_image_path; |
| $image_path_encoding = $file_name_encoding; |
| # use the @-command argument and not the file found using the |
| # include paths. It is considered that the files in include paths |
| # will be moved by the caller anyway. |
| # If the file path found was to be used it should be decoded to perl |
| # codepoints too. |
| $image_file = $image_basefile.$tried_extension; |
| $image_extension = $tried_extension; |
| last; |
| } |
| } |
| if (!defined($image_file) or $image_file eq '') { |
| if (defined($extension) and $extension ne '') { |
| $image_file = $image_basefile.$extension; |
| $image_extension = $extension; |
| } else { |
| $image_file = "$image_basefile.jpg"; |
| $image_extension = '.jpg'; |
| } |
| } |
| return ($image_file, $image_extension, $image_path, |
| $image_path_encoding); |
| } |
| |
| # flags used to specify the conversion contexts. Use flags to avoid |
| # passing multiple arguments or hashes, and also could be easier for |
| # passing to C. |
| our $CTXF_string = 0x0001; |
| our $CTXF_code = 0x0002; |
| our $CTXF_normal = 0x0004; |
| |
| my %default_css_string_commands_conversion; |
| my %default_css_string_types_conversion; |
| my %default_css_string_formatting_references; |
| |
| sub _html_convert_css_string($$$) { |
| my ($self, $element, $context_str) = @_; |
| |
| my $saved_commands = {}; |
| my $saved_types = {}; |
| my $saved_formatting_references = {}; |
| foreach my $cmdname (keys(%default_css_string_commands_conversion)) { |
| $saved_commands->{$cmdname} = $self->{'commands_conversion'}->{$cmdname}; |
| $self->{'commands_conversion'}->{$cmdname} |
| = $default_css_string_commands_conversion{$cmdname}; |
| } |
| foreach my $type (keys(%default_css_string_types_conversion)) { |
| $saved_types->{$type} = $self->{'types_conversion'}->{$type}; |
| $self->{'types_conversion'}->{$type} |
| = $default_css_string_types_conversion{$type}; |
| } |
| foreach my $formatting_reference |
| (keys(%default_css_string_formatting_references)) { |
| $saved_formatting_references->{$formatting_reference} |
| = $self->{'formatting_function'}->{$formatting_reference}; |
| $self->{'formatting_function'}->{$formatting_reference} |
| = $default_css_string_formatting_references{$formatting_reference}; |
| } |
| my $css_string_context_str = 'CSS string '.$context_str; |
| _new_document_context($self, $css_string_context_str, $CTXF_string); |
| my $result |
| = $self->convert_tree($element, "new_fmt_ctx C($css_string_context_str)"); |
| _pop_document_context($self); |
| |
| foreach my $cmdname (keys (%default_css_string_commands_conversion)) { |
| $self->{'commands_conversion'}->{$cmdname} = $saved_commands->{$cmdname}; |
| } |
| foreach my $type (keys(%default_css_string_types_conversion)) { |
| $self->{'types_conversion'}->{$type} = $saved_types->{$type}; |
| } |
| foreach my $formatting_reference (keys(%default_css_string_formatting_references)) { |
| $self->{'formatting_function'}->{$formatting_reference} |
| = $saved_formatting_references->{$formatting_reference}; |
| } |
| return $result; |
| } |
| |
| our %special_list_mark_css_string_no_arg_command = ( |
| # tried to use HYPHEN BULLET \2043 for use as in a bullet list, but, at least |
| # with my test of firefox the result is very different from a bullet. |
| # hyphen minus or hyphen \2010 are even smaller than hyphen bullet. |
| # Use the Unicode codepoint used normally for a mathematical minus \2212 |
| # even though it is too large, since the others are too short... |
| # (which is actually the default, but this could change). |
| #'minus' => '-', |
| #'minus' => '\2010 ', |
| 'minus' => '\2212 ', |
| ); |
| |
| sub html_convert_css_string_for_list_mark($$;$) { |
| my ($self, $element, $explanation) = @_; |
| |
| my $saved_css_string_no_arg_command = {}; |
| foreach my $cmdname (keys(%special_list_mark_css_string_no_arg_command)) { |
| $saved_css_string_no_arg_command->{$cmdname} |
| = $self->{'no_arg_commands_formatting'}->{$cmdname}->{'css_string'}; |
| $self->{'no_arg_commands_formatting'}->{$cmdname}->{'css_string'} |
| = $special_list_mark_css_string_no_arg_command{$cmdname}; |
| } |
| my $result = _html_convert_css_string($self, $element, $explanation); |
| foreach my $cmdname (keys(%special_list_mark_css_string_no_arg_command)) { |
| $self->{'no_arg_commands_formatting'}->{$cmdname}->{'css_string'} |
| = $saved_css_string_no_arg_command->{$cmdname}; |
| } |
| return $result; |
| } |
| |
| # Protect an url, in which characters with specific meaning in url are |
| # considered to have their specific meaning. |
| sub url_protect_url_text($$) { |
| my ($self, $input_string) = @_; |
| |
| # turn end of lines to spaces, as it is most likely what is expected |
| # rather than a percent encoded end of line. |
| $input_string =~ s/[\n\r]+/ /g; |
| # percent encode character string. It is better use UTF-8 irrespective |
| # of the actual charset of the HTML output file, according to the tests done. |
| my $href = encode("UTF-8", $input_string); |
| # protect 'ligntly', do not protect unreserved and reserved characters + the % itself |
| $href =~ s/([^^A-Za-z0-9\-_.!~*'()\$&+,\/:;=\?@\[\]\#%])/ sprintf "%%%02x", ord $1 /eg; |
| return &{$self->formatting_function('format_protect_text')}($self, $href); |
| } |
| |
| # Protect a file path used in an url. Characters appearing in file paths |
| # are not protected. All the other characters that can be percent |
| # protected are protected, including characters with specific meaning in url. |
| sub url_protect_file_text($$) { |
| my ($self, $input_string) = @_; |
| |
| # turn end of lines to spaces, as it is most likely what is expected. |
| $input_string =~ s/[\n\r]+/ /g; |
| # percent encode character string. It is better use UTF-8 irrespective |
| # of the actual charset of the HTML output file, according to the tests done. |
| my $href = encode("UTF-8", $input_string); |
| # protect everything that can be special in url except ~, / and : that could |
| # appear in file names and does not have much risk in being incorrectly |
| # interpreted (for :, the interpretation as a scheme delimiter may be possible). |
| $href =~ s/([^^A-Za-z0-9\-_.~\/:])/ sprintf "%%%02x", ord $1 /eg; |
| return &{$self->formatting_function('format_protect_text')}($self, $href); |
| } |
| |
| |
| |
| # API for links and elements directions formatting. |
| |
| # Functions that do not have an XS override. The reason why there is no |
| # XS override is that the tree elements with {'extra'}->{'manual_content'} |
| # cannot easily be found in the C tree based on the Perl tree element. |
| |
| 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; |
| } |
| |
| # returns file base name, extension and anchor associated to node |
| # (anchor, float...) command adhering strictly to the HTML Xref specification. |
| # The $CROSSREF_EXTENSION argument should be the external crossreference |
| # filename extension, if undef, the $EXTENSION argument is used. |
| sub _standard_label_id_file($$$$$) { |
| my ($self, $normalized, $label_element, $crossref_extension, |
| $extension) = @_; |
| |
| my $target; |
| my $filename; |
| if (!defined($normalized) and defined($label_element)) { |
| $normalized |
| = Texinfo::Convert::NodeNameNormalization::convert_to_node_identifier( |
| $label_element); |
| } |
| my $options = \%Texinfo::Options::converter_customization_options; |
| |
| if (defined($normalized)) { |
| $target = _normalized_to_id($normalized); |
| |
| # use default, not user-defined value |
| my $basefilename_length = $options->{'BASEFILENAME_LENGTH'}; |
| $filename = substr($normalized, 0, $basefilename_length); |
| } else { |
| $target = ''; |
| $filename = ''; |
| } |
| # to find out the Top node, one could check $normalized |
| if (defined($self->{'file_id_setting'}->{'label_target_name'})) { |
| $target = &{$self->{'file_id_setting'}->{'label_target_name'}}($self, |
| $normalized, $label_element, $target); |
| } |
| |
| my $file_extension = ''; |
| my $external_extension = $crossref_extension; |
| $external_extension = $extension |
| if (not defined($external_extension)); |
| $file_extension = '.' . $external_extension |
| if (defined($external_extension) and $external_extension ne ''); |
| |
| return ($filename, $file_extension, $target); |
| } |
| |
| our %htmlxref_entries = ( |
| 'node' => [ 'node', 'section', 'chapter', 'mono' ], |
| 'section' => [ 'section', 'chapter','node', 'mono' ], |
| 'chapter' => [ 'chapter', 'section', 'node', 'mono' ], |
| 'mono' => [ 'mono', 'chapter', 'section', 'node' ], |
| ); |
| |
| sub _external_node_href($$$) { |
| my ($self, $external_node, |
| # for messages only |
| $source_command) = @_; |
| |
| my $normalized = $external_node->{'extra'}->{'normalized'}; |
| my $node_contents = $external_node->{'extra'}->{'node_content'}; |
| #print STDERR "external_node: ".join('|', keys(%$external_node))."\n"; |
| my ($target_filebase, $external_file_extension, $target) |
| = _standard_label_id_file($self, $normalized, $node_contents, |
| $self->get_conf('EXTERNAL_CROSSREF_EXTENSION'), |
| $defaults{'EXTENSION'}); |
| |
| # always undef if conversion is called through convert() |
| my $default_target_split = $self->get_conf('EXTERNAL_CROSSREF_SPLIT'); |
| |
| # initialize to $default_target_split |
| my $is_target_split; |
| if ($default_target_split) { |
| $is_target_split = 1; |
| } else { |
| $is_target_split = 0; |
| } |
| # used if !$is_target_split |
| my $file = ''; |
| # used if $is_target_split |
| my $directory = ''; |
| if (exists($external_node->{'extra'}->{'manual_content'})) { |
| Texinfo::Convert::Text::set_options_code($self->{'convert_text_options'}); |
| my $manual_name = Texinfo::Convert::Text::convert_to_text( |
| $external_node->{'extra'}->{'manual_content'}, |
| $self->{'convert_text_options'}); |
| Texinfo::Convert::Text::reset_options_code($self->{'convert_text_options'}); |
| if ($self->get_conf('IGNORE_REF_TO_TOP_NODE_UP') and $target eq '') { |
| my $top_node_up = $self->get_conf('TOP_NODE_UP'); |
| if (defined($top_node_up) and "($manual_name)" eq $top_node_up) { |
| return ''; |
| } |
| } |
| my $manual_base = $manual_name; |
| # in 2023 there were manuals with .info. Warning added in 2024. |
| if ($manual_base =~ s/(\.info?)$//) { |
| $self->converter_line_warn(sprintf(__( |
| "do not set %s suffix in reference for manual `%s'"), |
| $1, $manual_name), |
| $source_command->{'source_info'}); |
| } |
| $manual_base =~ s/^.*\///; |
| my $split_found; |
| my $htmlxref_href; |
| my $htmlxref_mode = $self->get_conf('HTMLXREF_MODE'); |
| |
| if (!defined($htmlxref_mode) or $htmlxref_mode ne 'none') { |
| if (exists($self->{'htmlxref'}->{$manual_base})) { |
| my $htmlxref_info = $self->{'htmlxref'}->{$manual_base}; |
| my $document_split = $self->get_conf('SPLIT'); |
| $document_split = 'mono' if (!$document_split); |
| foreach my $split_ordered (@{$htmlxref_entries{$document_split}}) { |
| if (exists($htmlxref_info->{$split_ordered})) { |
| $split_found = $split_ordered; |
| if ($htmlxref_info->{$split_ordered} ne '') { |
| $htmlxref_href |
| = $self->url_protect_url_text($htmlxref_info->{$split_ordered}); |
| } |
| last; |
| } |
| } |
| } |
| if (defined($split_found)) { |
| if ($split_found eq 'mono') { |
| $is_target_split = 0; |
| } else { |
| $is_target_split = 1; |
| } |
| } else { # nothing specified for that manual, use default |
| if ($self->get_conf('CHECK_HTMLXREF')) { |
| if (defined($source_command) and $source_command->{'source_info'}) { |
| if (!_check_htmlxref_already_warned($self, $manual_name, |
| $source_command->{'source_info'})) { |
| $self->converter_line_warn(sprintf(__( |
| "no HTML cross-references entry found for `%s'"), $manual_name), |
| $source_command->{'source_info'}); |
| } |
| } else { |
| if (!_check_htmlxref_already_warned($self, $manual_name, undef)) { |
| $self->converter_document_warn(sprintf(__( |
| "no HTML cross-references entry found for `%s'"), $manual_name), |
| ); |
| cluck; |
| } |
| } |
| } |
| } |
| } |
| |
| if ($is_target_split) { |
| if (defined($htmlxref_href)) { |
| $directory = $htmlxref_href; |
| } else { |
| if (defined($self->get_conf('EXTERNAL_DIR'))) { |
| $directory = $self->get_conf('EXTERNAL_DIR')."/$manual_base"; |
| } elsif ($self->get_conf('SPLIT')) { |
| $directory = "../$manual_base"; |
| } |
| my $output_format = $self->get_conf('TEXINFO_OUTPUT_FORMAT'); |
| if (defined($output_format) and $output_format ne '') { |
| $directory .= '_'.$output_format; |
| } |
| $directory = $self->url_protect_file_text($directory); |
| } |
| $directory .= "/"; |
| } else {# target not split |
| if (defined($htmlxref_href)) { |
| $file = $htmlxref_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; |
| |
| $file = $self->url_protect_file_text($file); |
| } |
| } |
| } |
| |
| if ($is_target_split) { |
| my $file_name; |
| if (($target eq 'Top' or $target eq '') |
| 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; |
| } |
| if (defined($self->{'file_id_setting'}->{'external_target_split_name'})) { |
| ($target, $directory, $file_name) |
| = &{$self->{'file_id_setting'}->{'external_target_split_name'}}($self, |
| $normalized, $external_node, $target, |
| $directory, $file_name); |
| $directory = '' if (!defined($directory)); |
| $file_name = '' if (!defined($file_name)); |
| $target = '' if (!defined($target)); |
| } |
| my $result = $directory . $file_name; |
| if ($target ne '') { |
| $result .= '#' . $target; |
| } |
| return $result; |
| } else { |
| if ($target eq '') { |
| $target = 'Top'; |
| } |
| if (defined($self->{'file_id_setting'}->{ |
| 'external_target_non_split_name'})) { |
| ($target, $file) |
| = &{$self->{'file_id_setting'}->{'external_target_non_split_name'}}($self, |
| $normalized, $external_node, $target, $file); |
| $file = '' if (!defined($file)); |
| $target = '' if (!defined($target)); |
| } |
| my $result = $file; |
| if ($target ne '') { |
| $result .= '#' . $target; |
| } |
| return $result; |
| } |
| } |
| |
| sub command_href($$;$$$) { |
| my ($self, $command, $source_filename, $source_command, |
| $specified_target) = @_; |
| |
| if (exists($command->{'extra'}) |
| and exists($command->{'extra'}->{'manual_content'})) { |
| return _external_node_href($self, $command, $source_command); |
| } |
| |
| return _internal_command_href($self, $command, $source_filename, |
| $specified_target); |
| } |
| |
| our %contents_command_special_unit_variety = ( |
| 'contents' => 'contents', |
| 'shortcontents' => 'shortcontents', |
| 'summarycontents' => 'shortcontents', |
| ); |
| |
| # Return text to be used for $COMMAND. |
| # $TYPE refers to the type of value returned from this function: |
| # 'text' - return text |
| # 'text_nonumber' - return text, without the section/chapter number |
| # 'string' - return simpler text that can be used in element attributes |
| # 'string_nonumber' - same as string, without the section/chapter number |
| sub command_text($$;$) { |
| my ($self, $command, $type) = @_; |
| |
| if (!defined($type)) { |
| $type = 'text'; |
| } |
| |
| if (!defined($command)) { |
| cluck "in command_text($type) command not defined"; |
| } |
| |
| if (exists($command->{'extra'}) |
| and exists($command->{'extra'}->{'manual_content'})) { |
| |
| my $tree = Texinfo::TreeElement::new( |
| {'contents' => [Texinfo::TreeElement::new({'text' => '('}), |
| $command->{'extra'}->{'manual_content'}, |
| Texinfo::TreeElement::new({'text' => ')'})]}); |
| if (exists($command->{'extra'}->{'node_content'})) { |
| push @{$tree->{'contents'}}, $command->{'extra'}->{'node_content'}; |
| } |
| |
| my $context_str = "command_text $type "; |
| if (exists($command->{'cmdname'})) { |
| # this never happens, as the external node label tree |
| # element is never directly an @-command. It can be an @-command |
| # argument, in a menu, or a reconstituted tree. |
| $context_str .= '@'.$command->{'cmdname'}; |
| } elsif (exists($command->{'type'})) { |
| $context_str .= $command->{'type'}; |
| } |
| |
| my $context_type = $CTXF_code; |
| if ($type eq 'string' or $type eq 'string_nonumber') { |
| $context_type |= $CTXF_string; |
| } |
| |
| # NOTE the multiple pass argument is not unicized, and no global |
| # context argument is given because this external node manual label |
| # should in general be converted only once. |
| # In addition, regarding multiple pass, it is unlikely for |
| # @-commands which should better be converted only once to be present. |
| my $result |
| = $self->convert_tree_new_formatting_context($tree, |
| $context_str, $context_type, |
| 'command_text-manual_content'); |
| |
| return $result; |
| } |
| |
| return _internal_command_text($self, $command, $type); |
| } |
| |
| # Return text to be used for $COMMAND using the name rather than the |
| # label/identifier, when the distinction exists. |
| # $TYPE refers to the type of value returned from this function: |
| # 'text' - return text |
| # 'text_nonumber' - return text, without the section/chapter number |
| # 'string' - return simpler text that can be used in element attributes |
| sub command_name($$;$) { |
| my ($self, $command, $type) = @_; |
| |
| if (!defined($type)) { |
| $type = 'text'; |
| } |
| |
| if (!defined($command)) { |
| cluck "in command_name($type) command not defined"; |
| } |
| |
| if (exists($command->{'extra'}) |
| and exists($command->{'extra'}->{'manual_content'})) { |
| return command_text($self, $command, $type); |
| } |
| |
| return _internal_command_name($self, $command, $type); |
| } |
| |
| # This function could have had an XS override. However |
| # there is no equivalent function is C, the corresponding |
| # code being inlined, and the functions called are overriden. |
| # Therefore, an XS override would not be very interesting. |
| sub command_name_special_unit_information($$) { |
| my ($self, $cmdname) = @_; |
| |
| my $special_unit_variety; |
| if (exists($contents_command_special_unit_variety{$cmdname})) { |
| $special_unit_variety |
| = $contents_command_special_unit_variety{$cmdname}; |
| } elsif ($cmdname eq 'footnote') { |
| $special_unit_variety = 'footnotes'; |
| } else { |
| return (undef, undef, undef, undef); |
| } |
| my $special_unit_direction |
| = $self->special_unit_info('direction', $special_unit_variety); |
| my $special_unit |
| = $self->global_direction_unit($special_unit_direction); |
| my $class_base |
| = $self->special_unit_info('class', $special_unit_variety); |
| return ($special_unit_variety, $special_unit, $class_base, |
| $special_unit_direction); |
| } |
| |
| sub _html_get_tree_root_element($$;$); |
| |
| # If $FIND_CONTAINER is set, the element that holds the command output |
| # is found, otherwise the element that holds the command is found. This is |
| # mostly relevant for footnote only. |
| # If no known root element type is found, the returned root element is undef, |
| # and not set to the element at the tree root |
| sub _html_get_tree_root_element($$;$) { |
| my ($self, $command, $find_container) = @_; |
| |
| # can be used to debug/understand what is going on |
| #my $debug = 1; |
| |
| my $current = $command; |
| #print STDERR "START ".Texinfo::Common::debug_print_element($current)."\n" if ($debug); |
| |
| my ($output_unit, $root_command); |
| while (1) { |
| if (exists($current->{'type'}) |
| and $current->{'type'} eq 'special_unit_element') { |
| return ($current->{'associated_unit'}, $current); |
| } |
| if (exists($current->{'cmdname'})) { |
| if (exists($root_commands{$current->{'cmdname'}})) { |
| $root_command = $current; |
| #print STDERR "CMD ROOT $current->{'cmdname'}\n" if ($debug); |
| } elsif (exists($block_commands{$current->{'cmdname'}}) |
| and $block_commands{$current->{'cmdname'}} eq 'region') { |
| if ($current->{'cmdname'} eq 'copying' |
| and exists($self->{'document'})) { |
| my $global_commands |
| = $self->{'document'}->global_commands_information(); |
| if (defined($global_commands) |
| and exists($global_commands->{'insertcopying'})) { |
| foreach my $insertcopying (@{$global_commands |
| ->{'insertcopying'}}) { |
| #print STDERR "INSERTCOPYING\n" if ($debug); |
| my ($output_unit, $root_command) |
| = _html_get_tree_root_element($self, $insertcopying, |
| $find_container); |
| return ($output_unit, $root_command) |
| if (defined($output_unit) or defined($root_command)); |
| } |
| } |
| } elsif ($current->{'cmdname'} eq 'titlepage' |
| and $self->get_conf('USE_TITLEPAGE_FOR_TITLE') |
| and $self->get_conf('SHOW_TITLE')) { |
| #print STDERR "FOR titlepage document_units [0]\n" if ($debug); |
| return ($self->{'document_units'}->[0], |
| $self->{'document_units'}->[0]->{'unit_command'}); |
| } |
| die "Problem $output_unit, $root_command" if (defined($output_unit) |
| or defined($root_command)); |
| return (undef, undef); |
| } elsif ($find_container) { |
| # @footnote and possibly @*contents when a separate element is set |
| my ($special_unit_variety, $special_unit, $class_base, |
| $special_unit_direction) |
| = $self->command_name_special_unit_information($current->{'cmdname'}); |
| if (defined($special_unit)) { |
| #print STDERR "SPECIAL $current->{'cmdname'}: $special_unit_variety ($special_unit_direction)\n" if ($debug); |
| return ($special_unit, undef); |
| } |
| } |
| } |
| if (exists($current->{'associated_unit'})) { |
| #print STDERR "ASSOCIATED_UNIT ".Texinfo::OutputUnits::debug_print_output_unit($current->{'associated_unit'})."\n" if ($debug); |
| return ($current->{'associated_unit'}, $root_command); |
| } elsif (exists($current->{'parent'})) { |
| #print STDERR "PARENT ".Texinfo::Common::debug_print_element($current->{'parent'})."\n" if ($debug); |
| $current = $current->{'parent'}; |
| } else { |
| #print STDERR "UNKNOWN ROOT ".Texinfo::Common::debug_print_element($current)."\n" if ($debug); |
| return (undef, $root_command); |
| } |
| } |
| } |
| |
| # No XS, the C data element cannot in general be retrieved. |
| sub get_element_root_command_element($$) { |
| my ($self, $element) = @_; |
| |
| my ($output_unit, $root_command) |
| = _html_get_tree_root_element($self, $element); |
| if (defined($root_command)) { |
| if ($self->get_conf('USE_NODES')) { |
| if (exists($root_command->{'cmdname'})) { |
| if ($root_command->{'cmdname'} eq 'node') { |
| return ($output_unit, $root_command); |
| } elsif (exists($self->{'document'})) { |
| my $sections_list = $self->{'document'}->sections_list(); |
| my $section_relations |
| = $sections_list->[$root_command->{'extra'}->{'section_number'} -1]; |
| if (exists($section_relations->{'associated_node'})) { |
| return ($output_unit, |
| $section_relations->{'associated_node'}->{'element'}); |
| } |
| } |
| } |
| } elsif (exists($root_command->{'cmdname'}) |
| and $root_command->{'cmdname'} eq 'node') { |
| if (exists($self->{'document'})) { |
| my $nodes_list = $self->{'document'}->nodes_list(); |
| my $node_relations |
| = $nodes_list->[$root_command->{'extra'}->{'node_number'} -1]; |
| if (exists($node_relations->{'associated_section'})) { |
| return ($output_unit, |
| $node_relations->{'associated_section'}->{'element'}); |
| } |
| } |
| } |
| } |
| return ($output_unit, $root_command); |
| } |
| |
| my %valid_direction_return_type = ( |
| # a string that can be used in a href linking to the direction |
| 'href' => 1, |
| # a string representing the direction that can be used in |
| # context where only entities are available (ie with HTML attributes) |
| 'string' => 1, |
| # a string representing the direction to be used in contexts |
| # not restricted in term of available formatting (ie with HTML elements) |
| 'text' => 1, |
| # same as 'text', but select node in priority |
| 'node' => 1, |
| # same as 'text_nonumber' but select section in priority |
| 'section' => 1 |
| ); |
| |
| foreach my $no_number_type ('text', 'string', 'section') { |
| # without section number |
| $valid_direction_return_type{$no_number_type .'_nonumber'} = 1; |
| } |
| |
| # sub from_element_direction($SELF, $DIRECTION, $TYPE, $SOURCE_UNIT, |
| # $SOURCE_FILENAME, $SOURCE_FOR_MESSAGES) |
| # |
| # Return text used for linking from $SOURCE_UNIT in direction $DIRECTION. |
| # The text returned depends on $TYPE. |
| # |
| # This is used both for output units and external nodes |
| # |
| # If $SOURCE_UNIT is undef, $self->current_output_unit() is used. |
| # |
| # $SOURCE_FOR_MESSAGES is an element used for messages formatting, to get a |
| # location in input file. It is better to choose the node and not the |
| # sectioning command associated with the element, as the error messages |
| # are about external nodes not found. |
| # |
| # $self->current_output_unit() undef happens when there is no |
| # output file. In the test suite, that call results only from |
| # from_element_direction being called from _get_links, itself |
| # called from 'format_begin_file' ultimately called from output |
| # without output file. There could probably be other cases |
| # with crafted/test code, but it should never happen when output is |
| # called from the main program as there is always an output file. |
| sub from_element_direction($$$;$$$) { |
| my ($self, $direction, $type, $source_unit, $source_filename, |
| # for messages only |
| $source_command) = @_; |
| |
| my $target_unit; |
| my $command; |
| |
| $source_unit = $self->current_output_unit() if (!defined($source_unit)); |
| # NOTE $source_filename is only used for a command_href call. If with XS, |
| # if source_filename remains undef, the command_href XS code will set the |
| # source_filename to the current filename in XS. Therefore undef |
| # current_filename in that case leads to the same output as set |
| # current_filename. |
| # We still set it correctly in case it becomes used in other codes. |
| $source_filename = $self->current_filename() if (!defined($source_filename)); |
| if (!exists($valid_direction_return_type{$type})) { |
| print STDERR "Incorrect type $type in from_element_direction call\n"; |
| return undef; |
| } |
| my $global_target_unit = $self->global_direction_unit($direction); |
| if (defined($global_target_unit)) { |
| $target_unit = $global_target_unit; |
| # output TOP_NODE_UP related info even if $source_unit is not defined, |
| # which should correspond to cases when there is no output file, mainly in |
| # tests. |
| } elsif ((not defined($source_unit) |
| or ($source_unit |
| and $self->unit_is_top_output_unit($source_unit))) |
| 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' or $type eq 'section_nonumber' |
| or $type eq 'string_nonumber') { |
| return $self->get_conf('TOP_NODE_UP'); |
| } else { |
| cluck("BUG: type $type not available for TOP_NODE_UP\n"); |
| return ''; |
| } |
| } elsif (not defined($target_unit) and defined($source_unit) |
| and exists($source_unit->{'directions'}) |
| and exists($source_unit->{'directions'}->{$direction})) { |
| $target_unit |
| = $source_unit->{'directions'}->{$direction}; |
| } |
| |
| if (defined($target_unit)) { |
| ######## debug |
| if (!exists($target_unit->{'unit_type'})) { |
| die "No unit type for element_target $direction $target_unit: " |
| . Texinfo::OutputUnits::debug_print_output_unit($target_unit) |
| . "directions :" |
| . Texinfo::OutputUnits::print_output_unit_directions($source_unit); |
| } |
| ######## |
| if ($target_unit->{'unit_type'} eq 'external_node_unit') { |
| my $external_node_element = $target_unit->{'unit_command'}; |
| #print STDERR "FROM_ELEMENT_DIRECTION ext node $type $direction\n" |
| # if ($self->get_conf('DEBUG')); |
| if ($type eq 'href') { |
| return _external_node_href($self, $external_node_element, |
| $source_command); |
| } elsif ($type eq 'text' or $type eq 'node') { |
| return $self->command_text($external_node_element); |
| } elsif ($type eq 'string') { |
| return $self->command_text($external_node_element, $type); |
| } |
| } elsif ($type eq 'node') { |
| if (exists($target_unit->{'unit_node'})) { |
| $command = $target_unit->{'unit_node'}->{'element'}; |
| } |
| $type = 'text'; |
| } elsif ($type eq 'section' or $type eq 'section_nonumber') { |
| if (exists($target_unit->{'unit_section'})) { |
| $command = $target_unit->{'unit_section'}->{'element'}; |
| } |
| if ($type eq 'section_nonumber') { |
| $type = 'text_nonumber'; |
| } else { |
| $type = 'text'; |
| } |
| } else { |
| $command = $target_unit->{'unit_command'}; |
| if ($type eq 'href') { |
| if (defined($command)) { |
| return $self->command_href($command, $source_filename); |
| } else { |
| return undef; |
| } |
| } |
| } |
| } else { |
| return undef; |
| } |
| |
| if (defined($command)) { |
| #print STDERR "FROM_ELEMENT_DIRECTION $type $direction\n" |
| # if ($self->get_conf('DEBUG')); |
| return $self->command_text($command, $type); |
| } |
| # We end up here if there is a target element, but not of the expected |
| # type. For example, if type is section but there is no section associated |
| # to the target element node. |
| return undef; |
| } |
| |
| |
| |
| |
| # API for misc conversion and formatting functions |
| |
| my %default_formatting_references; |
| sub default_formatting_function($$) { |
| my ($self, $format) = @_; |
| |
| return $default_formatting_references{$format}; |
| } |
| |
| sub formatting_function($$) { |
| my ($self, $format) = @_; |
| |
| return $self->{'formatting_function'}->{$format}; |
| } |
| |
| my %defaults_format_special_unit_body_contents; |
| |
| sub defaults_special_unit_body_formatting($$) { |
| my ($self, $special_unit_variety) = @_; |
| |
| return $defaults_format_special_unit_body_contents{$special_unit_variety}; |
| } |
| |
| sub special_unit_body_formatting($$) { |
| my ($self, $special_unit_variety) = @_; |
| |
| return $self->{'special_unit_body'}->{$special_unit_variety}; |
| } |
| |
| # Return the default for the function references used for |
| # the formatting of commands, in case a user still wants to call |
| # default @-commands formatting functions when replacing functions, |
| # using code along |
| # &{$self->default_command_conversion($cmdname)}($self, $cmdname, $command, args, $content) |
| our %default_commands_conversion; |
| |
| sub default_command_conversion($$) { |
| my ($self, $cmdname) = @_; |
| |
| return $default_commands_conversion{$cmdname}; |
| } |
| |
| sub command_conversion($$) { |
| my ($self, $cmdname) = @_; |
| |
| return $self->{'commands_conversion'}->{$cmdname}; |
| } |
| |
| my %default_commands_open; |
| |
| sub default_command_open($$) { |
| my ($self, $cmdname) = @_; |
| |
| return $default_commands_open{$cmdname}; |
| } |
| |
| # The default shared conversion states are accessed and set through |
| # XS, if enabled, but user added shared conversion states are only |
| # in Perl. |
| |
| our %default_shared_conversion_states = ( |
| 'top' => {'in_skipped_node_top' => ['integer'],}, |
| 'abbr' => {'explained_commands' => ['string', 'string']}, |
| 'acronym' => {'explained_commands' => ['string', 'string']}, |
| 'footnote' => {'footnote_number' => ['integer'], |
| 'footnote_id_numbers' => ['string', 'integer']}, |
| 'listoffloats' => {'formatted_listoffloats' => ['string', 'integer']}, |
| 'menu' => {'html_menu_entry_index' => ['integer']}, |
| 'printindex' => {'formatted_index_entries' => ['index_entry', 'integer']}, |
| 'nodedescription' => {'formatted_nodedescriptions' => ['element', 'integer']}, |
| # also used for titlepage |
| 'quotation' => {'quotation_titlepage_stack' => ['integer'], |
| 'elements_authors' => ['integer', 'integer', 'element'], |
| 'element_authors_number' => ['integer', 'integer']}, |
| ); |
| |
| # used in user-defined code |
| sub define_shared_conversion_state($$$$) { |
| my ($self, $cmdname, $state_name, $specification) = @_; |
| |
| if (not exists($self->{'shared_conversion_state'}->{$cmdname})) { |
| $self->{'shared_conversion_state'}->{$cmdname} = {}; |
| } |
| if (not exists($self->{'shared_conversion_state'} |
| ->{$cmdname}->{$state_name})) { |
| $self->{'shared_conversion_state'}->{$cmdname}->{$state_name} = {}; |
| } |
| |
| my $state = $self->{'shared_conversion_state'}->{$cmdname}->{$state_name}; |
| |
| if (exists($state->{'spec'})) { |
| warn("BUG: redefining shared_conversion_state: $cmdname: $state_name"); |
| } |
| $state->{'spec'} = $specification; |
| } |
| |
| sub _get_shared_conversion_state($$$;@) { |
| my $self = shift; |
| my $cmdname = shift; |
| my $state_name = shift; |
| my @args = @_; |
| |
| my $state = $self->{'shared_conversion_state'}->{$cmdname}->{$state_name}; |
| |
| if (!defined($state)) { |
| #print STDERR "DEBUG: [". |
| # join('|',keys(%{$self->{'shared_conversion_state'}->{$cmdname}}))."]\n"; |
| confess("BUG: $self: undef shared_conversion_state: $cmdname: $state_name\n"); |
| } |
| |
| my $spec_nr = scalar(@{$state->{'spec'}}); |
| |
| if ($spec_nr == 1) { |
| return $state->{'values'}; |
| } |
| |
| if (!defined($state->{'values'})) { |
| $state->{'values'} = {}; |
| } |
| my $spec_idx = 1; |
| my $current = $state->{'values'}; |
| foreach my $arg (@args) { |
| if (!defined($arg)) { |
| return $current; |
| } |
| if ($spec_idx == $spec_nr - 1) { |
| return $current->{$arg}; |
| } |
| if (!$current->{$arg}) { |
| $current->{$arg} = {}; |
| } |
| $current = $current->{$arg}; |
| $spec_idx++; |
| } |
| return $current; |
| } |
| |
| sub get_shared_conversion_state($$$;@) { |
| my $self = shift; |
| my $cmdname = shift; |
| my $state_name = shift; |
| my @args = @_; |
| |
| if (exists($default_shared_conversion_states{$cmdname}) |
| and exists($default_shared_conversion_states{$cmdname}->{$state_name})) { |
| my $result = _XS_get_shared_conversion_state($self, $cmdname, |
| $state_name, @args); |
| return $result; |
| } |
| |
| return _get_shared_conversion_state($self, $cmdname, |
| $state_name, @args); |
| } |
| |
| sub _set_shared_conversion_state($$$;@) { |
| my $self = shift; |
| my $cmdname = shift; |
| my $state_name = shift; |
| my @args = @_; |
| |
| my $state = $self->{'shared_conversion_state'}->{$cmdname}->{$state_name}; |
| |
| my $spec_nr = scalar(@{$state->{'spec'}}); |
| if (scalar(@args) != $spec_nr) { |
| return undef; |
| } |
| |
| if ($spec_nr == 1) { |
| if (!defined($args[0])) { |
| return undef; |
| } |
| $state->{'values'} = $args[0]; |
| return $args[0]; |
| } |
| |
| if (!exists($state->{'values'})) { |
| $state->{'values'} = {}; |
| } |
| my $spec_idx = 1; |
| my $current = $state->{'values'}; |
| foreach my $arg (@args) { |
| if (!defined($arg)) { |
| return undef; |
| } |
| if ($spec_idx == $spec_nr - 1) { |
| $current->{$arg} = $args[$spec_idx]; |
| return $current->{$arg}; |
| } |
| if (!exists($current->{$arg})) { |
| $current->{$arg} = {}; |
| } |
| $current = $current->{$arg}; |
| $spec_idx++; |
| } |
| } |
| |
| # XS is only used for default conversion states. |
| sub set_shared_conversion_state($$$;@) { |
| my $self = shift; |
| my $cmdname = shift; |
| my $state_name = shift; |
| my @args = @_; |
| |
| if (exists($default_shared_conversion_states{$cmdname}) |
| and exists($default_shared_conversion_states{$cmdname}->{$state_name})) { |
| _XS_set_shared_conversion_state($self, $cmdname, |
| $state_name, @args); |
| return; |
| } |
| |
| _set_shared_conversion_state($self, $cmdname, |
| $state_name, @args); |
| } |
| |
| # class, direction, order, file_string, target |
| my %default_special_unit_info |
| = %{ Texinfo::HTMLData::get_default_special_unit_info() }; |
| |
| # heading |
| my %default_translated_special_unit_info |
| = %{ Texinfo::HTMLData::get_default_translated_special_unit_info() }; |
| |
| my $direction_orders = Texinfo::HTMLData::get_directions_order(); |
| # 'global', 'text', 'relative', 'file' |
| my @all_directions_except_special_units; |
| foreach my $direction_order (@$direction_orders) { |
| push @all_directions_except_special_units, @$direction_order; |
| } |
| my @global_directions_order = @{$direction_orders->[0]}; |
| my @text_directions_order = @{$direction_orders->[1]}; |
| |
| #print STDERR join('|', @all_directions_except_special_units)."\n"; |
| |
| # translation contexts should be consistent with |
| # %direction_type_translation_context. If the direction is not used |
| # as is, it should also be taken into account in direction_string(). |
| # For now 'This' becomes 'This (current section)'. |
| my %default_translated_directions_strings |
| = %{ Texinfo::HTMLData::get_default_translated_directions_strings() }; |
| |
| my @style_commands_contexts = ('normal', 'preformatted'); |
| |
| our @no_args_commands_contexts |
| = ('normal', 'preformatted', 'string', 'css_string'); |
| |
| |
| # a small package providing an API for the argument of |
| # format_translate_message. The idea is to be able to change the |
| # argument of format_translate_message, for example if a instead of |
| # a lang_info, a translations_lang is used, without a need to change |
| # user code. |
| |
| package Texinfo::Convert::HTML::Language; |
| |
| sub new($) { |
| my $lang_info = shift; |
| |
| bless $lang_info; |
| return $lang_info; |
| } |
| |
| sub bcp47_tag($) { |
| my $self = shift; |
| |
| return $self->{'bcp47_locale'}; |
| } |
| |
| sub xpg_locale($) { |
| my $self = shift; |
| |
| if (!exists($self->{'xpg_locale'})) { |
| $self->{'xpg_locale'} |
| = Texinfo::Translations::get_lang_info_xpg_locale($self); |
| } |
| return $self->{'xpg_locale'}; |
| } |
| |
| package Texinfo::Convert::HTML; |
| |
| my $unknown_lang_info |
| = Texinfo::Convert::HTML::Language::new({'bcp47_locale' => ''}); |
| |
| # redefined functions |
| # |
| # based on Texinfo::Translations::cache_translate_string, additionally |
| # calls user defined function. |
| sub _html_cache_translate_string($$$;$) { |
| my ($self, $string, $lang_translations, $translation_context) = @_; |
| |
| if (defined($self->{'formatting_function'}->{'format_translate_message'})) { |
| my $lang_info; |
| if (defined($lang_translations)) { |
| $lang_info |
| = Texinfo::Convert::HTML::Language::new($lang_translations->[0]); |
| } else { |
| $lang_info = $unknown_lang_info; |
| } |
| |
| my $translated_string |
| = &{$self->{'formatting_function'}->{'format_translate_message'}}($self, |
| $string, $lang_info, |
| $translation_context); |
| |
| if (defined($translated_string)) { |
| my $translations; |
| my $cached_lang = $lang_info->{'bcp47_locale'}; |
| if (!exists($self->{'translation_cache'}->{$cached_lang})) { |
| $self->{'translation_cache'}->{$cached_lang} = {}; |
| } |
| $translations = $self->{'translation_cache'}->{$cached_lang}; |
| |
| # reuse the tree if the translation matches the cached translation |
| # otherwise setup a new translation (without tree). |
| my $translation_context_str; |
| if (defined($translation_context)) { |
| $translation_context_str = $translation_context; |
| } else { |
| $translation_context_str = ''; |
| } |
| |
| my $strings_cache = $translations->{$translation_context_str}; |
| if (defined($strings_cache)) { |
| my $translated_string_tree = $strings_cache->{$string}; |
| if (defined($translated_string_tree)) { |
| if ($translated_string_tree->[0] eq $translated_string) { |
| return $translated_string_tree; |
| } |
| # if the string has changed, the cache is invalidated by |
| # resetting the cached string array reference just below. |
| } |
| } else { |
| $strings_cache = {}; |
| $translations->{$translation_context_str} = $strings_cache; |
| } |
| |
| my $result = [$translated_string]; |
| |
| $strings_cache->{$string} = $result; |
| |
| return $result; |
| } |
| } |
| |
| return Texinfo::Translations::cache_translate_string($string, |
| $lang_translations, $translation_context); |
| } |
| |
| # redefine generic Converter functions to pass a customized |
| # cache_translate_string function |
| sub cdt($$;$$) { |
| my ($self, $string, $replaced_substrings, $translation_context) = @_; |
| |
| return Texinfo::Translations::gdt($string, |
| $self->{'current_lang_translations'}, |
| $replaced_substrings, |
| $self->get_conf('DEBUG'), |
| $translation_context, $self, |
| \&_html_cache_translate_string); |
| } |
| |
| sub cdt_string($$;$$) { |
| my ($self, $string, $replaced_substrings, $translation_context) = @_; |
| |
| return Texinfo::Translations::gdt_string($string, |
| $self->{'current_lang_translations'}, |
| $replaced_substrings, |
| $translation_context, $self, |
| \&_html_cache_translate_string); |
| } |
| |
| sub element_cdt($$$;$$) { |
| my ($self, $string, $element, $replaced_substrings, |
| $translation_context) = @_; |
| |
| my $element_lang_translations |
| = Texinfo::Translations::new_element_language_translation( |
| $Texinfo::Translations::converters_translation_cache, |
| $element); |
| return Texinfo::Translations::gdt($string, $element_lang_translations, |
| $replaced_substrings, |
| $self->get_conf('DEBUG'), |
| $translation_context, $self, |
| \&_html_cache_translate_string); |
| } |
| |
| |
| |
| # setup conversion information and conversion and formatting functions. |
| |
| our %default_css_element_class_styles |
| = %{ Texinfo::HTMLDataCSS::get_base_default_css_info() }; |
| |
| $default_css_element_class_styles{'pre.format-preformatted'} |
| = $default_css_element_class_styles{'pre.display-preformatted'}; |
| |
| my %default_pre_class_types; |
| $default_pre_class_types{'menu_comment'} = 'menu-comment'; |
| |
| my %indented_preformatted_commands; |
| foreach my $indented_format ('example', 'display', 'lisp') { |
| $indented_preformatted_commands{$indented_format} = 1; |
| $indented_preformatted_commands{"small$indented_format"} = 1; |
| |
| # div.lisp is output as div.example |
| if ($indented_format ne 'lisp') { |
| $default_css_element_class_styles{"div.$indented_format"} |
| = 'margin-left: 3.2em'; |
| } |
| } |
| |
| # types that are in code style in the default case. |
| my %default_code_types = ( |
| ); |
| |
| # specification of arguments formatting |
| # to obtain the same order of conversion as in C, order for one argument |
| # should be: normal, monospace, string, monospacestring, monospacetext, |
| # filenametext, url, raw |
| # Also used to be converted automatically to Texinfo code for documentation. |
| our %html_default_commands_args = ( |
| 'anchor' => [['monospacestring']], |
| 'namedanchor' => [['monospacestring'], ['normal']], |
| 'email' => [['url', 'monospacestring'], ['normal']], |
| 'footnote' => [[]], |
| 'printindex' => [[]], |
| 'uref' => [['url', 'monospacestring'], ['normal'], ['normal']], |
| 'url' => [['url', 'monospacestring'], ['normal'], ['normal']], |
| 'sp' => [[]], |
| 'inforef' => [['monospace'],['normal'],['filenametext']], |
| 'xref' => [['monospace'],['normal'],['normal'],['filenametext'],['normal']], |
| 'pxref' => [['monospace'],['normal'],['normal'],['filenametext'],['normal']], |
| 'ref' => [['monospace'],['normal'],['normal'],['filenametext'],['normal']], |
| 'link' => [['monospace'],['normal'],['filenametext']], |
| 'image' => [['monospacestring', 'filenametext', 'url'],['filenametext'],['filenametext'],['normal','string'],['filenametext']], |
| # 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' => [[]], |
| 'value' => [['monospacestring']], |
| ); |
| |
| foreach my $explained_command (keys(%explained_commands)) { |
| $html_default_commands_args{$explained_command} |
| = [['normal'], ['normal', 'string']]; |
| } |
| |
| my %accent_commands; |
| |
| foreach my $accent_command (keys(%brace_commands)) { |
| if ($brace_commands{$accent_command} eq 'accent') { |
| $accent_commands{$accent_command} = 1; |
| $html_default_commands_args{$accent_command} = [[]]; |
| } |
| } |
| |
| my %kept_line_commands; |
| |
| my @informative_global_commands = ('documentlanguage', |
| 'documentscript', 'footnotestyle', |
| 'xrefautomaticsectiontitle', 'deftypefnnewline'); |
| |
| my @contents_commands = ('contents', 'shortcontents', 'summarycontents'); |
| |
| foreach my $line_command (@informative_global_commands, |
| 'documentlanguagevariant', |
| @contents_commands, keys(%formattable_line_commands), |
| keys(%formatted_line_commands), |
| keys(%default_index_commands)) { |
| $kept_line_commands{$line_command} = 1; |
| } |
| |
| foreach my $line_command (keys(%line_commands)) { |
| $default_commands_conversion{$line_command} = undef |
| unless (exists($kept_line_commands{$line_command})); |
| } |
| |
| foreach my $nobrace_command (keys(%nobrace_commands)) { |
| $default_commands_conversion{$nobrace_command} = undef |
| unless (exists($formatted_nobrace_commands{$nobrace_command})); |
| } |
| |
| # formatted/formattable @-commands that are not converted in |
| # HTML in the default case. |
| $default_commands_conversion{'page'} = undef; |
| $default_commands_conversion{'need'} = undef; |
| $default_commands_conversion{'vskip'} = undef; |
| |
| foreach my $ignored_brace_commands ('caption', 'errormsg', 'hyphenation', |
| 'shortcaption', 'seealso', 'seeentry', 'sortas') { |
| $default_commands_conversion{$ignored_brace_commands} = undef; |
| } |
| |
| foreach my $ignored_block_commands ('ignore', 'macro', 'rmacro', 'linemacro', |
| 'copying', 'documentdescription', 'documentinfo', 'titlepage', |
| 'publication', 'direntry', 'nodedescriptionblock') { |
| $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. |
| our %default_no_arg_commands_formatting = ( |
| 'normal' => {}, |
| 'preformatted' => {}, |
| 'string' => {}, |
| # more internal |
| 'css_string' => {}, |
| ); |
| |
| foreach my $cmdname (keys(%Texinfo::Convert::Converter::xml_text_entity_no_arg_commands_formatting)) { |
| $default_no_arg_commands_formatting{'normal'}->{$cmdname} = |
| {'text' => |
| $Texinfo::Convert::Converter::xml_text_entity_no_arg_commands_formatting{ |
| $cmdname}}; |
| } |
| |
| $default_no_arg_commands_formatting{'normal'}->{' '} = {'text' => ' '}; |
| $default_no_arg_commands_formatting{'normal'}->{"\t"} = {'text' => ' '}; |
| $default_no_arg_commands_formatting{'normal'}->{"\n"} = {'text' => ' '}; |
| |
| # possible example of use, right now not used, as |
| # the generic Converter customization is directly used through |
| # the call to translated_command_tree(). |
| #$default_no_arg_commands_formatting{'normal'}->{'error'}->{'translated_converted'} = 'error-->'; |
| ## This is used to have gettext pick up the chain to be translated |
| #if (0) { |
| # my $not_existing; |
| # $not_existing->cdt('error-->'); |
| #} |
| |
| $default_no_arg_commands_formatting{'normal'}->{'enddots'} |
| = {'element' => 'small', 'text' => '...'}; |
| $default_no_arg_commands_formatting{'preformatted'}->{'dots'} |
| = {'text' => '...'}; |
| $default_no_arg_commands_formatting{'preformatted'}->{'enddots'} |
| = {'text' => '...'}; |
| $default_no_arg_commands_formatting{'normal'}->{'*'} = {'text' => '<br>'}; |
| # this is used in math too, not sure that it is the best |
| # in that context, '<br>' could be better. |
| $default_no_arg_commands_formatting{'preformatted'}->{'*'} = {'text' => "\n"}; |
| |
| # escaped code points in CSS |
| # https://www.w3.org/TR/css-syntax/#consume-escaped-code-point |
| # Consume as many hex digits as possible, but no more than 5. Note that this means 1-6 hex digits have been consumed in total. If the next input code point is whitespace, consume it as well. Interpret the hex digits as a hexadecimal number. |
| # Note that in style= HTML attributes entities are used to |
| # protect CSS strings. For example, the CSS string a'b" |
| # is protected as CSS as a\'b", and " is escaped in an HTML style |
| # attribute: style="list-style-type: 'a\'b"'" |
| |
| # for the commands without a good representation in the other maps |
| my %css_no_arg_commands = ( |
| # we want to set explicitly |
| '*' => '\A ', |
| # do not set to force using only translations (as the command |
| # is in the default converter translated commands) |
| 'error' => undef, |
| ); |
| |
| foreach my $cmdname (keys(%{$default_no_arg_commands_formatting{'normal'}})) { |
| if (exists($css_no_arg_commands{$cmdname})) { |
| $default_no_arg_commands_formatting{'css_string'}->{$cmdname} |
| = {'text' => $css_no_arg_commands{$cmdname}} |
| if (defined($css_no_arg_commands{$cmdname})); |
| } elsif (exists($Texinfo::Convert::Unicode::unicode_map{$cmdname})) { |
| my $char_nr = hex($Texinfo::Convert::Unicode::unicode_map{$cmdname}); |
| my $css_string; |
| if ($char_nr < 128) { # 7bit ascii |
| $css_string = chr($char_nr); |
| } else { |
| $css_string = "\\$Texinfo::Convert::Unicode::unicode_map{$cmdname} "; |
| } |
| $default_no_arg_commands_formatting{'css_string'}->{$cmdname} |
| = {'text' => $css_string}; |
| } elsif (exists($nobrace_symbol_text{$cmdname})) { |
| $default_no_arg_commands_formatting{'css_string'}->{$cmdname} |
| = {'text' => $nobrace_symbol_text{$cmdname}}; |
| } elsif (exists($Texinfo::CommandsValues::text_brace_no_arg_commands{$cmdname})) { |
| # complete the commands not in unicode maps: TeX, enddots, LaTeX, tie |
| $default_no_arg_commands_formatting{'css_string'}->{$cmdname} |
| = {'text' => $Texinfo::CommandsValues::text_brace_no_arg_commands{$cmdname}}; |
| } else { |
| warn "BUG: $cmdname: no css_string\n"; |
| } |
| } |
| |
| |
| |
| # w not in css_string, set the corresponding default_css_element_class_styles |
| # especially, which also has none and not w in the class |
| $default_css_element_class_styles{'ul.mark-none'} = 'list-style-type: none'; |
| |
| # setup default_css_element_class_styles for mark commands based on css strings |
| foreach my $mark_command (keys(%{$default_no_arg_commands_formatting{'css_string'}})) { |
| if (exists($brace_commands{$mark_command})) { |
| my $css_string; |
| if ($mark_command eq 'bullet') { |
| $css_string = 'disc'; |
| } elsif (exists($default_no_arg_commands_formatting{'css_string'} |
| ->{$mark_command}) |
| and $default_no_arg_commands_formatting{'css_string'} |
| ->{$mark_command}->{'text'}) { |
| if (exists($special_list_mark_css_string_no_arg_command{$mark_command})) { |
| $css_string = $special_list_mark_css_string_no_arg_command{$mark_command}; |
| } else { |
| $css_string |
| = $default_no_arg_commands_formatting{'css_string'} |
| ->{$mark_command}->{'text'}; |
| } |
| $css_string =~ s/^(\\[A-Z0-9]+) $/$1/; |
| $css_string = '"'.$css_string.'"'; |
| } |
| if (defined($css_string)) { |
| $default_css_element_class_styles{"ul.mark-$mark_command"} |
| = "list-style-type: $css_string"; |
| } |
| } |
| } |
| |
| # used to show the built-in CSS rules. Called form main prorgam. |
| sub builtin_default_css_text() { |
| my $css_text = ''; |
| foreach my $css_rule (sort(keys(%default_css_element_class_styles))) { |
| if ($default_css_element_class_styles{$css_rule} ne '') { |
| $css_text .= "$css_rule {$default_css_element_class_styles{$css_rule}}\n"; |
| } |
| } |
| return $css_text; |
| } |
| |
| sub _text_element_conversion($$$) { |
| my ($self, $specification, $cmdname) = @_; |
| |
| my $text = ''; |
| # note that there could be HTML elements in text |
| if (exists($specification->{'text'})) { |
| $text = $specification->{'text'}; |
| } |
| |
| if (exists($specification->{'element'})) { |
| return $self->html_attribute_class($specification->{'element'}, [$cmdname]) |
| .'>'. $text . '</'.$specification->{'element'}.'>'; |
| } else { |
| return $text; |
| } |
| } |
| |
| sub _convert_no_arg_command($$$) { |
| my ($self, $cmdname, $command) = @_; |
| |
| if (in_upper_case($self) and exists($letter_no_arg_commands{$cmdname}) |
| and exists($letter_no_arg_commands{uc($cmdname)})) { |
| $cmdname = uc($cmdname); |
| } |
| |
| my $result; |
| |
| if (in_preformatted_context($self) or in_math($self)) { |
| $result = _text_element_conversion($self, |
| $self->{'no_arg_commands_formatting'}->{$cmdname}->{'preformatted'}, |
| $cmdname); |
| } elsif (in_string($self)) { |
| $result = _text_element_conversion($self, |
| $self->{'no_arg_commands_formatting'}->{$cmdname}->{'string'}, |
| $cmdname); |
| } else { |
| $result = _text_element_conversion($self, |
| $self->{'no_arg_commands_formatting'}->{$cmdname}->{'normal'}, |
| $cmdname); |
| } |
| |
| return $result; |
| } |
| |
| foreach my $cmdname(keys(%{$default_no_arg_commands_formatting{'normal'}})) { |
| $default_commands_conversion{$cmdname} = \&_convert_no_arg_command; |
| } |
| |
| sub _css_string_convert_no_arg_command($$$) { |
| my ($self, $cmdname, $command) = @_; |
| |
| if (in_upper_case($self) and exists($letter_no_arg_commands{$cmdname}) |
| and exists($self->{'no_arg_commands_formatting'}->{uc($cmdname)})) { |
| $cmdname = uc($cmdname); |
| } |
| #if (not defined($self->{'no_arg_commands_formatting'}->{$cmdname}->{'css_string'}->{$cmdname})) { |
| # cluck ("BUG: CSS $cmdname no text"); |
| #} |
| return $self->{'no_arg_commands_formatting'}->{$cmdname}->{'css_string'} |
| ->{'text'}; |
| } |
| |
| foreach my $cmdname(keys(%{$default_no_arg_commands_formatting{'normal'}})) { |
| $default_css_string_commands_conversion{$cmdname} |
| = \&_css_string_convert_no_arg_command; |
| } |
| |
| sub _convert_today_command($$$) { |
| my ($self, $cmdname, $command) = @_; |
| |
| my $tree = $self->expand_today(); |
| return $self->convert_tree($tree, 'convert today'); |
| } |
| |
| $default_commands_conversion{'today'} = \&_convert_today_command; |
| |
| # style commands |
| |
| my %quoted_style_commands; |
| foreach my $quoted_command ('samp') { |
| $quoted_style_commands{$quoted_command} = 1; |
| } |
| |
| my %default_upper_case_commands = ( 'sc' => 1 ); |
| |
| my %style_commands_element |
| = %{ Texinfo::HTMLData::get_html_style_commands_element() }; |
| |
| my %default_style_commands_formatting; |
| |
| my %style_brace_types = map {$_ => 1} ('style_other', 'style_code', |
| 'style_no_code'); |
| # @all_style_commands is the union of style brace commands and commands |
| # in %style_commands_element, a few not being style brace commands, and |
| # commands in %quoted_style_commands. |
| # Using keys of a map generated hash does like uniq, it avoids duplicates. |
| # The first grep selects style brace commands, ie commands with %brace_commands |
| # type in %style_brace_types. |
| my @all_style_commands = keys %{{ map { $_ => 1 } |
| ((grep {$style_brace_types{$brace_commands{$_}}} keys(%brace_commands)), |
| keys(%style_commands_element), keys(%quoted_style_commands)) }}; |
| |
| # NOTE only normal and preformatted contexts are used. css strings |
| # are formatted in string context, and in string context the argument |
| # is returned as is. |
| foreach my $cmdname (@all_style_commands) { |
| # indicateurl is formatted with a specific function |
| next if ($cmdname eq 'indicateurl'); |
| $default_style_commands_formatting{$cmdname} = {}; |
| # default is no element. |
| foreach my $context (@style_commands_contexts) { |
| $default_style_commands_formatting{$cmdname}->{$context} = {} |
| } |
| if (exists($style_commands_element{$cmdname})) { |
| my $html_element = $style_commands_element{$cmdname}; |
| foreach my $context (@style_commands_contexts) { |
| $default_style_commands_formatting{$cmdname}->{$context} |
| = {'element' => $html_element}; |
| } |
| } |
| if (exists($quoted_style_commands{$cmdname})) { |
| foreach my $context (@style_commands_contexts) { |
| $default_style_commands_formatting{$cmdname}->{$context}->{'quote'} = 1; |
| } |
| } |
| $default_commands_conversion{$cmdname} = \&_convert_style_command; |
| } |
| |
| $default_style_commands_formatting{'sc'}->{'preformatted'}->{'element'} = 'span'; |
| |
| # currently unused, could be re-used if there is a need to have attributes |
| # specified in %style_commands_element |
| sub _parse_attribute($) { |
| my $element = shift; |
| |
| return ('', '', '') if (!defined($element)); |
| |
| my ($class, $attributes) = ('', ''); |
| |
| if ($element =~ /^(\w+)(\s+.*)/) |
| { |
| $element = $1; |
| $attributes = $2; |
| if ($attributes =~ s/^\s+class=\"([^\"]+)\"//) { |
| $class = $1; |
| } |
| } |
| return ($element, $class, $attributes); |
| } |
| |
| sub _convert_style_command($$$$) { |
| my ($self, $cmdname, $command, $args) = @_; |
| |
| my $text; |
| if (defined($args) and defined($args->[0])) { |
| $text = $args->[0]->{'normal'}; |
| } else { |
| # happens with bogus @-commands without argument, like @strong something |
| return ''; |
| } |
| |
| if (in_string($self)) { |
| return $text; |
| } |
| |
| my $style_cmdname; |
| # effect of kbdinputstyle |
| if ($cmdname eq 'kbd' and exists($command->{'extra'}) |
| and $command->{'extra'}->{'code'}) { |
| $style_cmdname = 'code'; |
| } else { |
| $style_cmdname = $cmdname; |
| } |
| |
| if (exists($self->{'style_commands_formatting'}->{$style_cmdname})) { |
| my $style_formatting |
| = $self->{'style_commands_formatting'}->{$style_cmdname}; |
| my $formatting_spec; |
| if (in_preformatted_context($self)) { |
| $formatting_spec = $style_formatting->{'preformatted'}; |
| } else { |
| $formatting_spec = $style_formatting->{'normal'}; |
| } |
| if (defined($formatting_spec)) { |
| if (exists($formatting_spec->{'element'})) { |
| my @classes = ($style_cmdname); |
| if ($style_cmdname ne $cmdname) { |
| push @classes, "as-${style_cmdname}-${cmdname}"; |
| } |
| my $style = $formatting_spec->{'element'}; |
| my $open = $self->html_attribute_class($style, \@classes); |
| if ($open ne '') { |
| $text = $open . '>' . $text . "</$style>"; |
| } |
| } |
| if (exists($formatting_spec->{'quote'})) { |
| $text = $self->get_conf('OPEN_QUOTE_SYMBOL') . $text |
| . $self->get_conf('CLOSE_QUOTE_SYMBOL'); |
| } |
| } |
| } |
| return $text; |
| } |
| |
| sub _convert_w_command($$$$) { |
| my ($self, $cmdname, $command, $args) = @_; |
| |
| my $text; |
| if (defined($args) and defined($args->[0])) { |
| $text = $args->[0]->{'normal'}; |
| } else { |
| $text = ''; |
| } |
| if (in_string($self)) { |
| return $text; |
| } else { |
| return $text . '<!-- /@w -->'; |
| } |
| } |
| $default_commands_conversion{'w'} = \&_convert_w_command; |
| |
| sub _convert_value_command($$$$) { |
| my ($self, $cmdname, $command, $args) = @_; |
| |
| return $self->convert_tree($self->cdt('@{No value for `{value}\'@}', |
| {'value' => Texinfo::TreeElement::new( |
| {'text' => $args->[0]->{'monospacestring'}}) }), |
| 'Tr missing value'); |
| } |
| |
| $default_commands_conversion{'value'} = \&_convert_value_command; |
| |
| sub _convert_email_command($$$$) { |
| my ($self, $cmdname, $command, $args) = @_; |
| |
| my $args_nr = 0; |
| if (defined($args)) { |
| $args_nr = scalar(@$args); |
| } |
| |
| my $mail = ''; |
| my $mail_string = ''; |
| if ($args_nr > 0 and defined($args->[0])) { |
| my $mail_arg = $args->[0]; |
| $mail = $mail_arg->{'url'}; |
| $mail_string = $mail_arg->{'monospacestring'}; |
| } |
| |
| my $text = ''; |
| if ($args_nr > 1 and defined($args->[1]) |
| and defined($args->[1]->{'normal'})) { |
| my $text_arg = $args->[1]; |
| $text = $text_arg->{'normal'}; |
| } |
| $text = $mail_string unless ($text ne ''); |
| # match a non-space character. Both ascii and non-ascii spaces are |
| # considered as spaces. When perl 5.18 is the oldest version |
| # supported, it could become [^\s] |
| return $text unless ($mail =~ /[^\v\h\s]/); |
| if (in_string($self)) { |
| return "$mail_string ($text)"; |
| } else { |
| return $self->html_attribute_class('a', [$cmdname]) |
| .' href="'.$self->url_protect_url_text("mailto:$mail")."\">$text</a>"; |
| } |
| } |
| |
| $default_commands_conversion{'email'} = \&_convert_email_command; |
| |
| sub _convert_explained_command($$$$) { |
| my ($self, $cmdname, $command, $args) = @_; |
| |
| my $explanation_result; |
| my $explanation_string; |
| my $normalized_type = ''; |
| |
| if (exists($command->{'contents'}) |
| and exists($command->{'contents'}->[0]->{'contents'})) { |
| $normalized_type |
| = Texinfo::Convert::NodeNameNormalization::convert_to_identifier( |
| $command->{'contents'}->[0]); |
| } |
| |
| if (defined($args) and defined($args->[1]) |
| and defined($args->[1]->{'string'}) |
| and $args->[1]->{'string'} =~ /\S/) { |
| $explanation_string = $args->[1]->{'string'}; |
| $self->set_shared_conversion_state($cmdname, 'explained_commands', |
| $normalized_type, $explanation_string); |
| } else { |
| $explanation_string |
| = $self->get_shared_conversion_state($cmdname, 'explained_commands', |
| $normalized_type); |
| } |
| |
| my $result = ''; |
| if (defined($args) and defined($args->[0])) { |
| $result = $args->[0]->{'normal'}; |
| } |
| if (!in_string($self)) { |
| my $explanation = ''; |
| $explanation = " title=\"$explanation_string\"" |
| if (defined($explanation_string)); |
| my $html_element = 'abbr'; |
| $result = $self->html_attribute_class($html_element, [$cmdname]) |
| ."${explanation}>".$result."</$html_element>"; |
| } |
| if (defined($args) and defined($args->[1]) |
| and defined($args->[1]->{'normal'})) { |
| my $explanation_result = $args->[1]->{'normal'}; |
| # TRANSLATORS: abbreviation or acronym explanation |
| $result = $self->convert_tree($self->cdt('{explained_string} ({explanation})', |
| {'explained_string' => |
| Texinfo::TreeElement::new({'type' => '_converted', |
| 'text' => $result}), |
| 'explanation' => |
| Texinfo::TreeElement::new({'type' => '_converted', |
| 'text' => $explanation_result})}), |
| "convert explained $cmdname"); |
| } |
| |
| return $result; |
| } |
| |
| foreach my $explained_command (keys(%explained_commands)) { |
| $default_commands_conversion{$explained_command} |
| = \&_convert_explained_command; |
| } |
| |
| sub _convert_anchor_command($$$$) { |
| my ($self, $cmdname, $command, $args) = @_; |
| |
| # in_multiple_conversions returning true while multi_expanded_region returns |
| # undef happens when expanding target and heading commands text. Therefore |
| # if in_multiple_conversions was used, the output could be different. |
| # Note, however, that an anchor on a sectioning command line is never output |
| # because the sectioning command line is copied to be combined with |
| # the sectioning number through a translation and the copy is not registered |
| # as a target element and command_id will return undef. |
| # An anchor on a @node line can be output (it is invalid Texinfo) and it |
| # would be different for that case if in_multiple_conversions was used. |
| if (!multi_expanded_region($self) and !in_string($self)) { |
| my $id = $self->command_id($command); |
| if (defined($id) and $id ne '') { |
| return &{$self->formatting_function('format_separate_anchor')}($self, |
| $id, $cmdname); |
| } |
| } |
| return ''; |
| } |
| |
| $default_commands_conversion{'anchor'} = \&_convert_anchor_command; |
| $default_commands_conversion{'namedanchor'} = \&_convert_anchor_command; |
| |
| sub _convert_footnote_command($$$$) { |
| my ($self, $cmdname, $command, $args) = @_; |
| |
| my $foot_num |
| = $self->get_shared_conversion_state('footnote', 'footnote_number'); |
| if (!defined($foot_num)) { |
| $foot_num = 0; |
| } |
| |
| $foot_num++; |
| $self->set_shared_conversion_state('footnote', 'footnote_number', |
| $foot_num); |
| my $number_in_doc = $foot_num; |
| my $footnote_mark; |
| if ($self->get_conf('NUMBER_FOOTNOTES')) { |
| $footnote_mark = $number_in_doc; |
| } else { |
| $footnote_mark = $self->get_conf('NO_NUMBER_FOOTNOTE_SYMBOL'); |
| $footnote_mark = '' if (!defined($footnote_mark)); |
| } |
| |
| return "($footnote_mark)" if (in_string($self)); |
| |
| #print STDERR "FOOTNOTE $command\n"; |
| my $footnote_id = $self->command_id($command); |
| |
| # happens for bogus footnotes |
| if (!defined($footnote_id)) { |
| return ''; |
| } |
| # ID for linking back to the main text from the footnote. |
| my $footnote_docid = $self->footnote_location_target($command); |
| |
| # id used in output |
| my $footid; |
| my $docid; |
| |
| my $multiple_expanded_footnote = 0; |
| my $multi_expanded_region = multi_expanded_region($self); |
| if (defined($multi_expanded_region)) { |
| # to avoid duplicate names, use a prefix that cannot happen in anchors |
| my $target_prefix = "t_f"; |
| $footid = $target_prefix.$multi_expanded_region.'_' |
| .$footnote_id.'_'.$foot_num; |
| $docid = $target_prefix.$multi_expanded_region.'_' |
| .$footnote_docid.'_'.$foot_num; |
| } else { |
| my $footnote_id_number |
| = $self->get_shared_conversion_state('footnote', 'footnote_id_numbers', |
| $footnote_id); |
| if (!defined($footnote_id_number)) { |
| $self->set_shared_conversion_state('footnote', 'footnote_id_numbers', |
| $footnote_id, $foot_num); |
| $footid = $footnote_id; |
| $docid = $footnote_docid; |
| } else { |
| # This should rarely happen, except for @footnote in @copying and |
| # multiple @insertcopying... |
| # Here it is not checked that there is no clash with another anchor. |
| # However, unless there are more than 1000 footnotes this should not |
| # happen at all, and even in that case it is very unlikely. |
| $footid = $footnote_id.'_'.$foot_num; |
| $docid = $footnote_docid.'_'.$foot_num; |
| $multiple_expanded_footnote = 1; |
| } |
| } |
| my $footnote_href; |
| my $footnotestyle = $self->get_conf('footnotestyle'); |
| if ((!defined($footnotestyle) or $footnotestyle ne 'separate') |
| and (defined($multi_expanded_region) |
| or $multiple_expanded_footnote)) { |
| # if the footnote appears multiple times, command_href() will select |
| # one, but it may not be the one expanded at the location currently |
| # formatted (in general the first one, but it depends if it is in a |
| # tree element or not, for instance in @titlepage). |
| # With footnotestyle end, considering that the footnote is in the same file |
| # has a better chance of being correct. |
| $footnote_href = "#$footid"; |
| } else { |
| $footnote_href = $self->command_href($command, undef, undef, $footid); |
| } |
| |
| $self->register_footnote($command, $footid, $docid, $number_in_doc, |
| $self->current_filename(), $multi_expanded_region); |
| |
| my $footnote_number_text; |
| if (in_preformatted_context($self)) { |
| $footnote_number_text = "($footnote_mark)"; |
| } else { |
| $footnote_number_text = "<sup>$footnote_mark</sup>"; |
| } |
| return $self->html_attribute_class('a', [$cmdname]) |
| ." id=\"$docid\" href=\"$footnote_href\">$footnote_number_text</a>"; |
| } |
| $default_commands_conversion{'footnote'} = \&_convert_footnote_command; |
| |
| sub _convert_uref_command($$$$) { |
| my ($self, $cmdname, $command, $args) = @_; |
| |
| my $args_nr = 0; |
| |
| if (defined($args)) { |
| $args_nr = scalar(@$args); |
| } |
| |
| my $text = ''; |
| my $url = ''; |
| my $url_string = ''; |
| my $replacement = ''; |
| if ($args_nr > 0 and defined($args->[0])) { |
| my $url_arg = $args->[0]; |
| $url = $url_arg->{'url'}; |
| $url_string = $url_arg->{'monospacestring'}; |
| } |
| if ($args_nr > 1 and defined($args->[1])) { |
| my $text_arg = $args->[1]; |
| $text = $text_arg->{'normal'}; |
| } |
| if ($args_nr > 2 and defined($args->[2])) { |
| my $replacement_arg = $args->[2]; |
| $replacement = $replacement_arg->{'normal'}; |
| } |
| |
| $text = $replacement if ($replacement ne ''); |
| $text = $url_string if ($text eq ''); |
| return $text if ($url eq ''); |
| return "$text ($url_string)" if (in_string($self)); |
| |
| return $self->html_attribute_class('a', [$cmdname]) |
| .' href="'.$self->url_protect_url_text($url)."\">$text</a>"; |
| } |
| |
| $default_commands_conversion{'uref'} = \&_convert_uref_command; |
| $default_commands_conversion{'url'} = \&_convert_uref_command; |
| |
| sub _convert_image_command($$$$) { |
| my ($self, $cmdname, $command, $args) = @_; |
| |
| # NOTE the choice of filenametext or url is somewhat arbitrary here. |
| # url is formatted considering that it would be output as UTF-8 to fit |
| # with percent encoding, filenametext is formatted according to the the |
| # output encoding. It matter mostly for accent @-commands, @U and symbols |
| # no args @-commands not in the ASCII range. |
| # As a file name, filenametext could make sense, although a path |
| # with all the characters encoded, which happens if UTF-8 is considered |
| # as the output encoding may also make sense. Note that it is |
| # also used as the path part of a percent encoded url. |
| # In practice, the user should check that the output encoding |
| # and the commands used in file names match, so url or |
| # filenametext should lead to the same path. |
| if (defined($args) and defined($args->[0]) |
| and defined($args->[0]->{'filenametext'}) |
| and $args->[0]->{'filenametext'} ne '') { |
| my $image_basefile = $args->[0]->{'filenametext'}; |
| my $basefile_string = ''; |
| $basefile_string = $args->[0]->{'monospacestring'} |
| if (defined($args->[0]->{'monospacestring'})); |
| return $basefile_string if (in_string($self)); |
| my ($image_file, $image_extension, $image_path) |
| = $self->html_image_file_location_name($cmdname, $command, |
| $image_basefile, $args); |
| if (not defined($image_path)) { |
| # it would have been relevant to output the message only if |
| # if not ($self->in_multiple_conversions()) |
| # However, @image formatted multiple times should be |
| # rare out of test suites (and probably always incorrect), so we avoid |
| # complexity and slowdown. We still check that source_info is set, if |
| # not it should be a copy, therefore there is no need for error |
| # output, especially without line information. |
| if (exists($command->{'source_info'})) { |
| $self->converter_line_warn(sprintf( |
| __("\@image file `%s' (for HTML) not found, using `%s'"), |
| $image_basefile, $image_file), $command->{'source_info'}); |
| } |
| } |
| if (defined($self->get_conf('IMAGE_LINK_PREFIX'))) { |
| $image_file = $self->get_conf('IMAGE_LINK_PREFIX') . $image_file; |
| } |
| my $alt_string; |
| if (defined($args->[3]) and defined($args->[3]->{'string'}) |
| and $args->[3]->{'string'} ne '') { |
| $alt_string = $args->[3]->{'string'}; |
| } else { |
| $alt_string = $basefile_string; |
| } |
| return $self->close_html_lone_element( |
| $self->html_attribute_class('img', [$cmdname]) |
| . ' src="'.$self->url_protect_file_text($image_file) |
| ."\" alt=\"$alt_string\""); |
| } |
| return ''; |
| } |
| |
| $default_commands_conversion{'image'} = \&_convert_image_command; |
| |
| sub _convert_math_command($$$$) { |
| my ($self, $cmdname, $command, $args) = @_; |
| |
| my $arg; |
| if (defined($args) and defined($args->[0])) { |
| $arg = $args->[0]->{'normal'}; |
| } else { |
| return ''; |
| } |
| |
| my $math_type = $self->get_conf('HTML_MATH'); |
| if (defined($math_type) and $math_type eq 'mathjax') { |
| $self->register_file_information('mathjax', 1); |
| return $self->html_attribute_class('em', [$cmdname, 'tex2jax_process']) |
| .">\\($arg\\)</em>"; |
| } |
| return $self->html_attribute_class('em', [$cmdname]).">$arg</em>"; |
| } |
| |
| $default_commands_conversion{'math'} = \&_convert_math_command; |
| |
| sub _accent_entities_html_accent($$$;$$$$) { |
| my ($self, $text, $command, $index_in_stack, $accents_stack, |
| $in_upper_case, $use_numeric_entities) = @_; |
| |
| my $accent = $command->{'cmdname'}; |
| |
| if ($in_upper_case and $text =~ /^\w$/) { |
| $text = uc($text); |
| } |
| |
| # do not return a dotless i or j as such if it is further composed |
| # with an accented letter, return the letter as is |
| if ($accent eq 'dotless') { |
| if (exists($Texinfo::UnicodeData::unicode_accented_letters{$accent}) |
| and exists($Texinfo::UnicodeData::unicode_accented_letters{ |
| $accent}->{$text}) |
| and ($index_in_stack > 0 |
| and $Texinfo::UnicodeData::unicode_accented_letters{ |
| $accents_stack->[$index_in_stack-1]->{'cmdname'} })) { |
| return $text; |
| } |
| } |
| |
| if ($use_numeric_entities) { |
| my $formatted_accent |
| = Texinfo::Convert::Converter::xml_numeric_entity_accent($accent, $text); |
| if (defined($formatted_accent)) { |
| return $formatted_accent; |
| } |
| } else { |
| my ($accent_command_entity, $accent_command_text_with_entities); |
| if ($self->{'accent_entities'}->{$accent}) { |
| ($accent_command_entity, $accent_command_text_with_entities) |
| = @{$self->{'accent_entities'}->{$accent}}; |
| } |
| return "&${text}$accent_command_entity;" |
| if ($accent_command_entity |
| and defined($accent_command_text_with_entities) |
| # \z ensures that a \n at the end prevents matching, we do not |
| # want an end of line in the middle of the entity |
| and ($text =~ /^[$accent_command_text_with_entities]\z/)); |
| my $formatted_accent |
| = Texinfo::Convert::Converter::xml_numeric_entity_accent($accent, $text); |
| if (defined($formatted_accent)) { |
| return $formatted_accent; |
| } |
| } |
| |
| # should only be the case of @dotless, as other commands have a diacritic |
| # associated, and only if the argument is not i nor j. |
| return $text; |
| } |
| |
| sub _accent_entities_numeric_entities_accent($$$;$$$) { |
| my ($self, $text, $command, $index_in_stack, $accents_stack, |
| $in_upper_case) = @_; |
| |
| return _accent_entities_html_accent($self, $text, $command, $index_in_stack, |
| $accents_stack, $in_upper_case, 1); |
| } |
| |
| sub _convert_accent_command($$$$) { |
| my ($self, $cmdname, $command, $args) = @_; |
| |
| my $format_accents; |
| if ($self->get_conf('USE_NUMERIC_ENTITY')) { |
| $format_accents = \&_accent_entities_numeric_entities_accent; |
| } else { |
| $format_accents = \&_accent_entities_html_accent; |
| } |
| return $self->convert_accents($command, $format_accents, |
| $self->get_conf('OUTPUT_CHARACTERS'), |
| in_upper_case($self)); |
| } |
| |
| foreach my $cmdname (keys(%accent_commands)) { |
| $default_commands_conversion{$cmdname} = \&_convert_accent_command; |
| } |
| |
| sub _css_string_accent($$$;$$$) { |
| my ($self, $text, $command, $index_in_stack, $accents_stack, |
| $in_upper_case) = @_; |
| |
| my $accent = $command->{'cmdname'}; |
| |
| if ($in_upper_case and $text =~ /^\p{Word}$/) { |
| $text = uc($text); |
| } |
| if (exists($Texinfo::UnicodeData::unicode_accented_letters{$accent}) |
| and exists($Texinfo::UnicodeData::unicode_accented_letters{ |
| $accent}->{$text})) { |
| return '\\' . |
| $Texinfo::UnicodeData::unicode_accented_letters{$accent}->{$text}. ' '; |
| } |
| if (exists($Texinfo::CommandsValues::unicode_diacritics{$accent})) { |
| my $diacritic = '\\' |
| .$Texinfo::CommandsValues::unicode_diacritics{$accent}. ' '; |
| if ($accent ne 'tieaccent') { |
| return $text . $diacritic; |
| } else { |
| # tieaccent diacritic is naturally and correctly composed |
| # between two characters |
| my $remaining_text = $text; |
| # we consider that letters are either characters or escaped characters |
| if ($remaining_text =~ s/^([\p{L}\d]|\\[a-zA-Z0-9]+ )([\p{L}\d]|\\[a-zA-Z0-9]+ )(.*)$/$3/) { |
| return $1.$diacritic.$2 . $remaining_text; |
| } else { |
| return $text . $diacritic; |
| } |
| } |
| } |
| |
| # There are diacritics for every accent command except for dotless. |
| # We should only get there with dotless if the argument is not recognized. |
| return $text; |
| } |
| |
| sub _css_string_convert_accent_command($$$$) { |
| my ($self, $cmdname, $command, $args) = @_; |
| |
| my $format_accents = \&_css_string_accent; |
| return $self->convert_accents($command, $format_accents, |
| $self->get_conf('OUTPUT_CHARACTERS'), |
| in_upper_case($self)); |
| } |
| |
| foreach my $cmdname (keys(%accent_commands)) { |
| $default_css_string_commands_conversion{$cmdname} |
| = \&_css_string_convert_accent_command; |
| } |
| |
| # argument is formatted as code since indicateurl is in brace_code_commands |
| sub _convert_indicateurl_command($$$$) { |
| my ($self, $cmdname, $command, $args) = @_; |
| |
| my $text; |
| if (defined($args) and defined($args->[0])) { |
| $text = $args->[0]->{'normal'}; |
| } else { |
| return ''; |
| } |
| |
| if (!defined($text)) { |
| # happens with bogus @-commands without argument, like @strong something |
| return ''; |
| } |
| if (!in_string($self)) { |
| return $self->get_conf('OPEN_QUOTE_SYMBOL'). |
| $self->html_attribute_class('code', [$cmdname]).'>'.$text |
| .'</code>'.$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, $cmdname, $command, $args) = @_; |
| |
| my $text; |
| if (defined($args) and defined($args->[0])) { |
| $text = $args->[0]->{'normal'}; |
| } else { |
| # happens with empty command |
| return ''; |
| } |
| |
| return &{$self->formatting_function('format_heading_text')}($self, $cmdname, |
| [$cmdname], $text, 0); |
| } |
| |
| $default_commands_conversion{'titlefont'} = \&_convert_titlefont_command; |
| |
| sub _convert_U_command($$$$) { |
| my ($self, $cmdname, $command, $args) = @_; |
| |
| if (defined($args) and defined($args->[0])) { |
| my $arg_text = $args->[0]->{'normal'}; |
| if ($arg_text ne '') { |
| # checks on the value already done in Parser, just output it here. |
| return "&#x$arg_text;"; |
| } |
| } |
| return ''; |
| } |
| |
| $default_commands_conversion{'U'} = \&_convert_U_command; |
| |
| sub _default_format_comment($$) { |
| my ($self, $text) = @_; |
| |
| return $self->xml_comment(' '.$text); |
| } |
| |
| # Note: has an XS override |
| sub _default_format_protect_text { |
| my ($self, $text) = @_; |
| |
| my $result = $self->xml_protect_text($text); |
| $result =~ s/\f//g; |
| return $result; |
| } |
| |
| sub _default_css_string_format_protect_text($$) { |
| my ($self, $text) = @_; |
| |
| $text =~ s/\\/\\\\/g; |
| $text =~ s/\'/\\'/g; |
| return $text; |
| } |
| |
| # can be called on root commands, tree units, special elements |
| # and title elements. $cmdname can be undef for special elements. |
| sub _default_format_heading_text($$$$$;$$$) { |
| my ($self, $cmdname, $classes, $text, $level, $id, $element, $target) = @_; |
| |
| return '' if ($text !~ /\S/ and not defined($id)); |
| |
| # This happens with titlefont in title for instance |
| if (in_string($self)) { |
| $text .= "\n" unless (defined($cmdname) and $cmdname eq 'titlefont'); |
| return $text; |
| } |
| |
| if ($level < 1) { |
| $level = 1; |
| } elsif ($level > $self->get_conf('MAX_HEADER_LEVEL')) { |
| $level = $self->get_conf('MAX_HEADER_LEVEL'); |
| } |
| |
| my $result = $self->html_attribute_class("h$level", $classes); |
| |
| if (defined($id)) { |
| $result .= " id=\"$id\""; |
| |
| # The ID of this heading is likely the point the user would prefer being |
| # linked to over the $target, since that's where they would be seeing a |
| # copiable anchor. |
| $target = $id; |
| } |
| $result .= '>'; |
| |
| my $anchor = _get_copiable_anchor($self, $target); |
| if (defined($anchor)) { |
| $result .= '<span>'; |
| } |
| $result .= $text; |
| if (defined($anchor)) { |
| $result .= "$anchor</span>"; |
| } |
| $result .= "</h$level>"; |
| |
| # titlefont appears inline in text, so no end of line is |
| # added. The end of line should be added by the user if needed. |
| $result .= "\n" unless (defined($cmdname) and $cmdname eq 'titlefont'); |
| $result .= $self->get_conf('DEFAULT_RULE') . "\n" |
| if (defined($cmdname) and $cmdname eq 'part' |
| and defined($self->get_conf('DEFAULT_RULE')) |
| and $self->get_conf('DEFAULT_RULE') ne ''); |
| return $result; |
| } |
| |
| sub _default_format_separate_anchor($$;$) { |
| my ($self, $id, $class) = @_; |
| |
| # html_attribute_class would not work with span, so if span is |
| # used, html_attribute_class should not be used |
| return $self->html_attribute_class('a', [$class])." id=\"$id\"></a>"; |
| } |
| |
| # 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, $direction, $source_command, $omit_rel, |
| $use_first_element_in_file_directions) = @_; |
| |
| my $result; |
| |
| if ((defined($self->get_conf('USE_NODE_DIRECTIONS')) |
| and $self->get_conf('USE_NODE_DIRECTIONS')) |
| or (not defined($self->get_conf('USE_NODE_DIRECTIONS')) |
| and $self->get_conf('USE_NODES'))) { |
| $direction = 'Node'.$direction; |
| } |
| |
| if ($use_first_element_in_file_directions) { |
| $direction = 'FirstInFile'.$direction; |
| } |
| |
| my $href = $self->from_element_direction($direction, 'href', |
| undef, undef, $source_command); |
| my $node; |
| |
| my $xrefautomaticsectiontitle = $self->get_conf('xrefautomaticsectiontitle'); |
| if (defined($xrefautomaticsectiontitle) |
| and $xrefautomaticsectiontitle eq 'on') { |
| $node = $self->from_element_direction($direction, 'section_nonumber'); |
| } |
| |
| if (!defined($node)) { |
| $node = $self->from_element_direction($direction, 'node'); |
| } |
| |
| if (defined($node) and $node =~ /\S/) { |
| my $hyperlink; |
| if (defined($href) and $href ne '') { |
| my $hyperlink_attributes = $omit_rel ? '' |
| : _direction_href_attributes($self, $direction); |
| $hyperlink = "<a href=\"$href\"${hyperlink_attributes}>$node</a>"; |
| } else { |
| $hyperlink = $node; |
| } |
| # i18n |
| my $direction_text = $self->direction_string($direction, 'text'); |
| $direction_text = '' if (!defined($direction_text)); |
| |
| $result = ''; |
| my $open = $self->html_attribute_class('span', ['nav-label']); |
| if ($open ne '') { |
| $result .= $open.'>'; |
| } |
| $result .= $direction_text.': '; |
| if ($open ne '') { |
| $result .= '</span>'; |
| } |
| $open = $self->html_attribute_class('span', ['nav-link']); |
| if ($open ne '') { |
| $result .= $open.'>'; |
| } |
| $result .= $hyperlink; |
| if ($open ne '') { |
| $result .= '</span>'; |
| } |
| } |
| # 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, $direction, $source_command) = @_; |
| |
| return _default_panel_button_dynamic_direction($self, $direction, |
| $source_command, 1); |
| } |
| |
| # used for button bar at the foot of a section or chapter with |
| # directions of first element in file used instead of the last |
| # element directions. |
| sub _default_panel_button_dynamic_direction_section_footer($$$) { |
| my ($self, $direction, $source_command) = @_; |
| |
| return _default_panel_button_dynamic_direction($self, $direction, |
| $source_command, undef, 1); |
| } |
| |
| # Only used if ICONS is set and the button is active. |
| sub _default_format_button_icon_img($$$;$) { |
| my ($self, $button, $icon, $name) = @_; |
| |
| 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; |
| } |
| |
| my $img = $self->html_attribute_class('img', ['nav-icon']); |
| return $self->close_html_lone_element( |
| "$img src=\"".$self->url_protect_url_text($icon)."\" alt=\"$alt\""); |
| } |
| |
| sub _direction_href_attributes($$) { |
| my ($self, $direction) = @_; |
| |
| my $href_attributes = ''; |
| if ($self->get_conf('USE_ACCESSKEY')) { |
| my $accesskey = $self->direction_string($direction, 'accesskey', 'string'); |
| if (defined($accesskey) and ($accesskey ne '')) { |
| $href_attributes = " accesskey=\"$accesskey\""; |
| } |
| } |
| my $button_rel = $self->direction_string($direction, 'rel', 'string'); |
| if (defined($button_rel) and ($button_rel ne '')) { |
| $href_attributes .= " rel=\"$button_rel\""; |
| } |
| return $href_attributes; |
| } |
| |
| my %html_default_node_directions; |
| foreach my $node_directions ('NodeNext', 'NodePrev', 'NodeUp') { |
| $html_default_node_directions{$node_directions} = 1; |
| } |
| |
| sub _default_format_button($$;$) { |
| my ($self, $button, $source_command) = @_; |
| |
| my ($active, $passive, $need_delimiter); |
| if (ref($button) eq 'CODE') { |
| ($active, $need_delimiter) = &$button($self); |
| } elsif (ref($button) eq 'ARRAY' and scalar(@$button == 2)) { |
| my $text = $button->[1]; |
| my $direction = $button->[0]; |
| # $direction is simple text and $text is a reference on code |
| if (defined($direction) and ref($direction) eq '' |
| and defined($text) and (ref($text) eq 'CODE')) { |
| ($active, $need_delimiter) = &$text($self, $direction, $source_command); |
| # $direction is simple text and $text is also a simple text |
| } elsif (defined($direction) and ref($direction) eq '' |
| and defined($text) and ref($text) eq '') { |
| if ($text =~ s/^->\s*//) { |
| # this case is mostly for tests, to test the direction type $text |
| # with the direction $direction |
| $active = $self->from_element_direction($direction, $text, |
| undef, undef, $source_command); |
| } |
| $need_delimiter = 1; |
| } |
| } elsif (defined($self->global_direction_text($button))) { |
| # handle "direction" text button without output unit (Space) |
| if ($self->get_conf('ICONS')) { |
| my $direction_icon; |
| my $active_icons = $self->get_conf('ACTIVE_ICONS'); |
| if (defined($active_icons)) { |
| $direction_icon = $active_icons->{$button}; |
| } |
| if (defined($direction_icon) and $direction_icon ne '') { |
| my $button_name_string = $self->direction_string($button, |
| 'button', 'string'); |
| $active = &{$self->formatting_function('format_button_icon_img')}($self, |
| $button_name_string, $direction_icon); |
| } else { |
| $active = $self->direction_string($button, 'text'); |
| } |
| } else { |
| $active = $self->direction_string($button, 'text'); |
| } |
| $need_delimiter = 0; |
| } else { |
| my $href = $self->from_element_direction($button, 'href', |
| undef, undef, $source_command); |
| if (defined($href)) { |
| # button is active |
| my $btitle = ''; |
| my $description = $self->direction_string($button, 'description', 'string'); |
| if (defined($description)) { |
| $btitle = ' title="' . $description . '"'; |
| } |
| if ($self->get_conf('USE_ACCESSKEY')) { |
| my $accesskey = $self->direction_string($button, 'accesskey', 'string'); |
| if (defined($accesskey) and $accesskey ne '') { |
| $btitle .= " accesskey=\"$accesskey\""; |
| } |
| } |
| my $button_rel = $self->direction_string($button, 'rel', 'string'); |
| if (defined($button_rel) and $button_rel ne '') { |
| $btitle .= " rel=\"$button_rel\""; |
| } |
| if ($self->get_conf('ICONS')) { |
| my $active_icon; |
| my $active_icons = $self->get_conf('ACTIVE_ICONS'); |
| if (defined($active_icons)) { |
| $active_icon = $active_icons->{$button}; |
| } |
| if (defined($active_icon) and $active_icon ne '') { |
| my $button_name_string = $self->direction_string($button, |
| 'button', 'string'); |
| $active = "<a href=\"$href\"${btitle}>". |
| &{$self->formatting_function('format_button_icon_img')}($self, |
| $button_name_string, $active_icon, |
| $self->from_element_direction($button, 'string')) ."</a>"; |
| } else { |
| # use text |
| my $button_text = $self->direction_string($button, 'text'); |
| $button_text = '' if (!defined($button_text)); |
| $active = '['."<a href=\"$href\"${btitle}>".$button_text."</a>".']'; |
| } |
| } else { |
| # use text |
| my $button_text = $self->direction_string($button, 'text'); |
| $button_text = '' if (!defined($button_text)); |
| $active = '['."<a href=\"$href\"${btitle}>".$button_text."</a>".']'; |
| } |
| } else { |
| # button is passive |
| if ($self->get_conf('ICONS')) { |
| my $passive_icon; |
| my $passive_icons = $self->get_conf('PASSIVE_ICONS'); |
| if (defined($passive_icons)) { |
| $passive_icon = $passive_icons->{$button}; |
| } |
| if (defined($passive_icon) and $passive_icon ne '') { |
| my $button_name_string = $self->direction_string($button, |
| 'button', 'string'); |
| $passive = &{$self->formatting_function('format_button_icon_img')}( |
| $self, $button_name_string, $passive_icon, |
| $self->from_element_direction($button, 'string')); |
| } else { |
| my $button_text = $self->direction_string($button, 'text'); |
| $button_text = '' if (!defined($button_text)); |
| $passive = '['.$button_text. ']'; |
| } |
| } else { |
| my $button_text = $self->direction_string($button, 'text'); |
| $button_text = '' if (!defined($button_text)); |
| $passive = '['.$button_text. ']'; |
| } |
| } |
| $need_delimiter = 0; |
| } |
| if (not defined($need_delimiter)) { |
| # NOTE other options could have been chosen in that case: |
| # option 1: be forgiving if $need_delimiter is not set |
| # if ($html_default_node_directions{$button}) { |
| # $need_delimiter = 1; |
| # } else { |
| # $need_delimiter = 0; |
| # } |
| # option 2: be somewhat forgiving but show a backtrace |
| #cluck ("need_delimiter not defined"); |
| # $need_delimiter = 0; |
| # option3: no pity |
| confess ("need_delimiter not defined"); |
| } |
| return ($active, $passive, $need_delimiter); |
| } |
| |
| # called for special elements and tree units |
| sub _default_format_navigation_panel($$$$;$$) { |
| my ($self, $buttons, $cmdname, $source_command, $vertical, $in_header) = @_; |
| |
| # a string may be passed, for instance through command line, therefore |
| # it is useful to test that $buttons is an array reference to avoid |
| # a Perl error message |
| if (ref($buttons) ne 'ARRAY') { |
| return ''; |
| } |
| |
| # do the buttons first in case they are formatted as an empty string |
| my $nr_of_buttons_shown = 0; |
| my $result_buttons = ''; |
| foreach my $button (@$buttons) { |
| my $direction; |
| if (ref($button) eq 'ARRAY' |
| and defined($button->[0]) and ref($button->[0]) eq '') { |
| $direction = $button->[0]; |
| } elsif (defined($button) and ref($button) eq '') { |
| $direction = $button; |
| } |
| # if the first button is an empty button, pass |
| if (defined($direction) |
| and $direction eq 'Space' and $nr_of_buttons_shown == 0) { |
| next; |
| } |
| |
| my ($active, $passive, $need_delimiter) |
| # API info: using the API to allow for customization would be: |
| # = &{$self->formatting_function('format_button')}($self, $button, |
| # $source_command); |
| = &{$self->{'formatting_function'}->{'format_button'}}($self, $button, |
| $source_command); |
| if ($self->get_conf('HEADER_IN_TABLE')) { |
| $result_buttons .= '<tr>'."\n" if $vertical; |
| $result_buttons .= $self->html_attribute_class('td', ['nav-button']).'>'; |
| |
| if (defined($active)) { |
| $result_buttons .= $active; |
| } elsif (defined($passive)) { |
| $result_buttons .= $passive; |
| } |
| |
| $result_buttons .= "</td>\n"; |
| $result_buttons .= "</tr>\n" if $vertical; |
| |
| $nr_of_buttons_shown++; |
| } elsif (defined($active)) { |
| # only active buttons are print out when not in table |
| if ($need_delimiter and $nr_of_buttons_shown > 0) { |
| $result_buttons .= ', '; |
| } |
| |
| my $open = $self->html_attribute_class('span', ['nav-button']); |
| if ($open ne '') { |
| $result_buttons .= $open.'>'; |
| } |
| $result_buttons .= $active; |
| if ($open ne '') { |
| $result_buttons .= '</span>'; |
| } |
| |
| $nr_of_buttons_shown++; |
| } |
| } |
| |
| if ($result_buttons eq '') { |
| return ''; |
| } |
| |
| my $result = ''; |
| |
| # if $vertical/VERTICAL_HEAD_NAVIGATION, the buttons are in a vertical |
| # table which is itself in the first column of a table opened in |
| # header_navigation |
| |
| if ($self->get_conf('HEADER_IN_TABLE')) { |
| $result .= $self->html_attribute_class('table', ['nav-panel']).'>'."\n"; |
| $result .= "<tr>" unless $vertical; |
| } else { |
| $result .= $self->html_attribute_class('p', ['nav-panel']).">\n"; |
| } |
| |
| $result .= $result_buttons; |
| |
| if ($self->get_conf('HEADER_IN_TABLE')) { |
| $result .= "</tr>" unless $vertical; |
| $result .= "</table>\n"; |
| } else { |
| $result .= "</p>\n"; |
| } |
| return $result; |
| } |
| |
| sub _default_format_navigation_header($$$$) { |
| my ($self, $buttons, $cmdname, $element) = @_; |
| |
| my $result = ''; |
| if ($self->get_conf('VERTICAL_HEAD_NAVIGATION')) { |
| $result .= $self->html_attribute_class('table', |
| ['vertical-navigation']).'>'."\n"; |
| $result .= "<tr>\n"; |
| $result .= $self->html_attribute_class('td', |
| ['vertical-navigation']).'>'."\n"; |
| } |
| $result .= &{$self->formatting_function('format_navigation_panel')}($self, |
| $buttons, $cmdname, $element, |
| $self->get_conf('VERTICAL_HEAD_NAVIGATION'), 1); |
| if ($self->get_conf('VERTICAL_HEAD_NAVIGATION')) { |
| $result .= '</td> |
| <td> |
| '; |
| } elsif ($self->get_conf('SPLIT') |
| and $self->get_conf('SPLIT') eq 'node' and $result ne '' |
| and defined($self->get_conf('DEFAULT_RULE'))) { |
| $result .= $self->get_conf('DEFAULT_RULE')."\n"; |
| } |
| return $result; |
| } |
| |
| # this can only be called on root commands and associated tree units |
| sub _default_format_element_header($$$$) { |
| my ($self, $cmdname, $command, $output_unit) = @_; |
| |
| my $result = ''; |
| |
| print STDERR "FORMAT elt header " |
| # uncomment to get perl object names |
| #."$output_unit (@{$output_unit->{'unit_contents'}}) ". |
| . "(".join('|', map{Texinfo::Common::debug_print_element($_)} |
| @{$output_unit->{'unit_contents'}}) . ") ". |
| Texinfo::OutputUnits::output_unit_texi($output_unit) ."\n" |
| if ($self->get_conf('DEBUG')); |
| |
| # Do the heading if the command is the first command in the element |
| if (($output_unit->{'unit_contents'}->[0] eq $command |
| or (!exists($output_unit->{'unit_contents'}->[0]->{'cmdname'}) |
| and $output_unit->{'unit_contents'}->[1] eq $command)) |
| # and there is more than one element |
| and (exists($output_unit->{'tree_unit_directions'}))) { |
| my $is_top = $self->unit_is_top_output_unit($output_unit); |
| my $first_in_page = 0; |
| if (exists($output_unit->{'unit_filename'}) |
| and $self->count_elements_in_filename('current', |
| $output_unit->{'unit_filename'}) == 1) { |
| $first_in_page = 1; |
| } |
| my $previous_is_top = 0; |
| $previous_is_top = 1 |
| if (exists($output_unit->{'tree_unit_directions'}->{'prev'}) |
| and $self->unit_is_top_output_unit($output_unit->{'tree_unit_directions'} |
| ->{'prev'})); |
| |
| print STDERR "Header ($previous_is_top, $is_top, $first_in_page): " |
| .Texinfo::Convert::Texinfo::root_heading_command_to_texinfo($command)."\n" |
| if ($self->get_conf('DEBUG')); |
| |
| if ($is_top) { |
| # use TOP_BUTTONS for top. |
| $result .= |
| &{$self->formatting_function('format_navigation_header')}($self, |
| $self->get_conf('TOP_BUTTONS'), $cmdname, $command) |
| if ($self->get_conf('SPLIT') or $self->get_conf('HEADERS')); |
| } else { |
| my $split = $self->get_conf('SPLIT'); |
| if ($first_in_page and !$self->get_conf('HEADERS')) { |
| if (defined($split) and $split eq 'chapter') { |
| $result |
| .= &{$self->formatting_function('format_navigation_header')}($self, |
| $self->get_conf('CHAPTER_BUTTONS'), $cmdname, $command); |
| |
| $result .= $self->get_conf('DEFAULT_RULE') ."\n" |
| if (defined($self->get_conf('DEFAULT_RULE')) |
| and !$self->get_conf('VERTICAL_HEAD_NAVIGATION')); |
| } elsif (defined($split) and $split eq 'section') { |
| $result |
| .= &{$self->formatting_function('format_navigation_header')}($self, |
| $self->get_conf('SECTION_BUTTONS'), $cmdname, $command); |
| } |
| } |
| if (($first_in_page or $previous_is_top) |
| and $self->get_conf('HEADERS')) { |
| $result |
| .= &{$self->formatting_function('format_navigation_header')}($self, |
| $self->get_conf('SECTION_BUTTONS'), $cmdname, $command); |
| } elsif ($self->get_conf('HEADERS') |
| or (defined($split) and $split eq 'node')) { |
| # got to do this here, as it isn't done otherwise since |
| # navigation_header is not called |
| $result |
| .= &{$self->formatting_function('format_navigation_panel')}($self, |
| $self->get_conf('SECTION_BUTTONS'), $cmdname, |
| $command, undef, 1); |
| } |
| } |
| } |
| return $result; |
| } |
| |
| sub _contents_inline_element($$$) { |
| my ($self, $cmdname, |
| # undef unless called from @-command formatting function |
| $element) = @_; |
| |
| print STDERR "CONTENTS_INLINE $cmdname\n" if ($self->get_conf('DEBUG')); |
| my $table_of_contents |
| = &{$self->formatting_function('format_contents')}($self, |
| $cmdname, $element); |
| if (defined($table_of_contents) and $table_of_contents ne '') { |
| my ($special_unit_variety, $special_unit, $class_base, |
| $special_unit_direction) |
| = $self->command_name_special_unit_information($cmdname); |
| my $result = $self->html_attribute_class('div', ["region-${class_base}"]); |
| my $unit_command = $special_unit->{'unit_command'}; |
| my $id = $self->command_id($unit_command); |
| if (defined($id) and $id ne '') { |
| $result .= " id=\"$id\""; |
| } |
| $result .= ">\n"; |
| my $heading = $self->command_text($unit_command); |
| $heading = '' if (!defined($heading)); |
| $result .= &{$self->formatting_function('format_heading_text')}($self, |
| $cmdname, [$class_base.'-heading'], $heading, |
| $self->get_conf('CHAPTER_HEADER_LEVEL'))."\n"; |
| $result .= $table_of_contents . "</div>\n"; |
| return $result; |
| } |
| return ''; |
| } |
| |
| sub _convert_heading_command($$$$$) { |
| my ($self, $cmdname, $element, $args, $content) = @_; |
| |
| my $result = ''; |
| |
| # No situation where this could happen |
| if (in_string($self)) { |
| $result .= $self->command_text($element, 'string') ."\n" |
| if ($cmdname ne 'node'); |
| $result .= $content if (defined($content)); |
| return $result; |
| } |
| |
| my $element_id = $self->command_id($element); |
| |
| print STDERR "CONVERT elt heading " |
| .Texinfo::Convert::Texinfo::root_heading_command_to_texinfo($element)."\n" |
| if ($self->get_conf('DEBUG')); |
| |
| my $document = $self->get_info('document'); |
| my $sections_list; |
| my $nodes_list; |
| if (defined($document)) { |
| $sections_list = $document->sections_list(); |
| $nodes_list = $document->nodes_list(); |
| } |
| |
| my $output_unit; |
| my $section_relations; |
| my $node_relations; |
| |
| if (exists($Texinfo::Commands::root_commands{$cmdname})) { |
| if ($cmdname eq 'node') { |
| if (defined($nodes_list) and exists($element->{'extra'}) |
| and $element->{'extra'}->{'node_number'}) { |
| $node_relations |
| = $nodes_list->[$element->{'extra'}->{'node_number'} -1]; |
| } |
| } elsif (defined($sections_list)) { |
| $section_relations |
| = $sections_list->[$element->{'extra'}->{'section_number'} -1]; |
| } |
| # All the root commands are associated to an output unit, the condition |
| # on associated_unit is always true. |
| if (exists($element->{'associated_unit'})) { |
| $output_unit = $element->{'associated_unit'}; |
| } |
| } |
| |
| my $element_header = ''; |
| if ($output_unit) { |
| $element_header = &{$self->formatting_function('format_element_header')}( |
| $self, $cmdname, $element, $output_unit); |
| } |
| |
| my $toc_or_mini_toc_or_auto_menu = ''; |
| if ($self->get_conf('CONTENTS_OUTPUT_LOCATION') eq 'after_top' |
| and $cmdname eq 'top' |
| and defined($sections_list) |
| and scalar(@{$sections_list}) > 1) { |
| foreach my $content_command_name ('shortcontents', 'contents') { |
| if ($self->get_conf($content_command_name)) { |
| my $contents_text |
| = _contents_inline_element($self, $content_command_name, undef); |
| if ($contents_text ne '') { |
| $toc_or_mini_toc_or_auto_menu .= $contents_text; |
| } |
| } |
| } |
| } |
| |
| my $format_menu = $self->get_conf('FORMAT_MENU'); |
| if ($toc_or_mini_toc_or_auto_menu eq '' and defined($section_relations)) { |
| if ($format_menu eq 'sectiontoc') { |
| $toc_or_mini_toc_or_auto_menu = _mini_toc($self, $section_relations); |
| } elsif (($format_menu eq 'menu' or $format_menu eq 'menu_no_detailmenu') |
| and exists($section_relations->{'associated_node'})) { |
| my $associated_node_relations = $section_relations->{'associated_node'}; |
| # arguments_line type element |
| my $arguments_line |
| = $associated_node_relations->{'element'}->{'contents'}->[0]; |
| my $automatic_directions = 1; |
| if (scalar(@{$arguments_line->{'contents'}}) > 1) { |
| $automatic_directions = 0; |
| } |
| |
| if ($automatic_directions |
| and !exists($associated_node_relations->{'menus'})) { |
| my $identifiers_target = $document->labels_information(); |
| |
| my $menu_node; |
| if ($format_menu eq 'menu') { |
| $menu_node |
| = Texinfo::Structuring::new_complete_menu_master_menu($self, |
| $identifiers_target, $nodes_list, |
| $associated_node_relations); |
| } else { # $format_menu eq 'menu_no_detailmenu' |
| $menu_node |
| = Texinfo::Structuring::new_complete_node_menu( |
| $associated_node_relations, |
| $self->{'current_lang_translations'}, |
| $self->get_conf('DEBUG')); |
| } |
| if (defined($menu_node)) { |
| $toc_or_mini_toc_or_auto_menu |
| = $self->convert_tree($menu_node, 'master menu'); |
| } |
| } |
| } |
| } |
| |
| if ($self->get_conf('NO_TOP_NODE_OUTPUT') |
| and exists($Texinfo::Commands::root_commands{$cmdname})) { |
| my $in_skipped_node_top |
| = $self->get_shared_conversion_state('top', 'in_skipped_node_top'); |
| $in_skipped_node_top = 0 if (!defined($in_skipped_node_top)); |
| if ($in_skipped_node_top == 1) { |
| my $id_class = $cmdname; |
| $result .= &{$self->formatting_function('format_separate_anchor')}($self, |
| $element_id, $id_class); |
| $result .= $element_header; |
| $result .= $toc_or_mini_toc_or_auto_menu; |
| return $result; |
| } |
| } |
| |
| my $level_corrected_cmdname = $cmdname; |
| my $level_set_class; |
| if (exists($element->{'extra'}) |
| and exists($element->{'extra'}->{'section_level'})) { |
| # if the level was changed, use a consistent command name |
| $level_corrected_cmdname |
| = Texinfo::Structuring::section_level_adjusted_command_name($element); |
| if ($level_corrected_cmdname ne $cmdname) { |
| $level_set_class = "${cmdname}-level-set-${level_corrected_cmdname}"; |
| } |
| } |
| |
| # find the section starting here, can be through the associated node |
| # preceding the section, or the section itself |
| my $opening_section; |
| my $level_corrected_opening_section_cmdname; |
| if (defined($node_relations) |
| and exists($node_relations->{'associated_section'})) { |
| $opening_section = $node_relations->{'associated_section'}->{'element'}; |
| $level_corrected_opening_section_cmdname |
| = Texinfo::Structuring::section_level_adjusted_command_name( |
| $opening_section); |
| # if there is an associated node, it is not a section opening |
| # the section was opened before when the node was encountered |
| } elsif (defined($section_relations) |
| and !exists($section_relations->{'associated_node'})) { |
| $opening_section = $element; |
| $level_corrected_opening_section_cmdname = $level_corrected_cmdname; |
| } |
| |
| # could use empty args information also, to avoid calling command_text |
| #my $empty_heading = (!scalar(@$args) or !defined($args->[0])); |
| |
| # $heading not defined may happen if the command is a @node, for example |
| # if there is an error in the node. |
| my $heading = $self->command_text($element); |
| my $heading_level; |
| # node is used as heading if there is nothing else. |
| if (defined($node_relations)) { |
| if (defined($output_unit) and exists($output_unit->{'unit_node'}) |
| and $output_unit->{'unit_node'} eq $node_relations |
| and !exists($node_relations->{'associated_title_command'})) { |
| if ($element->{'extra'}->{'identifier'} eq 'Top') { |
| $heading_level = 0; |
| } else { |
| # use node |
| $heading_level = 3; |
| } |
| } |
| } elsif (exists($element->{'extra'}) |
| and exists($element->{'extra'}->{'section_level'})) { |
| $heading_level = $element->{'extra'}->{'section_level'}; |
| } else { |
| # for *heading* @-commands which do not have a level in the document |
| # as they are not associated with the sectioning tree, but still |
| # have a $heading_level. For all sectioning elements if |
| # Structuring sectioning_structure was not called on the |
| # document (cannot happen in main program nor test_utils.pl based tests) |
| $heading_level = Texinfo::Common::section_level($element); |
| } |
| |
| my $do_heading = (defined($heading) and $heading ne '' |
| and defined($heading_level)); |
| |
| # if set, the id is associated to the heading text |
| my $heading_id; |
| if ($opening_section) { |
| my $level; |
| if (exists($opening_section->{'extra'}) |
| and exists($opening_section->{'extra'}->{'section_level'})) { |
| $level = $opening_section->{'extra'}->{'section_level'}; |
| } else { |
| # if Structuring sectioning_structure was not called on the |
| # document (cannot happen in main program nor test_utils.pl based tests) |
| $level = Texinfo::Common::section_level($opening_section); |
| } |
| my $closed_strings = $self->close_registered_sections_level( |
| $self->current_filename(), $level); |
| $result .= join('', @{$closed_strings}); |
| $self->register_opened_section_level($self->current_filename(), $level, |
| "</div>\n"); |
| |
| # use a specific class name to mark that this is the start of |
| # the section extent. It is not necessary where the section is. |
| $result .= $self->html_attribute_class('div', |
| ["${level_corrected_opening_section_cmdname}-level-extent"]); |
| $result .= " id=\"$element_id\"" |
| if (defined($element_id) and $element_id ne ''); |
| $result .= ">\n"; |
| } elsif (defined($element_id) and $element_id ne '') { |
| if ($element_header ne '') { |
| # case of a @node without sectioning command and with a header. |
| # put the node element anchor before the header. |
| # Set the class name to the command name if there is no heading, |
| # else the class will be with the heading element. |
| my $id_class = $cmdname; |
| if ($do_heading) { |
| $id_class = "${cmdname}-id"; |
| } |
| $result .= &{$self->formatting_function('format_separate_anchor')}($self, |
| $element_id, $id_class); |
| } else { |
| $heading_id = $element_id; |
| } |
| } |
| |
| $result .= $element_header; |
| |
| if ($do_heading) { |
| if ($self->get_conf('TOC_LINKS') |
| and exists($Texinfo::Commands::root_commands{$cmdname}) |
| and exists($sectioning_heading_commands{$cmdname})) { |
| my $content_href = $self->command_contents_href($element, 'contents'); |
| if (defined($content_href)) { |
| $heading = "<a href=\"$content_href\">$heading</a>"; |
| } |
| } |
| |
| my @heading_classes; |
| push @heading_classes, $level_corrected_cmdname; |
| if (defined($level_set_class)) { |
| push @heading_classes, $level_set_class; |
| } |
| if (in_preformatted_context($self)) { |
| my $id_str = ''; |
| if (defined($heading_id)) { |
| $id_str = " id=\"$heading_id\""; |
| } |
| $result .= $self->html_attribute_class('strong', \@heading_classes) |
| ."${id_str}>".$heading.'</strong>'."\n"; |
| } else { |
| $result .= &{$self->formatting_function('format_heading_text')}($self, |
| $level_corrected_cmdname, \@heading_classes, $heading, |
| $heading_level +$self->get_conf('CHAPTER_HEADER_LEVEL') -1, |
| $heading_id, $element, $element_id); |
| } |
| } elsif (defined($heading_id)) { |
| # case of a lone node and no header, and case of an empty @top |
| $result .= &{$self->formatting_function('format_separate_anchor')}($self, |
| $heading_id, $cmdname); |
| } |
| |
| $result .= $content if (defined($content)); |
| |
| $result .= $toc_or_mini_toc_or_auto_menu; |
| |
| return $result; |
| } |
| |
| foreach my $cmdname (keys(%sectioning_heading_commands), 'node') { |
| $default_commands_conversion{$cmdname} = \&_convert_heading_command; |
| } |
| |
| sub _convert_raw_command($$$$$) { |
| my ($self, $cmdname, $command, $args, $content) = @_; |
| |
| $content = '' if (!defined($content)); |
| |
| if ($cmdname eq 'html') { |
| return $content; |
| } |
| |
| # In multiple conversions should only happen rarely, as in general, format |
| # commands do not happen in inline context where most of the multiple |
| # conversions are. A possibility is in float caption. |
| if (!$self->in_multiple_conversions()) { |
| $self->converter_line_warn(sprintf(__("raw format %s is not converted"), |
| $cmdname), $command->{'source_info'}); |
| } |
| return &{$self->formatting_function('format_protect_text')}($self, $content); |
| } |
| |
| foreach my $cmdname (keys(%format_raw_commands)) { |
| $default_commands_conversion{$cmdname} = \&_convert_raw_command; |
| } |
| |
| sub _convert_inline_command($$$$) { |
| my ($self, $cmdname, $command, $args) = @_; |
| |
| my $format; |
| if (defined($args) and defined($args->[0]) |
| and defined($args->[0]->{'monospacetext'}) |
| and $args->[0]->{'monospacetext'} ne '') { |
| $format = $args->[0]->{'monospacetext'}; |
| } else { |
| return ''; |
| } |
| |
| my $arg_index = undef; |
| if (exists($inline_format_commands{$cmdname})) { |
| if ($cmdname eq 'inlinefmtifelse' and !$self->is_format_expanded($format)) { |
| $arg_index = 2; |
| } elsif ($self->is_format_expanded($format)) { |
| $arg_index = 1; |
| } |
| } elsif (exists($command->{'extra'}) |
| and $command->{'extra'}->{'expand_index'}) { |
| $arg_index = 1; |
| } |
| if (defined($arg_index) and $arg_index < scalar(@$args)) { |
| my $text_arg = $args->[$arg_index]; |
| if (defined($text_arg)) { |
| if (defined($text_arg->{'normal'})) { |
| return $text_arg->{'normal'}; |
| } elsif (defined($text_arg->{'raw'})) { |
| return $text_arg->{'raw'}; |
| } |
| } |
| } |
| return ''; |
| } |
| |
| foreach my $cmdname (grep {$brace_commands{$_} eq 'inline'} |
| keys(%brace_commands)) { |
| $default_commands_conversion{$cmdname} = \&_convert_inline_command; |
| } |
| |
| sub _indent_with_table($$$;$) { |
| my ($self, $cmdname, $content, $extra_classes) = @_; |
| |
| my @classes = ($cmdname); |
| push (@classes, @$extra_classes) if (defined($extra_classes)); |
| return $self->html_attribute_class('table', \@classes) |
| .'><tr><td>'.$self->get_info('non_breaking_space').'</td><td>'.$content |
| ."</td></tr></table>\n"; |
| } |
| |
| sub _convert_preformatted_command($$$$$) { |
| my ($self, $cmdname, $command, $args, $content) = @_; |
| |
| if (!defined($content) or $content eq '') { |
| return ''; |
| } |
| |
| if (in_string($self)) { |
| return $content; |
| } |
| |
| my @classes; |
| |
| # this is mainly for classes as there are purprosely no classes |
| # for small* |
| my $main_cmdname; |
| if (exists($small_block_associated_command{$cmdname})) { |
| $main_cmdname = $small_block_associated_command{$cmdname}; |
| push @classes, $cmdname; |
| } else { |
| $main_cmdname = $cmdname; |
| } |
| |
| if ($cmdname eq 'example') { |
| # arguments_line type element |
| my $arguments_line = $command->{'contents'}->[0]; |
| foreach my $example_arg (@{$arguments_line->{'contents'}}) { |
| # convert or remove all @-commands, using simple ascii and unicode |
| # characters |
| my $converted_arg |
| = Texinfo::Convert::NodeNameNormalization::convert_to_normalized( |
| $example_arg); |
| if ($converted_arg ne '') { |
| push @classes, 'user-' . $converted_arg; |
| } |
| } |
| } elsif ($main_cmdname eq 'lisp') { |
| push @classes, $main_cmdname; |
| $main_cmdname = 'example'; |
| } |
| |
| if ($self->get_conf('INDENTED_BLOCK_COMMANDS_IN_TABLE') |
| and exists($indented_preformatted_commands{$cmdname})) { |
| return _indent_with_table($self, $cmdname, $content, \@classes); |
| } else { |
| unshift @classes, $main_cmdname; |
| return $self->html_attribute_class('div', \@classes) |
| .">\n".$content.'</div>'."\n"; |
| } |
| } |
| |
| foreach my $preformatted_command (keys(%preformatted_commands)) { |
| $default_commands_conversion{$preformatted_command} |
| = \&_convert_preformatted_command; |
| } |
| |
| sub _convert_indented_command($$$$$) { |
| my ($self, $cmdname, $command, $args, $content) = @_; |
| |
| if (!defined($content) or $content eq '') { |
| return ''; |
| } |
| |
| if (in_string($self)) { |
| return $content; |
| } |
| |
| my @classes; |
| |
| my $main_cmdname; |
| if (exists($small_block_associated_command{$cmdname})) { |
| push @classes, $cmdname; |
| $main_cmdname = $small_block_associated_command{$cmdname}; |
| } else { |
| $main_cmdname = $cmdname; |
| } |
| |
| if ($self->get_conf('INDENTED_BLOCK_COMMANDS_IN_TABLE')) { |
| return _indent_with_table($self, $main_cmdname, $content, \@classes); |
| } else { |
| unshift @classes, $main_cmdname; |
| return $self->html_attribute_class('blockquote', \@classes).">\n" |
| . $content . '</blockquote>'."\n"; |
| } |
| } |
| |
| $default_commands_conversion{'indentedblock'} = \&_convert_indented_command; |
| |
| sub _convert_verbatim_command($$$$$) { |
| my ($self, $cmdname, $command, $args, $content) = @_; |
| |
| $content = '' if (!defined($content)); |
| |
| if (!in_string($self)) { |
| return $self->html_attribute_class('pre', [$cmdname]).'>' |
| .$content . '</pre>'; |
| } else { |
| return $content; |
| } |
| } |
| |
| $default_commands_conversion{'verbatim'} = \&_convert_verbatim_command; |
| |
| sub _convert_displaymath_command($$$$$) { |
| my ($self, $cmdname, $command, $args, $content) = @_; |
| |
| $content = '' if (!defined($content)); |
| |
| if (in_string($self)) { |
| return $content; |
| } |
| |
| my $result = ''; |
| my $pre_classes = [$cmdname]; |
| |
| my $use_mathjax = ($self->get_conf('HTML_MATH') |
| and $self->get_conf('HTML_MATH') eq 'mathjax'); |
| |
| if ($use_mathjax) { |
| $self->register_file_information('mathjax', 1); |
| push @$pre_classes, 'tex2jax_process'; |
| } |
| $result .= $self->html_attribute_class('pre', $pre_classes).'>'; |
| if ($self->get_conf('HTML_MATH') |
| and $self->get_conf('HTML_MATH') eq 'mathjax') { |
| $result .= "\\[$content\\]"; |
| } else { |
| $result .= $content; |
| } |
| $result .= '</pre>'; |
| return $result; |
| } |
| |
| $default_commands_conversion{'displaymath'} = \&_convert_displaymath_command; |
| |
| sub _convert_verbatiminclude_command($$$$) { |
| my ($self, $cmdname, $command, $args) = @_; |
| |
| my $verbatim_include_verbatim |
| = $self->expand_verbatiminclude($command); |
| if (defined($verbatim_include_verbatim)) { |
| return $self->convert_tree($verbatim_include_verbatim, |
| 'convert verbatiminclude'); |
| } else { |
| return ''; |
| } |
| } |
| |
| $default_commands_conversion{'verbatiminclude'} |
| = \&_convert_verbatiminclude_command; |
| |
| sub _convert_command_simple_block($$$$$) { |
| my ($self, $cmdname, $command, $args, $content) = @_; |
| |
| $content = '' if (!defined($content)); |
| |
| return $self->html_attribute_class('div', [$cmdname]).'>' |
| .$content.'</div>'; |
| } |
| |
| $default_commands_conversion{'raggedright'} = \&_convert_command_simple_block; |
| $default_commands_conversion{'flushleft'} = \&_convert_command_simple_block; |
| $default_commands_conversion{'flushright'} = \&_convert_command_simple_block; |
| $default_commands_conversion{'group'} = \&_convert_command_simple_block; |
| |
| sub _convert_sp_command($$$$) { |
| my ($self, $cmdname, $command, $args) = @_; |
| |
| my $sp_nr = 1; |
| if (exists($command->{'extra'}) |
| and exists($command->{'extra'}->{'misc_args'})) { |
| $sp_nr = $command->{'extra'}->{'misc_args'}->[0]; |
| } |
| if ($sp_nr > 0) { |
| if (in_preformatted_context($self) or in_string($self)) { |
| return "\n" x $sp_nr; |
| } else { |
| return ($self->get_info('line_break_element')."\n") x $sp_nr; |
| } |
| } else { |
| return ''; |
| } |
| } |
| |
| $default_commands_conversion{'sp'} = \&_convert_sp_command; |
| |
| sub _convert_exdent_command($$$$) { |
| my ($self, $cmdname, $command, $args) = @_; |
| |
| my $arg = $self->get_pending_formatted_inline_content(); |
| if (defined($args) and defined($args->[0])) { |
| $arg .= $args->[0]->{'normal'}; |
| } |
| |
| if (in_string($self)) { |
| return $arg ."\n"; |
| } |
| |
| # FIXME do something with CSS? Currently nothing is defined for exdent |
| |
| if (in_preformatted_context($self)) { |
| return $self->html_attribute_class('pre', [$cmdname]).'>'.$arg ."\n</pre>"; |
| } else { |
| return $self->html_attribute_class('p', [$cmdname]).'>'.$arg ."\n</p>"; |
| } |
| } |
| |
| $default_commands_conversion{'exdent'} = \&_convert_exdent_command; |
| |
| sub _convert_center_command($$$$) { |
| my ($self, $cmdname, $command, $args) = @_; |
| |
| if (!defined($args) or !defined($args->[0])) { |
| return ''; |
| } |
| |
| if (in_string($self)) { |
| return $args->[0]->{'normal'}."\n"; |
| } else { |
| return $self->html_attribute_class('div', [$cmdname]).">" |
| .$args->[0]->{'normal'}."\n</div>"; |
| } |
| } |
| |
| $default_commands_conversion{'center'} = \&_convert_center_command; |
| |
| sub _convert_author_command($$$$) { |
| my ($self, $cmdname, $command, $args) = @_; |
| |
| my $quotation_titlepage_nr = $self->get_shared_conversion_state('quotation', |
| 'quotation_titlepage_stack'); |
| if (defined($quotation_titlepage_nr) and $quotation_titlepage_nr > 0) { |
| my $authors_nr |
| = $self->get_shared_conversion_state('quotation', 'element_authors_number', |
| $quotation_titlepage_nr); |
| |
| if ($authors_nr < 0) { |
| # in titlepage |
| if (!in_string($self)) { |
| return $self->html_attribute_class('strong', [$cmdname]) |
| .">$args->[0]->{'normal'}</strong>" |
| .$self->get_info('line_break_element')."\n"; |
| } else { |
| return $args->[0]->{'normal'} . "\n"; |
| } |
| } else { |
| # in quotation |
| $self->set_shared_conversion_state('quotation', 'elements_authors', |
| $quotation_titlepage_nr, $authors_nr, |
| $command); |
| |
| $authors_nr++; |
| $self->set_shared_conversion_state('quotation', 'element_authors_number', |
| $quotation_titlepage_nr, $authors_nr); |
| } |
| } |
| return ''; |
| } |
| |
| $default_commands_conversion{'author'} = \&_convert_author_command; |
| |
| sub _convert_title_command($$$$) { |
| my ($self, $cmdname, $command, $args) = @_; |
| |
| return '' if (!defined($args) or !defined($args->[0])); |
| |
| if (!in_string($self)) { |
| return $self->html_attribute_class('h1', [$cmdname]) |
| .">$args->[0]->{'normal'}</h1>\n"; |
| } else { |
| return $args->[0]->{'normal'}; |
| } |
| } |
| |
| $default_commands_conversion{'title'} = \&_convert_title_command; |
| |
| sub _convert_subtitle_command($$$$) { |
| my ($self, $cmdname, $command, $args) = @_; |
| |
| return '' if (!defined($args) or !defined($args->[0])); |
| |
| if (!in_string($self)) { |
| return $self->html_attribute_class('h3', [$cmdname]) |
| .">$args->[0]->{'normal'}</h3>\n"; |
| } else { |
| return $args->[0]->{'normal'}; |
| } |
| } |
| |
| $default_commands_conversion{'subtitle'} = \&_convert_subtitle_command; |
| |
| sub _convert_insertcopying_command($$$) { |
| my ($self, $cmdname, $command) = @_; |
| |
| my $global_commands; |
| my $document = $self->get_info('document'); |
| if (defined($document)) { |
| $global_commands = $document->global_commands_information(); |
| } |
| |
| if (defined($global_commands) and exists($global_commands->{'copying'})) { |
| return $self->convert_tree( |
| Texinfo::TreeElement::new( |
| {'contents' => $global_commands->{'copying'}->{'contents'}}), |
| 'convert insertcopying'); |
| } |
| return ''; |
| } |
| |
| $default_commands_conversion{'insertcopying'} |
| = \&_convert_insertcopying_command; |
| |
| sub _convert_maketitle_command($$$) { |
| my ($self, $cmdname, $command) = @_; |
| |
| return $self->get_info('title_titlepage'); |
| } |
| |
| $default_commands_conversion{'maketitle'} |
| = \&_convert_maketitle_command; |
| |
| sub _convert_listoffloats_command($$$$) { |
| my ($self, $cmdname, $command, $args) = @_; |
| |
| # should probably never happen |
| return '' if (in_string($self)); |
| |
| my $floats; |
| my $document = $self->get_info('document'); |
| if (defined($document)) { |
| $floats = $document->floats_information(); |
| } |
| my $listoffloats_name = $command->{'extra'}->{'float_type'}; |
| my $formatted_listoffloats_nr |
| = $self->get_shared_conversion_state('listoffloats', |
| 'formatted_listoffloats', |
| $listoffloats_name); |
| $formatted_listoffloats_nr = 0 if (!defined($formatted_listoffloats_nr)); |
| $formatted_listoffloats_nr++; |
| $self->set_shared_conversion_state('listoffloats', 'formatted_listoffloats', |
| $listoffloats_name, $formatted_listoffloats_nr); |
| |
| if (defined($floats) and exists($floats->{$listoffloats_name}) |
| and scalar(@{$floats->{$listoffloats_name}})) { |
| my $result = $self->html_attribute_class('dl', [$cmdname]).">\n" ; |
| foreach my $float_and_section (@{$floats->{$listoffloats_name}}) { |
| my ($float, $float_section) = @$float_and_section; |
| my $float_href = $self->command_href($float); |
| next if (!defined($float_href)); |
| $result .= '<dt>'; |
| my $float_text = $self->command_text($float); |
| if (defined($float_text) and $float_text ne '') { |
| if ($float_href ne '') { |
| $result .= "<a href=\"$float_href\">$float_text</a>"; |
| } else { |
| $result .= $float_text; |
| } |
| } |
| $result .= '</dt>'; |
| my $caption_element; |
| my $caption_cmdname; |
| my ($caption, $shortcaption) |
| = Texinfo::Common::find_float_caption_shortcaption($float); |
| |
| if (defined($shortcaption)) { |
| $caption_element = $shortcaption; |
| $caption_cmdname = 'shortcaption'; |
| } elsif (defined($caption)) { |
| $caption_element = $caption; |
| $caption_cmdname = 'caption'; |
| } |
| |
| my $caption_text; |
| my @caption_classes; |
| if (defined($caption_element) and exists($caption_element->{'contents'})) { |
| my $multiple_formatted = 'listoffloats'; |
| if ($formatted_listoffloats_nr > 1) { |
| $multiple_formatted .= '-'.($formatted_listoffloats_nr - 1); |
| } |
| $caption_text = $self->convert_tree_new_formatting_context( |
| $caption_element->{'contents'}->[0], $cmdname, undef, |
| $multiple_formatted); |
| push @caption_classes, "${caption_cmdname}-in-${cmdname}"; |
| } else { |
| $caption_text = ''; |
| } |
| $result .= $self->html_attribute_class('dd', \@caption_classes).'>' |
| .$caption_text.'</dd>'."\n"; |
| } |
| return $result . "</dl>\n"; |
| } else { |
| return ''; |
| } |
| } |
| |
| $default_commands_conversion{'listoffloats'} = \&_convert_listoffloats_command; |
| |
| sub _convert_menu_command($$$$$) { |
| my ($self, $cmdname, $command, $args, $content) = @_; |
| |
| $content = '' if (!defined($content)); |
| |
| return $content if ($cmdname eq 'detailmenu'); |
| |
| $self->set_shared_conversion_state('menu', 'html_menu_entry_index', 0); |
| |
| if ($content !~ /\S/) { |
| return ''; |
| } |
| # This can probably only happen with incorrect input. It happens with |
| # menu in documentdescription. It does not seem that it could happen |
| # in other situation with a Texinfo tree parsed from Texinfo code. |
| if (in_string($self)) { |
| return $content; |
| } |
| |
| my $begin_row = ''; |
| my $end_row = ''; |
| if (inside_preformatted($self)) { |
| $begin_row = '<tr><td>'; |
| $end_row = '</td></tr>'; |
| } |
| return $self->html_attribute_class('table', [$cmdname]) |
| .">${begin_row}\n" . $content . "${end_row}</table>\n"; |
| } |
| |
| $default_commands_conversion{'menu'} = \&_convert_menu_command; |
| $default_commands_conversion{'detailmenu'} = \&_convert_menu_command; |
| |
| sub _convert_float_command($$$$$) { |
| my ($self, $cmdname, $command, $args, $content) = @_; |
| |
| $content = '' if (!defined($content)); |
| |
| my ($caption_element, $prepended) = $self->float_name_caption($command); |
| |
| if (in_string($self)) { |
| my $prepended_text; |
| if (defined($prepended)) { |
| $prepended_text |
| = $self->convert_tree_new_formatting_context($prepended, |
| 'float prepended'); |
| } else { |
| $prepended_text = ''; |
| } |
| my $caption_text = ''; |
| if (defined($caption_element) and exists($caption_element->{'contents'})) { |
| $caption_text = $self->convert_tree_new_formatting_context( |
| $caption_element->{'contents'}->[0], 'float caption'); |
| } |
| return $prepended_text.$content.$caption_text; |
| } |
| |
| my $caption_command_name; |
| if (defined($caption_element)) { |
| $caption_command_name = $caption_element->{'cmdname'}; |
| } |
| |
| my $result = $self->html_attribute_class('div', [$cmdname]); |
| |
| my $id = $self->command_id($command); |
| if (defined($id) and $id ne '') { |
| $result .= " id=\"$id\""; |
| } |
| |
| $result .= ">\n" . $content; |
| |
| my $prepended_text; |
| my $caption_text; |
| if (defined($prepended)) { |
| # TODO add a span with a class name for the prependend information |
| # if not empty? |
| $prepended_text = $self->convert_tree_new_formatting_context( |
| Texinfo::TreeElement::new({'cmdname' => 'strong', |
| 'contents' => [ |
| Texinfo::TreeElement::new({'type' => 'brace_container', |
| 'contents' => [$prepended]})]}), |
| 'float number type'); |
| if (defined($caption_element) and exists($caption_element->{'contents'})) { |
| # Allows an empty/spaces only caption. |
| # Register the converted prepended tree to be prepended to |
| # the first paragraph in caption formatting |
| $self->register_pending_formatted_inline_content($caption_command_name, |
| $prepended_text); |
| $caption_text = $self->convert_tree_new_formatting_context( |
| $caption_element->{'contents'}->[0], 'float caption'); |
| my $cancelled_prepended |
| = $self->cancel_pending_formatted_inline_content($caption_command_name); |
| # unset if prepended text is in caption, i.e. is not cancelled |
| $prepended_text = '' if (not defined($cancelled_prepended)); |
| } |
| if ($prepended_text ne '') { |
| # prepended text is not empty and did not find its way in caption |
| $prepended_text = '<p>'.$prepended_text.'</p>'; |
| } |
| } elsif (defined($caption_element)) { |
| $caption_text = $self->convert_tree_new_formatting_context( |
| $caption_element->{'contents'}->[0], 'float caption'); |
| } |
| |
| if (defined($caption_text) and $caption_text ne '') { |
| $result .= $self->html_attribute_class('div', [$caption_command_name]). '>' |
| .$caption_text.'</div>'; |
| } elsif (defined($prepended) and $prepended_text ne '') { |
| $result .= $self->html_attribute_class('div', ['type-number-float']). '>' |
| . $prepended_text .'</div>'; |
| } |
| |
| return $result . '</div>'; |
| } |
| |
| $default_commands_conversion{'float'} = \&_convert_float_command; |
| |
| sub _convert_quotation_command($$$$$) { |
| my ($self, $cmdname, $command, $args, $content) = @_; |
| |
| $content = '' if (!defined($content)); |
| |
| $self->cancel_pending_formatted_inline_content($cmdname); |
| |
| my $result; |
| if (!in_string($self)) { |
| my @classes; |
| |
| if (exists($small_block_associated_command{$cmdname})) { |
| push @classes, $small_block_associated_command{$cmdname}; |
| } |
| push @classes, $cmdname; |
| |
| $result = $self->html_attribute_class('blockquote', \@classes).">\n" |
| . $content . "</blockquote>\n"; |
| } else { |
| $result = $content; |
| } |
| |
| my $quotation_titlepage_nr = $self->get_shared_conversion_state('quotation', |
| 'quotation_titlepage_stack'); |
| my $quotation_authors = []; |
| if (defined($quotation_titlepage_nr) and $quotation_titlepage_nr > 0) { |
| my $authors_nr |
| = $self->get_shared_conversion_state('quotation', 'element_authors_number', |
| $quotation_titlepage_nr); |
| |
| if ($authors_nr < 0) { |
| print STDERR "BUG: unexpected negative element_authors_number" |
| ." $authors_nr in convert_quotation_command\n"; |
| $authors_nr = 0; |
| } |
| for (my $i = 0; $i < $authors_nr; $i++) { |
| my $author = $self->get_shared_conversion_state('quotation', |
| 'elements_authors', $quotation_titlepage_nr, $i); |
| |
| push @$quotation_authors, $author; |
| } |
| $quotation_titlepage_nr--; |
| $self->set_shared_conversion_state('quotation', |
| 'quotation_titlepage_stack', |
| $quotation_titlepage_nr); |
| } else { |
| print STDERR "BUG: unexpected unset quotation_titlepage_stack" |
| ."in convert_quotation_command\n"; |
| } |
| |
| # TODO there is no easy way to mark with a class the @author |
| # @-command. Add a span or a div (@center is in a div)? |
| foreach my $author (@$quotation_authors) { |
| if (exists($author->{'contents'}->[0]->{'contents'})) { |
| # TRANSLATORS: quotation author |
| my $centered_author = $self->cdt("\@center --- \@emph{{author}}", |
| {'author' => $author->{'contents'}->[0]}); |
| $result .= $self->convert_tree($centered_author, |
| 'convert quotation author'); |
| } |
| } |
| |
| return $result; |
| } |
| |
| $default_commands_conversion{'quotation'} = \&_convert_quotation_command; |
| |
| sub _convert_cartouche_command($$$$$) { |
| my ($self, $cmdname, $command, $args, $content) = @_; |
| |
| $content = '' if (!defined($content)); |
| |
| return $content if (in_string($self)); |
| |
| my $title_content = ''; |
| if (defined($args) and defined($args->[0]) |
| and $args->[0]->{'normal'} ne '') { |
| $title_content = "<tr><th>\n". $args->[0]->{'normal'} ."</th></tr>"; |
| } |
| my $cartouche_content = ''; |
| if ($content =~ /\S/) { |
| $cartouche_content = "<tr><td>\n". $content ."</td></tr>"; |
| } |
| if ($cartouche_content ne '' or $title_content ne '') { |
| return $self->html_attribute_class('table', [$cmdname]) |
| . ">${title_content}${cartouche_content}" |
| . "</table>\n"; |
| } |
| return $content; |
| } |
| |
| $default_commands_conversion{'cartouche'} = \&_convert_cartouche_command; |
| |
| sub _convert_itemize_command($$$$$) { |
| my ($self, $cmdname, $command, $args, $content) = @_; |
| |
| $content = '' if (!defined($content)); |
| |
| if (in_string($self)) { |
| return $content; |
| } |
| |
| # arguments_line type element |
| my $arguments_line = $command->{'contents'}->[0]; |
| my $block_line_arg = $arguments_line->{'contents'}->[0]; |
| |
| my $command_as_argument_name; |
| my $prepended_element |
| = Texinfo::Common::itemize_line_prepended_element($block_line_arg); |
| if (defined($prepended_element)) { |
| $command_as_argument_name = $prepended_element->{'cmdname'}; |
| } |
| |
| my $mark_class_name; |
| if (defined($command_as_argument_name)) { |
| if ($command_as_argument_name eq 'w') { |
| $mark_class_name = 'none'; |
| } else { |
| $mark_class_name = $command_as_argument_name; |
| } |
| } |
| |
| if (defined($mark_class_name) |
| and defined($self->css_get_selector_style('ul.mark-'.$mark_class_name))) { |
| return $self->html_attribute_class('ul', [$cmdname, |
| 'mark-'.$mark_class_name]) |
| .">\n" . $content. "</ul>\n"; |
| } elsif ($self->get_conf('NO_CSS')) { |
| return $self->html_attribute_class('ul', [$cmdname]) |
| .">\n" . $content. "</ul>\n"; |
| } else { |
| my $css_string |
| = $self->html_convert_css_string_for_list_mark($block_line_arg, |
| 'itemize arg'); |
| if ($css_string ne '') { |
| return $self->html_attribute_class('ul', [$cmdname]) |
| ." style=\"list-style-type: '". |
| &{$self->formatting_function('format_protect_text')}($self, |
| $css_string) |
| . "'\">\n" . $content. "</ul>\n"; |
| } else { |
| return $self->html_attribute_class('ul', [$cmdname]) |
| .">\n" . $content. "</ul>\n"; |
| } |
| } |
| } |
| |
| $default_commands_conversion{'itemize'} = \&_convert_itemize_command; |
| |
| sub _convert_enumerate_command($$$$$) { |
| my ($self, $cmdname, $command, $args, $content) = @_; |
| |
| if (!defined($content) or $content eq '') { |
| return ''; |
| } elsif (in_string($self)) { |
| return $content; |
| } |
| |
| my $type_attribute = ''; |
| my $start_attribute = ''; |
| |
| my ($start, $type); |
| # arguments_line type element |
| my $arguments_line = $command->{'contents'}->[0]; |
| my $block_line_arg = $arguments_line->{'contents'}->[0]; |
| |
| my ($specification, $surplus) |
| = Texinfo::Common::simple_arg_text($block_line_arg); |
| if (defined($specification)) { |
| if ($specification =~ /^\d+$/ and $specification ne '1') { |
| $start = $specification; |
| } elsif ($specification =~ /^[A-Z]$/) { |
| $start = 1 + ord($specification) - ord('A'); |
| $type = 'A'; |
| } elsif ($specification =~ /^[a-z]$/) { |
| $start = 1 + ord($specification) - ord('a'); |
| $type = 'a'; |
| } |
| $type_attribute = " type=\"$type\"" if (defined($type)); |
| $start_attribute = " start=\"$start\"" if (defined($start)); |
| } |
| |
| return $self->html_attribute_class('ol', [$cmdname]).$type_attribute |
| .$start_attribute.">\n" . $content . "</ol>\n"; |
| } |
| |
| $default_commands_conversion{'enumerate'} = \&_convert_enumerate_command; |
| |
| sub _convert_multitable_command($$$$$) { |
| my ($self, $cmdname, $command, $args, $content) = @_; |
| |
| if (!defined($content)) { |
| return ''; |
| } |
| |
| if (in_string($self)) { |
| return $content; |
| } |
| |
| if ($content ne '') { |
| return $self->html_attribute_class('table', [$cmdname]).">\n" |
| . $content . "</table>\n"; |
| } else { |
| return ''; |
| } |
| } |
| |
| $default_commands_conversion{'multitable'} = \&_convert_multitable_command; |
| |
| sub _convert_xtable_command($$$$$) { |
| my ($self, $cmdname, $command, $args, $content) = @_; |
| |
| if (!defined($content)) { |
| return ''; |
| } |
| |
| if (in_string($self)) { |
| return $content; |
| } |
| |
| if ($content ne '') { |
| return $self->html_attribute_class('dl', [$cmdname]).">\n" |
| . $content . "</dl>\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, $cmdname, $command, $args, $content) = @_; |
| |
| $content = '' if (!defined($content)); |
| |
| if (in_string($self)) { |
| return $content; |
| } |
| if (exists($command->{'parent'}->{'cmdname'}) |
| and $command->{'parent'}->{'cmdname'} eq 'itemize') { |
| if ($content =~ /\S/) { |
| return '<li>' . $content . '</li>'; |
| } else { |
| return ''; |
| } |
| } elsif (exists($command->{'parent'}->{'cmdname'}) |
| and $command->{'parent'}->{'cmdname'} eq 'enumerate') { |
| if ($content =~ /\S/) { |
| return '<li>' . ' ' . $content . '</li>'; |
| } else { |
| return ''; |
| } |
| } elsif (exists($command->{'contents'}) |
| and exists($command->{'contents'}->[0]->{'type'}) |
| and $command->{'contents'}->[0]->{'type'} eq 'line_arg') { |
| if (!Texinfo::Common::empty_spaces_argument($command->{'contents'}->[0])) { |
| |
| my $result = ($cmdname eq 'item') ? '' : '<dt>'; |
| |
| my $index_entry_id = $self->command_id($command); |
| my $anchor; |
| if (defined($index_entry_id)) { |
| $result .= "<a id=\"$index_entry_id\"></a>"; |
| $anchor = _get_copiable_anchor($self, $index_entry_id); |
| if (defined($anchor)) { |
| $result .= '<span>'; |
| } |
| } |
| |
| my $pre_class_close; |
| if (in_preformatted_context($self)) { |
| my $pre_classes = $self->preformatted_classes_stack(); |
| foreach my $pre_class (@$pre_classes) { |
| if (exists($preformatted_code_commands{$pre_class})) { |
| $result .= $self->html_attribute_class('code', |
| ['table-term-preformatted-code']).'>'; |
| $pre_class_close = '</code>'; |
| last; |
| } |
| } |
| } |
| my $table_item_tree = $self->table_item_content_tree($command); |
| $table_item_tree = $command->{'contents'}->[0] |
| if (!defined($table_item_tree)); |
| my $converted_item = $self->convert_tree($table_item_tree, |
| 'convert table_item_tree'); |
| $result .= $converted_item; |
| if (defined($pre_class_close)) { |
| $result .= $pre_class_close; |
| } |
| if (defined($anchor)) { |
| $result .= $anchor . '</span>'; |
| } |
| return $result . "</dt>\n"; |
| } else { |
| return ''; |
| } |
| } elsif ($command->{'parent'}->{'type'} |
| and $command->{'parent'}->{'type'} eq 'row') { |
| return &{$self->command_conversion('tab')}($self, $cmdname, $command, |
| $args, $content); |
| } |
| return ''; |
| } |
| |
| $default_commands_conversion{'item'} = \&_convert_item_command; |
| $default_commands_conversion{'headitem'} = \&_convert_item_command; |
| $default_commands_conversion{'itemx'} = \&_convert_item_command; |
| |
| sub _convert_tab_command($$$$$) { |
| my ($self, $cmdname, $command, $args, $content) = @_; |
| |
| $content = '' if (!defined($content)); |
| |
| $content =~ s/^\s*//; |
| $content =~ s/\s*$//; |
| |
| if (in_string($self)) { |
| return $content; |
| } |
| |
| my $cell_nr = $command->{'extra'}->{'cell_number'}; |
| my $row = $command->{'parent'}; |
| my $row_cmdname = $row->{'contents'}->[0]->{'cmdname'}; |
| my $multitable = $row->{'parent'}->{'parent'}; |
| my $columnfractions |
| = Texinfo::Common::multitable_columnfractions($multitable); |
| |
| my $fractions = ''; |
| if (defined($columnfractions)) { |
| if (exists($columnfractions->{'extra'}->{'misc_args'}->[$cell_nr-1])) { |
| my $percent = sprintf('%.0f', |
| 100. * $columnfractions->{'extra'}->{'misc_args'}->[$cell_nr-1]); |
| my $width = "$percent%"; |
| if ($self->get_conf('_INLINE_STYLE_WIDTH')) { |
| $fractions = " style=\"width: $width\""; |
| } else { |
| $fractions = " width=\"$width\""; |
| } |
| } |
| } |
| |
| if ($row_cmdname eq 'headitem') { |
| return "<th${fractions}>" . $content . '</th>'; |
| } else { |
| return "<td${fractions}>" . $content . '</td>'; |
| } |
| } |
| |
| $default_commands_conversion{'tab'} = \&_convert_tab_command; |
| |
| sub _convert_xref_command($$$$) { |
| my ($self, $cmdname, $command, $args) = @_; |
| |
| # may happen with bogus @-commands without argument, maybe only |
| # at the end of a document |
| if (!defined($args)) { |
| return ''; |
| } |
| |
| my $tree; |
| my $name; |
| if ($cmdname ne 'link' and $cmdname ne 'inforef' |
| and defined($args->[2]) |
| and defined($args->[2]->{'normal'}) and $args->[2]->{'normal'} ne '') { |
| $name = $args->[2]->{'normal'}; |
| } elsif (defined($args->[1]) |
| and defined($args->[1]->{'normal'}) and $args->[1]->{'normal'} ne '') { |
| $name = $args->[1]->{'normal'} |
| } |
| |
| my $file_arg; |
| |
| if ($cmdname eq 'link' or $cmdname eq 'inforef') { |
| if (defined($args->[2])) { |
| $file_arg = $args->[2]; |
| } |
| } elsif (defined($args->[3])) { |
| $file_arg = $args->[3]; |
| } |
| |
| my $file; |
| if (defined($file_arg) |
| and defined($file_arg->{'filenametext'}) |
| and $file_arg->{'filenametext'} ne '') { |
| $file = $file_arg->{'filenametext'}; |
| } |
| |
| my $book; |
| $book = $args->[4]->{'normal'} |
| if (defined($args->[4]) and exists($args->[4]->{'normal'}) |
| and $args->[4]->{'normal'} ne ''); |
| |
| my $arg_node = $command->{'contents'}->[0]; |
| |
| # internal reference |
| if ($cmdname ne 'inforef' and !defined($book) and !defined($file) |
| and defined($arg_node) and exists($arg_node->{'extra'}) |
| and exists($arg_node->{'extra'}->{'normalized'}) |
| and !exists($arg_node->{'extra'}->{'manual_content'}) |
| and $self->converter_find_identifier_target( |
| $arg_node->{'extra'}->{'normalized'})) { |
| my $target_node |
| = $self->converter_find_identifier_target( |
| $arg_node->{'extra'}->{'normalized'}); |
| # This is the node if USE_NODES, otherwise this may be the sectioning |
| # command (if the sectioning command is really associated to the node) |
| my $target_root = $self->command_root_element_command($target_node); |
| my $document = $self->get_info('document'); |
| |
| my $associated_section_relations; |
| my $associated_title_command; |
| if (defined($document) and $target_node->{'cmdname'} eq 'node') { |
| my $nodes_list = $document->nodes_list(); |
| my $node_relations |
| = $nodes_list->[$target_node->{'extra'}->{'node_number'} -1]; |
| |
| $associated_section_relations = $node_relations->{'associated_section'}; |
| $associated_title_command |
| = $node_relations->{'associated_title_command'}; |
| } |
| if (!defined($associated_section_relations) |
| or $associated_section_relations->{'element'} ne $target_root) { |
| $target_root = $target_node; |
| } |
| |
| my $href; |
| if (!in_string($self)) { |
| $href = $self->command_href($target_root, undef, $command); |
| } |
| |
| if (!defined($name)) { |
| if ($self->get_conf('xrefautomaticsectiontitle') eq 'on' |
| and defined($associated_title_command) |
| # this condition avoids infinite recursions, indeed in that case |
| # the node will be used and not the section. There should not be |
| # @*ref in nodes, and even if there are, it does not seems to be |
| # possible to construct an infinite recursion with nodes only |
| # as the node must both be a reference target and refer to a specific |
| # target at the same time, which is not possible. |
| and not command_is_in_referred_command_stack($self, |
| $associated_title_command)) { |
| if (in_string($self)) { |
| $name = $self->command_text($associated_title_command, 'string'); |
| } else { |
| $name = $self->command_text($associated_title_command, |
| 'text_nonumber'); |
| } |
| } elsif ($target_node->{'cmdname'} eq 'float') { |
| if (!$self->get_conf('XREF_USE_FLOAT_LABEL')) { |
| if (in_string($self)) { |
| # not tested |
| $name = $self->command_text($target_root, 'string'); |
| } else { |
| $name = $self->command_text($target_root); |
| } |
| } |
| 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 !in_preformatted_context($self)) |
| # this condition avoids infinite recursions, example with |
| # USE_NODES=0 and node referring to the section and section referring |
| # to the node |
| and not command_is_in_referred_command_stack($self, |
| $target_root)) { |
| if ($self->get_conf('xrefautomaticsectiontitle') eq 'on') { |
| if (in_string($self)) { |
| $name = $self->command_name($target_root, 'string'); |
| } else { |
| $name = $self->command_name($target_root, 'text_nonumber'); |
| } |
| } elsif (in_string($self)) { |
| $name = $self->command_text($target_root, 'string'); |
| } else { |
| $name = $self->command_text($target_root, 'text_nonumber'); |
| } |
| #die "$target_root $target_root->{'normalized'}" if (!defined($name)); |
| } elsif (defined($args->[0]->{'monospace'})) { |
| $name = $args->[0]->{'monospace'}; |
| } else { |
| $name = ''; |
| } |
| } |
| my $reference = $name; |
| if (defined($href)) { |
| $reference = $self->html_attribute_class('a', [$cmdname]) |
| ." href=\"$href\">$name</a>"; |
| } |
| my $substrings |
| = { 'reference_name' |
| => Texinfo::TreeElement::new({'type' => '_converted', |
| 'text' => $reference}) }; |
| |
| if ($cmdname eq 'pxref') { |
| $tree = $self->cdt('see {reference_name}', $substrings); |
| } elsif ($cmdname eq 'xref') { |
| $tree = $self->cdt('See {reference_name}', $substrings); |
| } elsif ($cmdname eq 'ref' or $cmdname eq 'link') { |
| $tree = $self->cdt('{reference_name}', $substrings); |
| } |
| } else { |
| # external reference, including unknown node without file nor book |
| |
| # We setup a label_element based on the node argument and not directly the |
| # node argument to be able to use the $file argument |
| my $label_element; |
| my $node_content; |
| if (defined($arg_node) and exists($arg_node->{'extra'}) |
| and exists($arg_node->{'extra'}->{'node_content'})) { |
| $node_content = $arg_node->{'extra'}->{'node_content'}; |
| $label_element = Texinfo::TreeElement::new( |
| {'extra' => {'node_content' => $node_content}}); |
| if (exists($arg_node->{'extra'}->{'normalized'})) { |
| $label_element->{'extra'}->{'normalized'} |
| = $arg_node->{'extra'}->{'normalized'}; |
| } |
| } |
| # file argument takes precedence over the file in the node (file)node entry |
| if (defined($file)) { |
| if (!$label_element) { |
| $label_element = Texinfo::TreeElement::new({'extra' => {}}); |
| } elsif (!exists($label_element->{'extra'})) { |
| $label_element->{'extra'} = {}; |
| } |
| $label_element->{'extra'}->{'manual_content'} = $file_arg->{'arg_tree'}; |
| } elsif (defined($arg_node) and exists($arg_node->{'extra'}) |
| and exists($arg_node->{'extra'}->{'manual_content'})) { |
| my $manual_content = $arg_node->{'extra'}->{'manual_content'}; |
| if (!defined($label_element)) { |
| $label_element = Texinfo::TreeElement::new({'extra' => {}}); |
| } elsif (!exists($label_element->{'extra'})) { |
| $label_element->{'extra'} = {}; |
| } |
| $label_element->{'extra'}->{'manual_content'} = $manual_content; |
| $file = $self->set_context_convert_tree($manual_content, $CTXF_code, |
| 'node file in ref'); |
| } |
| |
| if (!defined($name)) { |
| if (defined($book)) { |
| if (defined($node_content)) { |
| my $node_name = $self->set_context_convert_tree($node_content, |
| $CTXF_code, 'node in ref'); |
| if (defined($node_name) and $node_name ne 'Top') { |
| $name = $node_name; |
| } |
| } |
| } else { |
| if (defined($label_element)) { |
| $name = $self->command_text($label_element); |
| } |
| if (!defined($name) |
| and defined($args->[0]) |
| and defined($args->[0]->{'monospace'}) |
| and $args->[0]->{'monospace'} ne '' |
| and $args->[0]->{'monospace'} ne 'Top') { |
| # unknown node (and no book nor file) or @inforef without file |
| $name = $args->[0]->{'monospace'}; |
| } |
| } |
| } |
| |
| my $href; |
| if (defined($label_element) and !in_string($self)) { |
| $href = $self->command_href($label_element, undef, $command); |
| } |
| |
| my $reference; |
| my $book_reference; |
| if (defined($href)) { |
| # attribute to distiguish links to Texinfo manuals from other links |
| # and to provide manual name of target |
| my $manual_name_attribute = ''; |
| if (defined($file) |
| and not $self->get_conf('NO_CUSTOM_HTML_ATTRIBUTE')) { |
| $manual_name_attribute = "data-manual=\"". |
| &{$self->formatting_function('format_protect_text')}($self, $file)."\" "; |
| } |
| if (defined($name)) { |
| $reference = "<a ${manual_name_attribute}href=\"$href\">$name</a>"; |
| } elsif (defined($book)) { |
| $book_reference = "<a ${manual_name_attribute}href=\"$href\">$book</a>"; |
| } |
| } |
| my $substrings; |
| if (defined($book) and defined($reference)) { |
| $substrings = {'reference' => |
| Texinfo::TreeElement::new({'type' => '_converted', 'text' => $reference}), |
| 'book' => |
| Texinfo::TreeElement::new({'type' => '_converted', 'text' => $book })}; |
| if ($cmdname eq 'pxref') { |
| $tree = $self->cdt('see {reference} in @cite{{book}}', $substrings); |
| } elsif ($cmdname eq 'xref' or $cmdname eq 'inforef') { |
| $tree = $self->cdt('See {reference} in @cite{{book}}', $substrings); |
| } else { # @ref |
| $tree = $self->cdt('{reference} in @cite{{book}}', $substrings); |
| } |
| } elsif (defined($book_reference)) { |
| $substrings = { 'book_reference' => |
| Texinfo::TreeElement::new({'type' => '_converted', |
| 'text' => $book_reference })}; |
| if ($cmdname eq 'pxref') { |
| $tree = $self->cdt('see @cite{{book_reference}}', $substrings); |
| } elsif ($cmdname eq 'xref' or $cmdname eq 'inforef') { |
| $tree = $self->cdt('See @cite{{book_reference}}', $substrings); |
| } else { # @ref |
| $tree = $self->cdt('@cite{{book_reference}}', $substrings); |
| } |
| } elsif (defined($book) and defined($name)) { |
| $substrings = { |
| 'section' => |
| Texinfo::TreeElement::new({'type' => '_converted', 'text' => $name}), |
| 'book' => |
| Texinfo::TreeElement::new({'type' => '_converted', 'text' => $book })}; |
| if ($cmdname eq 'pxref') { |
| $tree = $self->cdt('see `{section}\' in @cite{{book}}', $substrings); |
| } elsif ($cmdname eq 'xref' or $cmdname eq 'inforef') { |
| $tree = $self->cdt('See `{section}\' in @cite{{book}}', $substrings); |
| } else { # @ref |
| $tree = $self->cdt('`{section}\' in @cite{{book}}', $substrings); |
| } |
| } elsif (defined($book)) { # should seldom or even never happen |
| $substrings = {'book' => |
| Texinfo::TreeElement::new({'type' => '_converted', 'text' => $book })}; |
| if ($cmdname eq 'pxref') { |
| $tree = $self->cdt('see @cite{{book}}', $substrings); |
| } elsif ($cmdname eq 'xref' or $cmdname eq 'inforef') { |
| $tree = $self->cdt('See @cite{{book}}', $substrings); |
| } else { # @ref |
| $tree = $self->cdt('@cite{{book}}', $substrings); |
| } |
| } elsif (defined($reference)) { |
| $substrings = { 'reference' => |
| Texinfo::TreeElement::new({'type' => '_converted', |
| 'text' => $reference}) }; |
| if ($cmdname eq 'pxref') { |
| $tree = $self->cdt('see {reference}', $substrings); |
| } elsif ($cmdname eq 'xref' or $cmdname eq 'inforef') { |
| $tree = $self->cdt('See {reference}', $substrings); |
| } else { # @ref |
| $tree = $self->cdt('{reference}', $substrings); |
| } |
| } elsif (defined($name)) { |
| $substrings = { 'section' => |
| Texinfo::TreeElement::new({'type' => '_converted', 'text' => $name}) }; |
| if ($cmdname eq 'pxref') { |
| $tree = $self->cdt('see `{section}\'', $substrings); |
| } elsif ($cmdname eq 'xref' or $cmdname eq 'inforef') { |
| $tree = $self->cdt('See `{section}\'', $substrings); |
| } else { # @ref |
| $tree = $self->cdt('`{section}\'', $substrings); |
| } |
| } |
| |
| if (!defined($tree)) { |
| # May happen if there is no argument |
| #die "external: $cmdname, ($args), '$name' '$file' '$book' '$href' '$reference'. tree undef"; |
| return ''; |
| } |
| } |
| return $self->convert_tree($tree, "convert xref $cmdname"); |
| } |
| |
| foreach my $cmdname(keys(%ref_commands)) { |
| $default_commands_conversion{$cmdname} = \&_convert_xref_command; |
| } |
| |
| sub _convert_printindex_command($$$$) { |
| my ($self, $cmdname, $command, $args, $content) = @_; |
| |
| return '' if (in_string($self)); |
| |
| my $index_name; |
| if (exists($command->{'extra'}) |
| and exists($command->{'extra'}->{'misc_args'}) |
| and defined($command->{'extra'}->{'misc_args'}->[0])) { |
| $index_name = $command->{'extra'}->{'misc_args'}->[0]; |
| } else { |
| return ''; |
| } |
| my $index_entries_by_letter |
| = $self->get_converter_indices_sorted_by_letter(); |
| if (!defined($index_entries_by_letter) |
| or !exists($index_entries_by_letter->{$index_name}) |
| or !scalar(@{$index_entries_by_letter->{$index_name}})) { |
| return ''; |
| } |
| |
| my $document = $self->get_info('document'); |
| my $indices_information; |
| if (defined($document)) { |
| $indices_information = $document->indices_information(); |
| } |
| |
| #foreach my $letter_entry (@{$index_entries_by_letter->{$index_name}}) { |
| # print STDERR "IDXLETTER $letter_entry->{'letter'}\n"; |
| # foreach my $index_entry (@{$letter_entry->{'entries'}}) { |
| # print STDERR " ".join('|', keys(%$index_entry))."||| $index_entry->{'key'}\n"; |
| # } |
| #} |
| my $index_element_id; |
| my $current_output_unit = $self->current_output_unit(); |
| if (defined($current_output_unit) |
| and exists($current_output_unit->{'unit_command'})) { |
| $index_element_id |
| = $self->command_id($current_output_unit->{'unit_command'}); |
| } |
| if (!defined($index_element_id)) { |
| my ($output_unit, $root_command) |
| = $self->get_element_root_command_element($command); |
| if (defined($root_command)) { |
| $index_element_id = $self->command_id($root_command); |
| } |
| if (not defined($index_element_id)) { |
| # to avoid duplicate names, use a prefix that cannot happen in anchors |
| my $target_prefix = 't_i'; |
| $index_element_id = $target_prefix; |
| } |
| } |
| |
| my %letter_id; |
| my %letter_is_symbol; |
| # First collect the links that are used in entries and in letter summaries |
| my $symbol_idx = 0; |
| my $normalized_letter_idx = 0; |
| |
| foreach my $letter_entry (@{$index_entries_by_letter->{$index_name}}) { |
| my $letter = $letter_entry->{'letter'}; |
| my $is_symbol = $letter !~ /^\p{Alpha}/; |
| $letter_is_symbol{$letter} = $is_symbol; |
| my $identifier; |
| if ($is_symbol) { |
| $symbol_idx++; |
| $identifier = $index_element_id . "_${index_name}_symbol-$symbol_idx"; |
| } else { |
| my $normalized_letter = |
| Texinfo::Convert::NodeNameNormalization::convert_to_identifier( |
| Texinfo::TreeElement::new({'text' => $letter})); |
| $identifier = $index_element_id |
| . "_${index_name}_letter-${normalized_letter}"; |
| } |
| $letter_id{$letter} = $identifier; |
| |
| } |
| |
| my $rule = $self->get_conf('DEFAULT_RULE'); |
| $rule = '' if (!defined($rule)); |
| |
| my %formatted_letters; |
| # Next do the entries to determine the letters that are not empty |
| my @letter_entries; |
| my $result_index_entries = ''; |
| foreach my $letter_entry (@{$index_entries_by_letter->{$index_name}}) { |
| my $letter = $letter_entry->{'letter'}; |
| my $entries_text = ''; |
| my $entry_nr = -1; |
| my $first_entry; |
| # since we normalize, a different formatting will not trigger a new |
| # formatting of the main entry or a subentry level. This is the |
| # same for Texinfo TeX |
| my @prev_normalized_entry_levels; |
| foreach my $index_entry_ref (@{$letter_entry->{'entries'}}) { |
| $entry_nr++; |
| my $main_entry_element = $index_entry_ref->{'entry_element'}; |
| next if ($self->get_conf('NO_TOP_NODE_OUTPUT') |
| and exists($main_entry_element->{'extra'}->{'element_node'}) |
| and $main_entry_element->{'extra'}->{'element_node'} eq 'Top'); |
| |
| # to avoid double error messages, call |
| # convert_tree_new_formatting_context below with a multiple_pass |
| # argument if an entry was already formatted once, for example if |
| # there are multiple printindex. |
| my $formatted_index_entry_nr |
| = $self->get_shared_conversion_state('printindex', |
| 'formatted_index_entries', |
| $index_entry_ref); |
| $formatted_index_entry_nr = 0 if (!defined($formatted_index_entry_nr)); |
| $formatted_index_entry_nr++; |
| $self->set_shared_conversion_state('printindex', |
| 'formatted_index_entries', |
| $index_entry_ref, $formatted_index_entry_nr); |
| |
| my $entry_content_element |
| = Texinfo::Indices::converter_index_content_element( |
| $main_entry_element, $self, 0); |
| |
| my $entry_index_name = $index_entry_ref->{'index_name'}; |
| my $in_code = 0; |
| $in_code = 1 |
| if ($indices_information->{$entry_index_name}->{'in_code'}); |
| my $entry_ref_tree |
| = Texinfo::TreeElement::new({'contents' => [$entry_content_element]}); |
| |
| |
| # determine the trees and normalized main entry and subentries, to be |
| # compared with the previous line normalized entries to determine |
| # what is already formatted as part of the previous lines and |
| # what levels should be added. The last level is always formatted. |
| my @new_normalized_entry_levels; |
| my @entry_trees; |
| # NOTE it seems that subentry is not followed in convert_to_normalized |
| $new_normalized_entry_levels[0] |
| = uc(Texinfo::Convert::NodeNameNormalization::convert_to_normalized( |
| $entry_ref_tree)); |
| $entry_trees[0] = $entry_ref_tree; |
| my $subentry_level = 1; |
| my $subentries_max_level = 2; |
| my @subentries_list; |
| Texinfo::Common::collect_subentries($main_entry_element, |
| \@subentries_list); |
| if (scalar(@subentries_list)) { |
| foreach my $subentry (@subentries_list) { |
| my $subentry_tree; |
| my $line_arg = $subentry->{'contents'}->[0]; |
| if (exists($line_arg->{'contents'}) |
| and scalar(@{$line_arg->{'contents'}})) { |
| my @contents; |
| foreach my $content (@{$line_arg->{'contents'}}) { |
| push @contents, $content unless (exists($content->{'cmdname'}) |
| and $content->{'cmdname'} eq 'subentry'); |
| } |
| $subentry_tree |
| = Texinfo::TreeElement::new({'contents' => \@contents}); |
| } |
| if ($subentry_level >= $subentries_max_level) { |
| # at the max, concatenate the remaining subentries |
| my $other_subentries_tree |
| = Texinfo::Convert::Utils::comma_index_subentries_tree($subentry); |
| if (defined($other_subentries_tree)) { |
| if (defined($subentry_tree)) { |
| push @{$subentry_tree->{'contents'}}, |
| @{$other_subentries_tree->{'contents'}}; |
| } else { |
| $subentry_tree = Texinfo::TreeElement::new( |
| {'contents' => [@{$other_subentries_tree->{'contents'}}]}); |
| } |
| } |
| } elsif (defined($subentry_tree)) { |
| push @new_normalized_entry_levels, |
| uc(Texinfo::Convert::NodeNameNormalization::convert_to_normalized( |
| $subentry_tree)); |
| } |
| push @entry_trees, $subentry_tree; |
| $subentry_level++; |
| last if ($subentry_level > $subentries_max_level); |
| } |
| } |
| my $formatting_context; |
| if ($in_code) { |
| $formatting_context = $CTXF_code; |
| } |
| #print STDERR join('|', @new_normalized_entry_levels)."\n"; |
| # level/index of the last entry |
| my $last_entry_level = $subentry_level -1; |
| my $with_new_formatted_entry = 0; |
| # format the leading entries when there are subentries (all entries |
| # except the last one), and when there is not such a subentry already |
| # formatted on the previous lines. |
| # Each on a line with increasing indentation, no hyperlink. |
| for (my $level = 0; $level < $last_entry_level; $level++) { |
| # skip levels already formatted as part of the previous lines |
| if (!$with_new_formatted_entry |
| and scalar(@prev_normalized_entry_levels) > $level |
| and $prev_normalized_entry_levels[$level] |
| eq $new_normalized_entry_levels[$level]) { |
| next; |
| } |
| $with_new_formatted_entry = 1; |
| my $convert_info |
| = "index $index_name l $letter index entry $entry_nr subentry $level"; |
| my $entry; |
| if ($formatted_index_entry_nr > 1) { |
| # call with multiple_pass argument |
| $entry |
| = $self->convert_tree_new_formatting_context($entry_trees[$level], |
| $convert_info, $formatting_context, |
| "index-formatted-$formatted_index_entry_nr"); |
| } else { |
| if ($in_code) { |
| $entry = $self->set_context_convert_tree($entry_trees[$level], |
| $CTXF_code, $convert_info); |
| } else { |
| $entry = $self->convert_tree($entry_trees[$level], |
| $convert_info); |
| } |
| } |
| $entry = '<code>' .$entry .'</code>' if ($in_code); |
| my @td_entry_classes = (); |
| if ($level == 0) { |
| push @td_entry_classes, "$cmdname-index-entry"; |
| } elsif ($level > 0) { |
| # indent |
| push @td_entry_classes, "$cmdname-index-subentry-level-$level"; |
| } |
| $entries_text .= '<tr>' |
| # TODO same class used for leading entry rows here and |
| # last element of the entry with the href below. Could be different. |
| .$self->html_attribute_class('td', \@td_entry_classes).'>' |
| . $entry . '</td>' |
| # empty cell, no section for this line |
| . "<td></td></tr>\n"; |
| } |
| # last entry, always converted, associated to chapter/node and |
| # with an hyperlink or to seeentry/seealso |
| my $entry_tree = $entry_trees[$last_entry_level]; |
| |
| my $referred_entry; |
| my $seeentry |
| = Texinfo::Common::index_entry_referred_entry($main_entry_element, |
| 'seeentry'); |
| if (defined($seeentry)) { |
| $referred_entry = $seeentry; |
| } else { |
| $referred_entry |
| = Texinfo::Common::index_entry_referred_entry($main_entry_element, |
| 'seealso'); |
| } |
| |
| # index entry with @seeentry or @seealso |
| if (defined($referred_entry)) { |
| my $entry; |
| # for @seealso, to appear where chapter/node ususally appear |
| my $reference = ''; |
| my $delimiter = ''; |
| my $section_class; |
| if (defined($seeentry)) { |
| my $result_tree; |
| if ($in_code) { |
| $result_tree |
| # TRANSLATORS: redirect to another index entry |
| # TRANSLATORS: @: is discardable and is used to avoid a msgfmt error |
| = $self->cdt('@code{{main_index_entry}}, @emph{See@:} @code{{seeentry}}', |
| {'main_index_entry' => $entry_tree, |
| 'seeentry' => $referred_entry}); |
| } else { |
| $result_tree |
| # TRANSLATORS: redirect to another index entry |
| # TRANSLATORS: @: is discardable and used to avoid a msgfmt error |
| = $self->cdt('{main_index_entry}, @emph{See@:} {seeentry}', |
| {'main_index_entry' => $entry_tree, |
| 'seeentry' => $referred_entry}); |
| } |
| my $convert_info |
| = "index $index_name l $letter index entry $entry_nr seeentry"; |
| if ($formatted_index_entry_nr > 1) { |
| # call with multiple_pass argument |
| $entry = $self->convert_tree_new_formatting_context($result_tree, |
| $convert_info, undef, |
| "index-formatted-$formatted_index_entry_nr"); |
| } else { |
| $entry = $self->convert_tree($result_tree, $convert_info); |
| } |
| $section_class = "$cmdname-index-see-entry-section"; |
| } else { |
| my $reference_tree; |
| if ($in_code) { |
| # TRANSLATORS: refer to another index entry |
| $reference_tree |
| = $self->cdt('@emph{See also} @code{{see_also_entry}}', |
| {'see_also_entry' => $referred_entry}); |
| } else { |
| # TRANSLATORS: refer to another index entry |
| $reference_tree |
| = $self->cdt('@emph{See also} {see_also_entry}', |
| {'see_also_entry' => $referred_entry}); |
| } |
| my $conv_str_entry |
| = "index $index_name l $letter index entry $entry_nr (with seealso)"; |
| my $conv_str_reference |
| = "index $index_name l $letter index entry $entry_nr seealso"; |
| if ($formatted_index_entry_nr > 1) { |
| # call with multiple_pass argument |
| $entry = $self->convert_tree_new_formatting_context($entry_tree, |
| $conv_str_entry, $formatting_context, |
| "index-formatted-$formatted_index_entry_nr"); |
| $reference |
| = $self->convert_tree_new_formatting_context($reference_tree, |
| $conv_str_reference, undef, |
| "index-formatted-$formatted_index_entry_nr"); |
| } else { |
| if ($in_code) { |
| $entry = $self->set_context_convert_tree($entry_tree, |
| $CTXF_code, $conv_str_entry); |
| } else { |
| $entry = $self->convert_tree($entry_tree, |
| $conv_str_entry); |
| } |
| |
| $reference = $self->convert_tree($reference_tree, |
| $conv_str_reference); |
| } |
| $entry = '<code>' .$entry .'</code>' if ($in_code); |
| $delimiter = $self->get_conf('INDEX_ENTRY_COLON'); |
| $section_class = "$cmdname-index-see-also"; |
| } |
| |
| my @td_entry_classes = (); |
| if (defined($seeentry)) { |
| push @td_entry_classes, "$cmdname-index-see-entry"; |
| } |
| if ($last_entry_level == 0) { |
| push @td_entry_classes, "$cmdname-index-entry"; |
| } elsif ($last_entry_level > 0) { |
| push @td_entry_classes, |
| "$cmdname-index-subentry-level-$last_entry_level"; |
| } |
| $entries_text .= '<tr>' |
| .$self->html_attribute_class('td', \@td_entry_classes).'>' |
| . $entry . |
| $delimiter . '</td>' |
| .$self->html_attribute_class('td', [$section_class]).'>'; |
| $entries_text .= $reference; |
| $entries_text .= "</td></tr>\n"; |
| |
| @prev_normalized_entry_levels = @new_normalized_entry_levels; |
| } else { |
| my $entry; |
| if (!defined($entry_tree)) { |
| # can happen at least with an empty subentry |
| $entry = ''; |
| } else { |
| my $convert_info |
| = "index $index_name l $letter index entry $entry_nr"; |
| if ($formatted_index_entry_nr > 1) { |
| # call with multiple_pass argument |
| $entry = $self->convert_tree_new_formatting_context($entry_tree, |
| $convert_info, $formatting_context, |
| "index-formatted-$formatted_index_entry_nr"); |
| } else { |
| if ($in_code) { |
| $entry = $self->set_context_convert_tree($entry_tree, |
| $CTXF_code, $convert_info); |
| } else { |
| $entry = $self->convert_tree($entry_tree, $convert_info); |
| } |
| } |
| } |
| |
| next if ($entry !~ /\S/ and $last_entry_level == 0); |
| |
| if (!defined($first_entry)) { |
| $first_entry = $index_entry_ref; |
| } |
| |
| @prev_normalized_entry_levels = @new_normalized_entry_levels; |
| |
| $entry = '<code>' .$entry .'</code>' if ($in_code); |
| my $target_element; |
| if (exists($index_entry_ref->{'entry_associated_element'})) { |
| $target_element = $index_entry_ref->{'entry_associated_element'}; |
| } else { |
| $target_element = $main_entry_element; |
| } |
| my $entry_href = $self->command_href($target_element); |
| my $formatted_entry = "<a href=\"$entry_href\">$entry</a>"; |
| my @td_entry_classes = (); |
| if ($last_entry_level == 0) { |
| push @td_entry_classes, "$cmdname-index-entry"; |
| } elsif ($last_entry_level > 0) { |
| # subentry |
| push @td_entry_classes, "$cmdname-index-subentry-level-$last_entry_level"; |
| } |
| $entries_text .= '<tr>' |
| .$self->html_attribute_class('td', \@td_entry_classes).'>' |
| . $formatted_entry . $self->get_conf('INDEX_ENTRY_COLON') . '</td>'; |
| |
| my $associated_command; |
| if ($self->get_conf('NODE_NAME_IN_INDEX')) { |
| my $associated_command_id |
| = $main_entry_element->{'extra'}->{'element_node'}; |
| if (defined($associated_command_id)) { |
| $associated_command |
| = $self->converter_find_identifier_target( |
| $associated_command_id); |
| } |
| if (!defined($associated_command)) { |
| $associated_command |
| = $self->command_node($target_element); |
| } |
| if (!defined($associated_command) |
| # do not warn if the entry is in a special region, like titlepage |
| and not $main_entry_element->{'extra'}->{'element_region'} |
| and $formatted_index_entry_nr == 1) { |
| # NOTE $self->in_multiple_conversions() is not checked as printindex |
| # should not happen in multiple tree conversion, but the error message |
| # is printed for the first entry formatting only. |
| $self->converter_line_warn( |
| sprintf( |
| __("entry for index `%s' for \@printindex %s outside of any node"), |
| $index_entry_ref->{'index_name'}, |
| $index_name), |
| $main_entry_element->{'source_info'}); |
| } |
| } |
| if (!defined($associated_command)) { |
| $associated_command |
| = $self->command_root_element_command($target_element); |
| if (!defined($associated_command)) { |
| # Use Top if not associated command found |
| $associated_command |
| = $self->global_direction_unit('Top')->{'unit_command'}; |
| # NOTE the warning here catches the most relevant cases of |
| # index entry that is not associated to the right command, which |
| # are very few in the test suite. There is also a warning in the |
| # parser with a much broader scope with possible overlap, but the |
| # overlap is not a problem. |
| # NODE_NAME_IN_INDEX may be undef even with USE_NODES set if the |
| # converter is called as convert() as in the test suite |
| if (defined($self->get_conf('NODE_NAME_IN_INDEX')) |
| and not $self->get_conf('NODE_NAME_IN_INDEX') |
| # do not warn if the entry is in a special region, like titlepage |
| and not $main_entry_element->{'extra'}->{'element_region'} |
| and $formatted_index_entry_nr == 1) { |
| # NOTE $self->in_multiple_conversions() is not checked as printindex |
| # should not happen in multiple tree conversion, but the error message |
| # is printed for the first entry formatting only. |
| # NOTE the index entry may be associated to a node in that case. |
| $self->converter_line_warn( |
| sprintf( |
| __("entry for index `%s' for \@printindex %s outside of any section"), |
| $index_entry_ref->{'index_name'}, |
| $index_name), |
| $main_entry_element->{'source_info'}); |
| } |
| } |
| } |
| |
| $entries_text .= |
| $self->html_attribute_class('td', ["$cmdname-index-section"]).'>'; |
| |
| if (defined($associated_command)) { |
| my $associated_command_href |
| = $self->command_href($associated_command); |
| my $associated_command_text |
| = $self->command_text($associated_command); |
| |
| if (defined($associated_command_href)) { |
| $entries_text |
| .= "<a href=\"$associated_command_href\">" |
| ."$associated_command_text</a>"; |
| } elsif (defined($associated_command_text)) { |
| $entries_text .= $associated_command_text; |
| } |
| } |
| $entries_text .= "</td></tr>\n"; |
| } |
| } |
| # a letter and associated indice entries |
| if ($entries_text ne '') { |
| my $formatted_letter; |
| my $letter_command; |
| |
| # may not be defined if there are only seeentry/seealso |
| if (defined($first_entry)) { |
| my $letter_text; |
| ($letter_text, $letter_command) |
| = Texinfo::Indices::index_entry_first_letter_text_or_command( |
| $first_entry, $self); |
| } |
| |
| if (defined($letter_command) |
| and !(exists($brace_commands{$letter_command->{'cmdname'}}) |
| and $brace_commands{$letter_command->{'cmdname'}} eq 'accent') |
| and $letter_command->{'cmdname'} ne 'U' |
| # special case, the uppercasing of that command is not done |
| # if as a command, while it is done correctly in $letter |
| and $letter_command->{'cmdname'} ne 'ss') { |
| my $cmdname = $letter_command->{'cmdname'}; |
| if (exists($letter_no_arg_commands{$cmdname}) |
| and $cmdname ne uc($cmdname) |
| and exists($letter_no_arg_commands{uc($cmdname)})) { |
| $letter_command |
| = Texinfo::TreeElement::new({'cmdname' => uc($cmdname)}); |
| } |
| $formatted_letter = $self->convert_tree($letter_command, |
| "index letter $letter command"); |
| } else { |
| $formatted_letter |
| = &{$self->formatting_function('format_protect_text')}($self, $letter); |
| } |
| $formatted_letters{$letter} = $formatted_letter; |
| |
| $result_index_entries .= '<tr>' . |
| $self->html_attribute_class('th', ["index-letter-header-$cmdname", |
| "$index_name-letter-header-$cmdname"]) |
| ." colspan=\"2\" id=\"$letter_id{$letter}\">".$formatted_letter |
| . "</th></tr>\n" . $entries_text |
| . "<tr><td colspan=\"2\">${rule}</td></tr>\n"; |
| push @letter_entries, $letter_entry; |
| } |
| } |
| |
| # Do the summary letters linking to the letters done above |
| my @non_alpha = (); |
| my @alpha = (); |
| foreach my $letter_entry (@letter_entries) { |
| my $letter = $letter_entry->{'letter'}; |
| my $summary_letter_link |
| = $self->html_attribute_class('a',["summary-letter-$cmdname"]) |
| ." href=\"#$letter_id{$letter}\"><b>".$formatted_letters{$letter} |
| .'</b></a>'; |
| if ($letter_is_symbol{$letter}) { |
| push @non_alpha, $summary_letter_link; |
| } else { |
| push @alpha, $summary_letter_link; |
| } |
| } |
| |
| if (scalar(@non_alpha) + scalar(@alpha) == 0) { |
| return ''; |
| } |
| |
| my $non_breaking_space = $self->get_info('non_breaking_space'); |
| |
| # Format the summary letters |
| my $join = ''; |
| my $non_alpha_text = ''; |
| my $alpha_text = ''; |
| if (scalar(@non_alpha) + scalar(@alpha) > 1) { |
| $join = " $non_breaking_space \n".$self->get_info('line_break_element')."\n" |
| if (scalar(@non_alpha) and scalar(@alpha)); |
| if (scalar(@non_alpha)) { |
| $non_alpha_text = join("\n $non_breaking_space \n", @non_alpha) . "\n"; |
| } |
| if (scalar(@alpha)) { |
| $alpha_text = join("\n $non_breaking_space \n", @alpha) |
| . "\n $non_breaking_space \n"; |
| } |
| } |
| my $result = $self->html_attribute_class('div', |
| [$cmdname, "$index_name-$cmdname"]).">\n"; |
| # format the summary |
| if (scalar(@non_alpha) + scalar(@alpha) > 1) { |
| my $summary_header = $self->html_attribute_class('table', |
| ["index-letters-header-$cmdname", |
| "$index_name-letters-header-$cmdname"]).'><tr><th>' |
| # TRANSLATORS: before list of letters and symbols grouping index entries |
| . $self->convert_tree($self->cdt('Jump to'), 'Tr letters header text') |
| . ": $non_breaking_space </th><td>" . |
| $non_alpha_text . $join . $alpha_text . "</td></tr></table>\n"; |
| |
| $result .= $summary_header; |
| } |
| |
| # now format the index entries |
| $result |
| .= $self->html_attribute_class('table', ["index-entries-$cmdname", |
| "$index_name-entries-$cmdname"]).">\n"; |
| $result .= "<tr><td colspan=\"2\">${rule}</td></tr>\n"; |
| $result .= $result_index_entries; |
| $result .= "</table>\n"; |
| |
| if (scalar(@non_alpha) + scalar(@alpha) > 1) { |
| my $summary_footer = $self->html_attribute_class('table', |
| ["index-letters-footer-$cmdname", |
| "$index_name-letters-footer-$cmdname"]).'><tr><th>' |
| # TRANSLATORS: before list of letters and symbols grouping index entries |
| . $self->convert_tree($self->cdt('Jump to'), 'Tr letters footer text') |
| . ": $non_breaking_space </th><td>" |
| . $non_alpha_text . $join . $alpha_text . "</td></tr></table>\n"; |
| $result .= $summary_footer |
| } |
| return $result . "</div>\n"; |
| } |
| |
| $default_commands_conversion{'printindex'} = \&_convert_printindex_command; |
| |
| sub _convert_informative_command($$$) { |
| my ($self, $cmdname, $command) = @_; |
| |
| return '' if (in_string($self)); |
| |
| Texinfo::Common::set_informative_command_value($self, $command); |
| |
| return ''; |
| } |
| |
| foreach my $informative_command (@informative_global_commands) { |
| $default_commands_conversion{$informative_command} |
| = \&_convert_informative_command; |
| } |
| |
| sub _convert_documentlanguagevariant_command($$$) { |
| my ($self, $cmdname, $command) = @_; |
| |
| return ''; |
| } |
| |
| $default_commands_conversion{'documentlanguagevariant'} |
| = \&_convert_documentlanguagevariant_command; |
| |
| sub _convert_contents_command($$$) { |
| my ($self, $cmdname, $command) = @_; |
| |
| return '' if (in_string($self)); |
| $cmdname = 'shortcontents' if ($cmdname eq 'summarycontents'); |
| |
| Texinfo::Common::set_informative_command_value($self, $command); |
| |
| my $document = $self->get_info('document'); |
| my $sections_list; |
| if (defined($document)) { |
| $sections_list = $document->sections_list(); |
| } |
| |
| if ($self->get_conf('CONTENTS_OUTPUT_LOCATION') eq 'inline' |
| and ($cmdname eq 'contents' or $cmdname eq 'shortcontents') |
| and $self->get_conf($cmdname) |
| and defined($sections_list) |
| and scalar(@{$sections_list}) > 1) { |
| return _contents_inline_element($self, $cmdname, $command); |
| } |
| return ''; |
| } |
| |
| foreach my $contents_command (@contents_commands) { |
| $default_commands_conversion{$contents_command} = \&_convert_contents_command; |
| } |
| |
| sub _convert_def_command($$$$$) { |
| my ($self, $cmdname, $command, $args, $content) = @_; |
| |
| $content = '' if (!defined($content)); |
| |
| return $content if (in_string($self)); |
| |
| my @classes; |
| if ($cmdname ne 'defblock') { |
| # The def* class is used for the def line, the first-def* class is |
| # used for the whole block. |
| my $command_name; |
| if (exists($Texinfo::Common::def_aliases{$cmdname})) { |
| $command_name = $Texinfo::Common::def_aliases{$cmdname}; |
| push @classes, "first-$cmdname-alias-first-$command_name"; |
| } else { |
| $command_name = $cmdname; |
| } |
| unshift @classes, "first-$command_name"; |
| } else { |
| push @classes, $cmdname; |
| } |
| |
| push @classes, 'def-block'; |
| |
| if (!$self->get_conf('DEF_TABLE')) { |
| return $self->html_attribute_class('dl', \@classes).">\n" |
| . $content ."</dl>\n"; |
| } else { |
| return $self->html_attribute_class('table', \@classes).">\n" |
| . $content . "</table>\n"; |
| } |
| } |
| |
| # Keys are tree element types, values are function references to convert |
| # elements of that type. Can be overridden accessing |
| # Texinfo::Config::GNUT_get_types_conversion, setup by |
| # Texinfo::Config::texinfo_register_type_formatting() |
| my %default_types_conversion; |
| |
| foreach my $cmdname (keys(%def_commands), 'defblock') { |
| if (exists($line_commands{$cmdname})) { |
| $default_commands_conversion{$cmdname} = \&_convert_def_line_type; |
| } else { |
| $default_commands_conversion{$cmdname} = \&_convert_def_command; |
| } |
| } |
| |
| |
| # associate same formatting function for @small* command |
| # as for the associated @-command |
| foreach my $small_command (keys(%small_block_associated_command)) { |
| $default_commands_conversion{$small_command} |
| = $default_commands_conversion{$small_block_associated_command{$small_command}}; |
| } |
| |
| # Can be used to check that all the relevant commands are converted |
| if (0) { |
| foreach my $cmdname (keys(%Texinfo::Common::all_commands)) { |
| if (!exists($default_commands_conversion{$cmdname})) { |
| # should be @if* @*index and item_LINE |
| if ($cmdname =~ /^if/ or $cmdname =~ /index$/ or $cmdname eq 'item_LINE') {} |
| else |
| { |
| warn "MISSING $cmdname\n"; |
| } |
| } |
| } |
| } |
| |
| sub _open_node_part_command($$$) { |
| my ($self, $cmdname, $element) = @_; |
| |
| if ($self->get_conf('NO_TOP_NODE_OUTPUT')) { |
| my $in_skipped_node_top |
| = $self->get_shared_conversion_state('top', 'in_skipped_node_top'); |
| $in_skipped_node_top = 0 if (!defined($in_skipped_node_top)); |
| my $node_element; |
| if ($cmdname eq 'node') { |
| $node_element = $element; |
| } elsif ($cmdname eq 'part') { |
| my $document = $self->get_info('document'); |
| if (defined($document)) { |
| my $sections_list = $document->sections_list(); |
| my $part_relations |
| = $sections_list->[$element->{'extra'}->{'section_number'} -1]; |
| if (exists($part_relations->{'part_following_node'})) { |
| $node_element = $part_relations->{'part_following_node'}->{'element'}; |
| } |
| } |
| } |
| if (defined($node_element) or $cmdname eq 'part') { |
| if (defined($node_element) and exists($node_element->{'extra'}) |
| and exists($node_element->{'extra'}->{'identifier'}) |
| and $node_element->{'extra'}->{'identifier'} eq 'Top') { |
| $in_skipped_node_top = 1; |
| $self->set_shared_conversion_state('top', 'in_skipped_node_top', |
| $in_skipped_node_top); |
| } elsif ($in_skipped_node_top == 1) { |
| $in_skipped_node_top = -1; |
| $self->set_shared_conversion_state('top', 'in_skipped_node_top', |
| $in_skipped_node_top); |
| } |
| } |
| } |
| return ''; |
| } |
| |
| $default_commands_open{'node'} = \&_open_node_part_command; |
| $default_commands_open{'part'} = \&_open_node_part_command; |
| |
| sub _open_quotation_titlepage_stack($$) { |
| my ($self, $element_authors_number) = @_; |
| |
| my $quotation_titlepage_nr = $self->get_shared_conversion_state('quotation', |
| 'quotation_titlepage_stack'); |
| $quotation_titlepage_nr = 0 if (!defined($quotation_titlepage_nr)); |
| |
| $quotation_titlepage_nr++; |
| |
| $self->set_shared_conversion_state('quotation', 'quotation_titlepage_stack', |
| $quotation_titlepage_nr); |
| |
| |
| $self->set_shared_conversion_state('quotation', 'element_authors_number', |
| $quotation_titlepage_nr, $element_authors_number); |
| } |
| |
| sub _open_quotation_command($$$) { |
| my ($self, $cmdname, $command) = @_; |
| |
| my $formatted_quotation_arg_to_prepend; |
| # arguments_line type element |
| my $arguments_line = $command->{'contents'}->[0]; |
| my $block_line_args = $arguments_line->{'contents'}->[0]; |
| if (!Texinfo::Common::empty_spaces_argument($block_line_args)) { |
| $formatted_quotation_arg_to_prepend |
| = $self->convert_tree($self->cdt('@b{{quotation_arg}:} ', |
| {'quotation_arg' => $block_line_args}), |
| "open $cmdname prepended arg"); |
| } |
| $self->register_pending_formatted_inline_content($cmdname, |
| $formatted_quotation_arg_to_prepend); |
| _open_quotation_titlepage_stack($self, 0); |
| |
| return ''; |
| } |
| |
| $default_commands_open{'quotation'} = \&_open_quotation_command; |
| |
| # associate same opening function for @small* command |
| # as for the associated @-command |
| foreach my $small_command (keys(%small_block_associated_command)) { |
| if (exists($default_commands_open{$small_block_associated_command{$small_command}})) { |
| $default_commands_open{$small_command} |
| = $default_commands_open{$small_block_associated_command{$small_command}}; |
| } |
| } |
| |
| # Keys are output units types, values are function references to convert |
| # output units of that type. Can be overridden accessing |
| # Texinfo::Config::GNUT_get_output_units_conversion, setup by |
| # Texinfo::Config::texinfo_register_output_unit_formatting() |
| my %default_output_units_conversion; |
| |
| sub default_output_unit_conversion($$) { |
| my ($self, $type) = @_; |
| |
| return $default_output_units_conversion{$type}; |
| } |
| |
| sub output_unit_conversion($$) { |
| my ($self, $type) = @_; |
| |
| return $self->{'output_units_conversion'}->{$type}; |
| } |
| |
| sub default_type_conversion($$) { |
| my ($self, $type) = @_; |
| |
| return $default_types_conversion{$type}; |
| } |
| |
| sub type_conversion($$) { |
| my ($self, $type) = @_; |
| |
| return $self->{'types_conversion'}->{$type}; |
| } |
| |
| my %default_types_open; |
| |
| sub default_type_open($$) { |
| my ($self, $type) = @_; |
| |
| return $default_types_open{$type}; |
| } |
| |
| # Ignored commands |
| foreach my $type ( |
| 'ignorable_spaces_after_command', |
| 'spaces_at_end', |
| 'spaces_before_paragraph', |
| # may be better not to ignore spaces when a : is postpended |
| # and the user really wants a space |
| #'space_at_end_menu_node', |
| 'spaces_after_close_brace', |
| 'spaces_before_argument', |
| 'spaces_after_argument' |
| ) { |
| $default_types_conversion{$type} = undef; |
| } |
| |
| foreach my $type ( |
| 'postamble_after_end', |
| 'preamble_before_beginning', |
| 'preamble_before_setfilename', |
| 'arguments_line') { |
| $default_types_conversion{$type} = undef; |
| } |
| |
| sub _convert_paragraph_type($$$$) { |
| my ($self, $type, $element, $content) = @_; |
| |
| $content = '' if (!defined($content)); |
| |
| $content = $self->get_associated_formatted_inline_content($element).$content; |
| |
| if (paragraph_number($self) == 1) { |
| my $in_format = top_block_command($self); |
| 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' |
| # this should only happen if in @nodedescriptionblock, otherwise |
| # there are no paragraphs, but preformatted |
| or $in_format eq 'menu') { |
| return $content; |
| } |
| } |
| } |
| return $content if (in_string($self)); |
| |
| if ($content =~ /\S/) { |
| my $align = $self->in_align(); |
| if ($align and exists($HTML_align_commands{$align})) { |
| return $self->html_attribute_class('p', [$align.'-paragraph']).">" |
| .$content."</p>"; |
| } else { |
| return "<p>".$content."</p>"; |
| } |
| } else { |
| return ''; |
| } |
| } |
| |
| $default_types_conversion{'paragraph'} = \&_convert_paragraph_type; |
| |
| |
| sub _open_inline_container_type($$$) { |
| my ($self, $type, $element) = @_; |
| |
| my $pending_formatted = $self->get_pending_formatted_inline_content(); |
| |
| if (defined($pending_formatted)) { |
| $self->associate_pending_formatted_inline_content($element, $pending_formatted); |
| } |
| return ''; |
| } |
| |
| $default_types_open{'paragraph'} = \&_open_inline_container_type; |
| $default_types_open{'preformatted'} = \&_open_inline_container_type; |
| |
| |
| sub _preformatted_class($) { |
| my $self = shift; |
| |
| my $pre_class; |
| my $pre_classes = $self->preformatted_classes_stack(); |
| foreach my $class (@$pre_classes) { |
| $pre_class = $class unless (defined($pre_class) |
| and exists($preformatted_code_commands{$pre_class}) |
| and !(exists($preformatted_code_commands{$class}) |
| or $class eq 'menu')); |
| } |
| return $pre_class.'-preformatted'; |
| } |
| |
| sub _convert_preformatted_type($$$$) { |
| my ($self, $type, $element, $content) = @_; |
| |
| $content = '' if (!defined($content)); |
| |
| $content = $self->get_associated_formatted_inline_content($element).$content; |
| |
| return '' if ($content eq ''); |
| |
| if (top_block_command($self) eq 'multitable') { |
| $content =~ s/^\s*//; |
| $content =~ s/\s*$//; |
| } |
| |
| if (in_string($self)) { |
| return $content; |
| } |
| |
| my $pre_class; |
| # menu_entry_description is always in a preformatted container |
| # in the tree, as the whole menu is meant to be an |
| # environment where spaces and newlines are preserved. |
| if (exists($element->{'parent'}->{'type'}) |
| and $element->{'parent'}->{'type'} eq 'menu_entry_description') { |
| if (!inside_preformatted($self)) { |
| # If not in preformatted block command, |
| # we don't preserve spaces and newlines in menu_entry_description, |
| # instead the whole menu_entry is in a table, so no <pre> in that situation |
| return $content; |
| } else { |
| # if directly in description, we want to avoid the linebreak that |
| # comes with pre being a block level element, so set a special class |
| $pre_class = 'menu-entry-description-preformatted'; |
| } |
| } |
| |
| $content =~ s/^\n/\n\n/; # a newline immediately after a <pre> is ignored. |
| |
| $pre_class = _preformatted_class($self) if (!defined($pre_class)); |
| my $result = $self->html_attribute_class('pre', [$pre_class]).'>' |
| . $content . '</pre>'; |
| |
| return $result; |
| } |
| |
| $default_types_conversion{'preformatted'} = \&_convert_preformatted_type; |
| |
| sub _convert_balanced_braces_type($$$$) { |
| my ($self, $type, $element, $content) = @_; |
| |
| $content = '' if (!defined($content)); |
| |
| return $content; |
| } |
| |
| $default_types_conversion{'balanced_braces'} = \&_convert_balanced_braces_type; |
| |
| # use the type and not the index commands names, as they are diverse and |
| # can be dynamically added, so it is difficult to use as selector for output |
| # formatting. The command name can be obtained here as $element->{'cmdname'}. |
| sub _convert_index_entry_command_type($$$$) { |
| my ($self, $type, $element, $content) = @_; |
| |
| my $index_id = $self->command_id($element); |
| # since in_multiple_conversions returning true while multi_expanded_region |
| # returns undef can only happen when expanding target and heading commands |
| # text, in which there should not be index commands, using one function |
| # or the other in the conddition should not give a different result. |
| if (defined($index_id) and $index_id ne '' |
| and !in_multiple_conversions($self) |
| and !in_string($self)) { |
| my $result = &{$self->formatting_function('format_separate_anchor')}($self, |
| $index_id, 'index-entry-id'); |
| $result .= "\n" unless (in_preformatted_context($self)); |
| return $result; |
| } |
| return ''; |
| } |
| $default_types_conversion{'index_entry_command'} = \&_convert_index_entry_command_type; |
| |
| sub _convert_definfoenclose_type($$$$) { |
| my ($self, $type, $element, $content) = @_; |
| |
| $content = '' if (!defined($content)); |
| |
| # TODO add a span to mark the original command as a class? |
| # Not to be done as long as definfoenclose is deprecated. |
| return &{$self->formatting_function('format_protect_text')}($self, |
| $element->{'extra'}->{'begin'}) |
| . $content . |
| &{$self->formatting_function('format_protect_text')}($self, |
| $element->{'extra'}->{'end'}); |
| } |
| |
| $default_types_conversion{'definfoenclose_command'} |
| = \&_convert_definfoenclose_type; |
| |
| # Note: has an XS override |
| sub _entity_text { |
| my $text = shift; |
| |
| $text =~ s/---/\&mdash\;/g; |
| $text =~ s/--/\&ndash\;/g; |
| $text =~ s/``/\&ldquo\;/g; |
| $text =~ s/''/\&rdquo\;/g; |
| $text =~ s/'/\&rsquo\;/g; |
| $text =~ s/`/\&lsquo\;/g; |
| |
| return $text; |
| } |
| |
| sub _convert_text($$$$) { |
| my ($self, $type, $element, $text) = @_; |
| |
| if (in_verbatim($self)) { |
| # API info: using the API to allow for customization would be: |
| #return &{$self->formatting_function('format_protect_text')}($self, $text); |
| return _default_format_protect_text($self, $text); |
| } |
| return $text if (in_raw($self)); |
| |
| $text = uc($text) if (in_upper_case($self)); |
| |
| # API info: using the API to allow for customization would be: |
| #$text = &{$self->formatting_function('format_protect_text')}($self, $text); |
| $text = _default_format_protect_text($self, $text); |
| |
| # API info: for efficiency, we cache the result of the calls to configuration |
| # in $self->{'use_unicode_text'}. |
| # API code conforming would be: |
| #if ($self->get_conf('OUTPUT_CHARACTERS') |
| # and $self->get_conf('OUTPUT_ENCODING_NAME') |
| # and $self->get_conf('OUTPUT_ENCODING_NAME') eq 'utf-8') { |
| if ($self->{'use_unicode_text'}) { |
| $text = Texinfo::Convert::Unicode::unicode_text($text, |
| (in_code($self) or in_math($self))); |
| } elsif (!in_code($self) and !in_math($self)) { |
| if ($self->get_conf('USE_NUMERIC_ENTITY')) { |
| $text = $self->xml_format_text_with_numeric_entities($text); |
| } elsif ($self->get_conf('USE_ISO')) { |
| $text = _entity_text($text); |
| } else { |
| $text =~ s/``/"/g; |
| $text =~ s/''/"/g; |
| $text =~ s/---/\x{1F}/g; |
| $text =~ s/--/-/g; |
| $text =~ s/\x{1F}/--/g; |
| } |
| } |
| |
| return $text if (in_preformatted_context($self)); |
| |
| if (in_non_breakable_space($self)) { |
| my $non_breaking_space = $self->get_info('non_breaking_space'); |
| $text =~ s/\n/ /g; |
| $text =~ s/ +/$non_breaking_space/g; |
| } elsif (in_space_protected($self)) { |
| if (chomp($text)) { |
| my $line_break_element = $self->get_info('line_break_element'); |
| # protect spaces in line_break_element formatting. |
| # Note that this case is theoretical right now, as it is not possible |
| # to redefine the line_break_element and there are no spaces |
| # in the possible values. However this could be a deficiency of the API, |
| # it could be better to be able to redefine line_break_element |
| $line_break_element =~ s/ /\x{1F}/g; |
| $text .= $line_break_element; |
| } |
| # Protect spaces within text |
| my $non_breaking_space = $self->get_info('non_breaking_space'); |
| $text =~ s/ /$non_breaking_space/g; |
| # Revert protected spaces in leading html attribute |
| $text =~ s/\x{1F}/ /g; |
| } |
| return $text; |
| } |
| |
| $default_types_conversion{'text'} = \&_convert_text; |
| |
| sub _css_string_convert_text($$$$) { |
| my ($self, $type, $element, $text) = @_; |
| |
| $text = uc($text) if (in_upper_case($self)); |
| |
| # need to hide \ otherwise it is protected in protect_text |
| if (!in_code($self) and !in_math($self)) { |
| $text =~ s/---/\x{1F}2014 /g; |
| $text =~ s/--/\x{1F}2013 /g; |
| $text =~ s/``/\x{1F}201C /g; |
| $text =~ s/''/\x{1F}201D /g; |
| $text =~ s/'/\x{1F}2019 /g; |
| $text =~ s/`/\x{1F}2018 /g; |
| } |
| |
| $text |
| = &{$self->formatting_function('format_protect_text')}($self, $text); |
| $text =~ s/\x{1F}/\\/g; |
| |
| return $text; |
| } |
| $default_css_string_types_conversion{'text'} = \&_css_string_convert_text; |
| |
| sub _simplify_text_for_comparison($) { |
| my $text = shift; |
| |
| $text =~ s/[^\p{Word}]//g; |
| return $text; |
| } |
| |
| sub _convert_untranslated_def_line_arg_type($$$$) { |
| my ($self, $type, $element, $content) = @_; |
| |
| my $translated; |
| my $category_text = $element->{'contents'}->[0]->{'text'}; |
| if (exists($element->{'extra'}) |
| and exists($element->{'extra'}->{'translation_context'})) { |
| $translated = $self->pcdt($element->{'extra'}->{'translation_context'}, |
| $category_text); |
| } else { |
| $translated = $self->cdt($category_text); |
| } |
| my $result = $self->convert_tree($translated, 'translated TEXT'); |
| |
| return $result; |
| } |
| |
| $default_types_conversion{'untranslated_def_line_arg'} |
| = \&_convert_untranslated_def_line_arg_type; |
| |
| |
| sub _convert_row_type($$$$) { |
| my ($self, $type, $element, $content) = @_; |
| |
| $content = '' if (!defined($content)); |
| |
| return $content if (in_string($self)); |
| |
| if ($content =~ /\S/) { |
| my $result = '<tr>' . $content . '</tr>'; |
| if (exists($element->{'contents'}) |
| and $element->{'contents'}->[0]->{'cmdname'} ne 'headitem') { |
| # if headitem, end of line added in _convert_multitable_head_type |
| $result .= "\n"; |
| } |
| return $result; |
| } else { |
| return ''; |
| } |
| } |
| $default_types_conversion{'row'} = \&_convert_row_type; |
| |
| sub _convert_multitable_head_type($$$$) { |
| my ($self, $type, $element, $content) = @_; |
| |
| $content = '' if (!defined($content)); |
| |
| return $content if (in_string($self)); |
| |
| if ($content =~ /\S/) { |
| return '<thead>' . $content . '</thead>' . "\n"; |
| } else { |
| return ''; |
| } |
| } |
| |
| $default_types_conversion{'multitable_head'} = \&_convert_multitable_head_type; |
| |
| sub _convert_multitable_body_type($$$$) { |
| my ($self, $type, $element, $content) = @_; |
| |
| return $content if (in_string($self)); |
| if ($content =~ /\S/) { |
| return '<tbody>' . $content . '</tbody>' . "\n"; |
| } else { |
| return ''; |
| } |
| } |
| |
| $default_types_conversion{'multitable_body'} = \&_convert_multitable_body_type; |
| |
| # The node is used, not the nodedescription because it is easier to |
| # find the node in XS |
| sub _formatted_nodedescription_nr($$) { |
| my ($self, $node) = @_; |
| |
| # update the number of time the node description was formatted |
| my $formatted_nodedescription_nr |
| = $self->get_shared_conversion_state('nodedescription', |
| 'formatted_nodedescriptions', |
| $node); |
| $formatted_nodedescription_nr = 0 |
| if (!defined($formatted_nodedescription_nr)); |
| $formatted_nodedescription_nr++; |
| $self->set_shared_conversion_state('nodedescription', |
| 'formatted_nodedescriptions', |
| $node, $formatted_nodedescription_nr); |
| return $formatted_nodedescription_nr; |
| } |
| |
| sub _convert_menu_entry_type($$$) { |
| my ($self, $type, $element) = @_; |
| |
| my $name_entry; |
| my $menu_description; |
| my $menu_entry_node; |
| my $menu_entry_leading_text; |
| my @menu_entry_separators; |
| |
| foreach my $arg (@{$element->{'contents'}}) { |
| if ($arg->{'type'} eq 'menu_entry_leading_text') { |
| $menu_entry_leading_text = $arg; |
| } elsif ($arg->{'type'} eq 'menu_entry_name') { |
| $name_entry = $arg; |
| } elsif ($arg->{'type'} eq 'menu_entry_description') { |
| $menu_description = $arg; |
| } elsif ($arg->{'type'} eq 'menu_entry_separator') { |
| push @menu_entry_separators, $arg; |
| } elsif ($arg->{'type'} eq 'menu_entry_node') { |
| $menu_entry_node = $arg; |
| } |
| } |
| |
| my $href; |
| my $rel = ''; |
| my $associated_title_command; |
| |
| my $node_description; |
| my $long_description = 0; |
| my $formatted_nodedescription_nr; |
| |
| # external node |
| if (exists($menu_entry_node->{'extra'}) |
| and exists($menu_entry_node->{'extra'}->{'manual_content'})) { |
| $href = $self->command_href($menu_entry_node, undef, $element); |
| # may not exist in case of menu entry node consisting only of spaces |
| } elsif (exists($menu_entry_node->{'extra'}) |
| and exists($menu_entry_node->{'extra'}->{'normalized'})) { |
| my $node = $self->converter_find_identifier_target( |
| $menu_entry_node->{'extra'}->{'normalized'}); |
| if ($node) { |
| my $node_relations; |
| if ($node->{'cmdname'} eq 'node') { |
| my $document = $self->get_info('document'); |
| if (defined($document)) { |
| my $nodes_list = $document->nodes_list(); |
| $node_relations |
| = $nodes_list->[$node->{'extra'}->{'node_number'} -1]; |
| |
| if (exists($node_relations->{'node_description'})) { |
| $node_description = $node_relations->{'node_description'}; |
| } elsif (exists($node_relations->{'node_long_description'})) { |
| $node_description = $node_relations->{'node_long_description'}; |
| $long_description = 1; |
| } |
| } |
| } |
| # if !NODE_NAME_IN_MENU, we pick the associated title command element |
| if (!$self->get_conf('NODE_NAME_IN_MENU') and defined($node_relations)) { |
| $associated_title_command |
| = $node_relations->{'associated_title_command'}; |
| } |
| |
| if (defined($associated_title_command)) { |
| $href = $self->command_href($associated_title_command, |
| undef, $element); |
| } else { |
| $href = $self->command_href($node, undef, $element); |
| } |
| if (exists($node->{'extra'}) and $node->{'extra'}->{'isindex'}) { |
| # Mark the target as an index. See |
| # http://microformats.org/wiki/existing-rel-values#HTML5_link_type_extensions |
| $rel = ' rel="index"'; |
| } |
| if (defined($node_description) |
| # not menu_description probably cannot happen |
| and (not defined($menu_description) |
| # empty description |
| or (not exists($menu_description->{'contents'}) |
| or (scalar(@{$menu_description->{'contents'}}) == 1 |
| # preformatted inside menu_entry_description |
| and (not (exists($menu_description->{'contents'}->[0] |
| ->{'contents'})) |
| or (scalar(@{$menu_description->{'contents'}->[0] |
| ->{'contents'}}) == 1) |
| and exists($menu_description->{'contents'}->[0] |
| ->{'contents'}->[0]->{'text'}) |
| and $menu_description->{'contents'}->[0] |
| ->{'contents'}->[0]->{'text'} !~ /\S/))))) { |
| $formatted_nodedescription_nr |
| = _formatted_nodedescription_nr($self, $node); |
| } |
| } |
| } |
| |
| my $html_menu_entry_index |
| = $self->get_shared_conversion_state('menu', 'html_menu_entry_index'); |
| $html_menu_entry_index = 0 if (!defined($html_menu_entry_index)); |
| $html_menu_entry_index++; |
| $self->set_shared_conversion_state('menu', 'html_menu_entry_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'); |
| |
| my $in_string = in_string($self); |
| if (inside_preformatted($self) or $in_string) { |
| my $leading_text = $menu_entry_leading_text->{'text'}; |
| $leading_text =~ s/\*/$MENU_SYMBOL/; |
| my $result_name_node = $leading_text; |
| |
| if (defined($name_entry)) { |
| $result_name_node |
| .= $self->convert_tree($name_entry, |
| 'menu_arg menu_entry_name preformatted'); |
| my $name_separator = shift @menu_entry_separators; |
| $result_name_node |
| .= $self->convert_tree($name_separator, |
| 'menu_arg name separator preformatted'); |
| } |
| |
| if (defined($menu_entry_node)) { |
| my $name = $self->set_context_convert_tree($menu_entry_node, |
| $CTXF_code, 'menu_arg menu_entry_node preformatted'); |
| if (defined($href) and !$in_string) { |
| $result_name_node .= "<a href=\"$href\"$rel$accesskey>$name</a>"; |
| } else { |
| $result_name_node .= $name; |
| } |
| } |
| if (scalar(@menu_entry_separators)) { |
| my $node_separator = shift @menu_entry_separators; |
| $result_name_node |
| .= $self->convert_tree($node_separator, |
| 'menu_arg node separator preformatted'); |
| } |
| |
| if (not $in_string) { |
| my $pre_class = _preformatted_class($self); |
| $result_name_node = $self->html_attribute_class('pre', [$pre_class]).'>' |
| . $result_name_node . '</pre>'; |
| } |
| |
| my $description = ''; |
| if ($formatted_nodedescription_nr) { |
| my $description_element; |
| if (!$long_description) { |
| $description_element = $node_description->{'contents'}->[0]; |
| } else { |
| # nodedescriptionblock |
| $description_element = Texinfo::TreeElement::new( |
| {'contents' => $node_description->{'contents'}}); |
| } |
| my $multiple_formatted; |
| if ($formatted_nodedescription_nr > 1) { |
| $multiple_formatted |
| = 'preformatted-node-description-'.$formatted_nodedescription_nr; |
| } |
| $description |
| .= $self->convert_tree_new_formatting_context($description_element, |
| 'menu_arg node description preformatted', |
| undef, $multiple_formatted, undef, |
| 'menu'); |
| } elsif ($menu_description) { |
| $description .= $self->convert_tree($menu_description, |
| 'menu_arg description preformatted'); |
| } |
| |
| return $result_name_node . $description; |
| } |
| |
| my $name; |
| my $name_no_number; |
| if (defined($associated_title_command) and defined($href)) { |
| $name = $self->command_text($associated_title_command); |
| if ($name ne '') { |
| $name = "<a href=\"$href\"$rel$accesskey>$name</a>"; |
| $name_no_number |
| = $self->command_text($associated_title_command, 'text_nonumber'); |
| } |
| } |
| # A leading menu symbol is only inserted if the section name is not |
| # used since the section name usually comes with a section number (unless |
| # NUMBER_SECTIONS is 0, or the section is unnumbered/heading/xrefname) |
| if (!defined($name) or $name eq '') { |
| if (defined($name_entry)) { |
| $name = $self->convert_tree($name_entry, 'convert menu_entry_name'); |
| } |
| if (!defined($name) or $name eq '') { |
| if (exists($menu_entry_node->{'extra'}) |
| and exists($menu_entry_node->{'extra'}->{'manual_content'})) { |
| $name = $self->command_text($menu_entry_node); |
| } elsif (exists($menu_entry_node->{'extra'}) |
| and exists($menu_entry_node->{'extra'}->{'node_content'})) { |
| $name = $self->set_context_convert_tree( |
| $menu_entry_node->{'extra'}->{'node_content'}, |
| $CTXF_code, 'menu_arg name'); |
| } else { |
| $name = ''; |
| } |
| } |
| $name =~ s/^\s*//; |
| $name_no_number = $name; |
| if (defined($href)) { |
| $name = "<a href=\"$href\"$rel$accesskey>$name</a>"; |
| } |
| $name = "$MENU_SYMBOL ".$name; |
| } |
| my $description = ''; |
| if ($formatted_nodedescription_nr) { |
| my $description_element; |
| if (!$long_description) { |
| $description_element = $node_description->{'contents'}->[0]; |
| } else { |
| # nodedescriptionblock |
| $description_element = Texinfo::TreeElement::new( |
| {'contents' => $node_description->{'contents'}}); |
| } |
| my $multiple_formatted; |
| if ($formatted_nodedescription_nr > 1) { |
| $multiple_formatted |
| = 'node-description-'.$formatted_nodedescription_nr; |
| } |
| $description |
| = $self->convert_tree_new_formatting_context($description_element, |
| 'menu_arg node description', undef, |
| $multiple_formatted, undef, 'menu'); |
| } elsif (defined($menu_description)) { |
| $description = $self->convert_tree($menu_description, |
| 'menu_arg description'); |
| } |
| my $non_breaking_space = $self->get_info('non_breaking_space'); |
| return '<tr>' |
| .$self->html_attribute_class('td', ['menu-entry-destination']).'>' |
| ."$name$MENU_ENTRY_COLON</td>" |
| .$self->html_attribute_class('td', ['menu-entry-description']).'>' |
| ."$description</td></tr>\n"; |
| } |
| |
| $default_types_conversion{'menu_entry'} = \&_convert_menu_entry_type; |
| |
| sub _convert_menu_comment_type($$$$) { |
| my ($self, $type, $element, $content) = @_; |
| |
| $content = '' if (!defined($content)); |
| |
| if (inside_preformatted($self) or in_string($self)) { |
| return $content; |
| } else { |
| return '<tr>'.$self->html_attribute_class('th', ['menu-comment']) |
| . ' colspan="2">'.$content .'</th></tr>'; |
| } |
| } |
| |
| $default_types_conversion{'menu_comment'} = \&_convert_menu_comment_type; |
| |
| sub _convert_before_item_type($$$$) { |
| my ($self, $type, $element, $content) = @_; |
| |
| return '' if (!defined ($content) or $content !~ /\S/); |
| return $content if (in_string($self)); |
| my $top_block_command = top_block_command($self); |
| if ($top_block_command eq 'itemize' or $top_block_command eq 'enumerate') { |
| return '<li>'. $content .'</li>'; |
| } elsif ($top_block_command eq 'table' or $top_block_command eq 'vtable' |
| or $top_block_command eq 'ftable') { |
| return '<dd>'. $content .'</dd>'."\n"; |
| } elsif ($top_block_command eq 'multitable') { |
| $content =~ s/^\s*//; |
| $content =~ s/\s*$//; |
| |
| return '<tr><td>'.$content.'</td></tr>'."\n"; |
| } |
| } |
| |
| $default_types_conversion{'before_item'} = \&_convert_before_item_type; |
| |
| sub _convert_table_term_type($$$$) { |
| my ($self, $type, $element, $content) = @_; |
| |
| $content = '' if (!defined($content)); |
| |
| return '<dt>'.$content; |
| } |
| |
| $default_types_conversion{'table_term'} = \&_convert_table_term_type; |
| |
| sub _convert_def_line_type($$$$) { |
| my ($self, $type, $element, $content) = @_; |
| |
| if (in_string($self)) { |
| # should probably never happen |
| return &{$self->formatting_function('format_protect_text')}($self, |
| Texinfo::Convert::Text::convert_to_text( |
| $element, $self->{'convert_text_options'})); |
| } |
| |
| my $index_label = ''; |
| my $index_id = $self->command_id($element); |
| # since in_multiple_conversions returning true while multi_expanded_region |
| # returns undef can only happen when expanding target and heading commands |
| # text, in which there should not be @def*, using one function |
| # or the other in the conddition should not give a different result. |
| if (defined($index_id) and $index_id ne '' |
| and !in_multiple_conversions($self)) { |
| $index_label = " id=\"$index_id\""; |
| } |
| my ($category_element, $class_element, |
| $type_element, $name_element, $arguments) |
| = Texinfo::Convert::Utils::definition_arguments_content($element); |
| |
| my $original_def_cmdname = $element->{'extra'}->{'original_def_cmdname'}; |
| my $original_command_name; |
| my $alias_class; |
| if (exists($Texinfo::Common::def_aliases{$original_def_cmdname})) { |
| $original_command_name = $Texinfo::Common::def_aliases{$original_def_cmdname}; |
| $alias_class = "$original_def_cmdname-alias-$original_command_name"; |
| } else { |
| $original_command_name = $original_def_cmdname; |
| } |
| |
| my $def_command = $element->{'extra'}->{'def_command'}; |
| my $base_command_name; |
| if (exists($Texinfo::Common::def_aliases{$def_command})) { |
| $base_command_name |
| = $Texinfo::Common::def_aliases{$def_command}; |
| } else { |
| $base_command_name = $def_command; |
| } |
| |
| my @classes = (); |
| push @classes, $original_command_name; |
| if (defined($alias_class)) { |
| push @classes, $alias_class; |
| } |
| if ($base_command_name ne $original_command_name) { |
| push @classes, "def-cmd-$base_command_name"; |
| } |
| |
| push @classes, 'def-line'; |
| |
| my $def_call = ''; |
| if (defined($type_element)) { |
| my $explanation = "DEF_TYPE $def_command"; |
| my $type_text = $self->set_context_convert_tree($type_element, |
| $CTXF_code, $explanation); |
| if ($type_text ne '') { |
| $def_call .= $self->html_attribute_class('code', ['def-type']).'>'. |
| $type_text .'</code>'; |
| } |
| if (($base_command_name eq 'deftypefn' |
| or $base_command_name eq 'deftypeop') |
| and $self->get_conf('deftypefnnewline') |
| and $self->get_conf('deftypefnnewline') eq 'on') { |
| $def_call .= $self->get_info('line_break_element') . ' '; |
| } elsif ($type_text ne '') { |
| $def_call .= ' '; |
| } |
| } |
| |
| if (defined($name_element)) { |
| my $def_name_text = $self->set_context_convert_tree($name_element, |
| $CTXF_code, "DEF_NAME $def_command"); |
| |
| $def_call .= $self->html_attribute_class('strong', ['def-name']).'>'. |
| $def_name_text .'</strong>'; |
| } |
| |
| if (defined($arguments)) { |
| my $explanation = "DEF_ARGS $def_command"; |
| # arguments not only metasyntactic variables |
| # (deftypefn, deftypevr, deftypeop, deftypecv) |
| if (exists($Texinfo::Common::def_no_var_arg_commands{$base_command_name})) { |
| my $arguments_formatted = $self->set_context_convert_tree($arguments, |
| $CTXF_code, $explanation); |
| if ($arguments_formatted =~ /\S/) { |
| $def_call .= ' ' unless($element->{'extra'}->{'omit_def_name_space'}); |
| $def_call .= $self->html_attribute_class('code', |
| ['def-code-arguments']).'>' |
| . $arguments_formatted.'</code>'; |
| } |
| } else { |
| # only metasyntactic variable arguments (deffn, defvr, deftp, defop, defcv) |
| # Has an effect only for @def* in @example and similar block |
| # commands that sets code expansion. |
| my $arguments_formatted = $self->set_context_convert_tree($arguments, |
| $CTXF_normal, $explanation); |
| if ($arguments_formatted =~ /\S/) { |
| $def_call .= ' ' unless($element->{'extra'}->{'omit_def_name_space'}); |
| $def_call .= $self->html_attribute_class('var', |
| ['def-var-arguments']).'>' |
| . $arguments_formatted .'</var>'; |
| } |
| } |
| } |
| |
| if ($self->get_conf('DEF_TABLE')) { |
| my $category_result = ''; |
| my $def_category_tree |
| = Texinfo::Convert::Utils::definition_category_tree($element, |
| $self->{'current_lang_translations'}, |
| $self->get_conf('DEBUG'), $self); |
| $category_result |
| = $self->convert_tree($def_category_tree) |
| if (defined($def_category_tree)); |
| |
| return $self->html_attribute_class('tr', \@classes) |
| . "$index_label>".$self->html_attribute_class('td', ['call-def']).'>' |
| . $def_call . '</td>'.$self->html_attribute_class('td', ['category-def']) |
| . '>' . '[' . $category_result . ']' . "</td></tr>\n"; |
| } |
| |
| my $result = $self->html_attribute_class('dt', \@classes) . "$index_label>"; |
| |
| if (defined($category_element)) { |
| my $e_category_tree; |
| if (defined($class_element)) { |
| my $substrings = {'category' => $category_element, |
| 'class' => $class_element}; |
| if ($base_command_name eq 'deftypeop' |
| and defined($type_element) |
| and $self->get_conf('deftypefnnewline') |
| and $self->get_conf('deftypefnnewline') eq 'on') { |
| $e_category_tree = $self->cdt('{category} on @code{{class}}:@* ', |
| $substrings); |
| } elsif ($base_command_name eq 'defop' |
| or $base_command_name eq 'deftypeop') { |
| $e_category_tree = $self->cdt('{category} on @code{{class}}: ', |
| $substrings); |
| } elsif ($base_command_name eq 'defcv' |
| or $base_command_name eq 'deftypecv') { |
| $e_category_tree = $self->cdt('{category} of @code{{class}}: ', |
| $substrings); |
| } |
| } else { |
| my $substrings = {'category' => $category_element}; |
| if (defined($type_element) |
| and ($base_command_name eq 'deftypefn' |
| or $base_command_name eq 'deftypeop') |
| and $self->get_conf('deftypefnnewline') |
| and $self->get_conf('deftypefnnewline') eq 'on') { |
| # TODO if in @def* in @example and with @deftypefnnewline |
| # on there is no effect of @deftypefnnewline on, as @* in |
| # preformatted environment becomes an end of line, but the def* |
| # line is not in a preformatted environment. There should be |
| # an explicit <br> in that case. Probably requires changing |
| # the conversion of @* in a @def* line in preformatted, nothing |
| # really specific of @deftypefnnewline on. |
| $e_category_tree = $self->cdt('{category}:@* ', $substrings); |
| } else { |
| $e_category_tree = $self->cdt('{category}: ', $substrings); |
| } |
| } |
| if (defined($e_category_tree)) { |
| my $open = $self->html_attribute_class('span', ['category-def']); |
| if ($open ne '') { |
| $result .= $open.'>'; |
| } |
| my $explanation = "DEF_CATEGORY $def_command"; |
| $result .= $self->convert_tree($e_category_tree, $explanation); |
| if ($open ne '') { |
| $result .= '</span>'; |
| } |
| } |
| } |
| |
| my $anchor = _get_copiable_anchor($self, $index_id); |
| if (defined($anchor)) { |
| $result .= '<span>'; |
| } |
| $result .= $def_call; |
| if (defined($anchor)) { |
| $result .= $anchor . '</span>'; |
| } |
| $result .= "</dt>\n"; |
| |
| return $result; |
| } |
| |
| sub _get_copiable_anchor($$) { |
| my ($self, $id) = @_; |
| |
| if (defined($id) and $id ne '' and $self->get_conf('COPIABLE_LINKS')) { |
| my $paragraph_symbol = $self->get_info('paragraph_symbol'); |
| return $self->html_attribute_class('a', ['copiable-link']) |
| ." href=\"#$id\"> $paragraph_symbol</a>"; |
| } |
| return undef; |
| } |
| |
| $default_types_conversion{'def_line'} = \&_convert_def_line_type; |
| |
| sub _convert_def_item_type($$$$) { |
| my ($self, $type, $element, $content) = @_; |
| |
| $content = '' if (!defined($content)); |
| |
| return $content if (in_string($self)); |
| |
| if ($content =~ /\S/) { |
| if (! $self->get_conf('DEF_TABLE')) { |
| return '<dd>' . $content . '</dd>'; |
| } else { |
| return '<tr><td colspan="2">' . $content . '</td></tr>'; |
| } |
| } |
| } |
| |
| $default_types_conversion{'def_item'} = \&_convert_def_item_type; |
| $default_types_conversion{'inter_def_item'} = \&_convert_def_item_type; |
| $default_types_conversion{'before_defline'} = \&_convert_def_item_type; |
| |
| sub _convert_table_definition_type($$$$) { |
| my ($self, $type, $element, $content) = @_; |
| |
| $content = '' if (!defined($content)); |
| |
| return $content if (in_string($self)); |
| |
| if ($content =~ /\S/) { |
| return '<dd>' . $content . '</dd>'."\n"; |
| } |
| } |
| |
| $default_types_conversion{'table_definition'} |
| = \&_convert_table_definition_type; |
| $default_types_conversion{'inter_item'} |
| = \&_convert_table_definition_type; |
| |
| # Function for converting special output units |
| sub _convert_special_unit_type($$$$) { |
| my ($self, $type, $output_unit, $content) = @_; |
| |
| $content = '' if (!defined($content)); |
| |
| if (in_string($self)) { |
| return ''; |
| } |
| |
| my $result = ''; |
| |
| my $special_unit_variety = $output_unit->{'special_unit_variety'}; |
| my $closed_strings = $self->close_registered_sections_level( |
| $self->current_filename(), 0); |
| $result .= join('', @{$closed_strings}); |
| |
| my $special_unit_body |
| .= &{$self->special_unit_body_formatting($special_unit_variety)}($self, |
| $special_unit_variety, $output_unit); |
| |
| # This may happen with footnotes in regions that are not expanded, |
| # like @copying or @titlepage |
| if ($special_unit_body eq '') { |
| return ''; |
| } |
| |
| my $unit_command = $output_unit->{'unit_command'}; |
| |
| my $id = $self->command_id($unit_command); |
| my $class_base |
| = $self->special_unit_info('class', $special_unit_variety); |
| $result .= $self->html_attribute_class('div', ["element-${class_base}"]); |
| if ($id ne '') { |
| $result .= " id=\"$id\""; |
| } |
| $result .= ">\n"; |
| if ($self->get_conf('HEADERS') |
| # first in page |
| or (exists($output_unit->{'unit_filename'}) |
| and $self->count_elements_in_filename('current', |
| $output_unit->{'unit_filename'}) == 1)) { |
| $result .= &{$self->formatting_function('format_navigation_header')}($self, |
| $self->get_conf('MISC_BUTTONS'), undef, $unit_command); |
| } |
| my $heading = $self->command_text($unit_command); |
| my $level = $self->get_conf('CHAPTER_HEADER_LEVEL'); |
| if ($special_unit_variety eq 'footnotes') { |
| $level = $self->get_conf('FOOTNOTE_SEPARATE_HEADER_LEVEL'); |
| } |
| $result .= &{$self->formatting_function('format_heading_text')}($self, |
| undef, [$class_base.'-heading'], $heading, $level)."\n"; |
| |
| |
| $result .= $special_unit_body . '</div>'; |
| $result .= &{$self->formatting_function('format_element_footer')}($self, |
| $type, $output_unit, $content, $unit_command); |
| return $result; |
| } |
| |
| $default_output_units_conversion{'special_unit'} |
| = \&_convert_special_unit_type; |
| |
| # Function for converting the output units. The node and associated section |
| # appear together in the output unit. $OUTPUT_UNIT was created in this |
| # module (in _prepare_conversion_units), it's not a tree element (created |
| # by the parser). |
| # $CONTENT is the contents of the output unit, already converted. |
| sub _convert_unit_type($$$$) { |
| my ($self, $type, $output_unit, $content) = @_; |
| |
| $content = '' if (!defined($content)); |
| |
| if (in_string($self)) { |
| return $content; |
| } |
| my $result = ''; |
| if (not exists($output_unit->{'tree_unit_directions'}) |
| or not exists($output_unit->{'tree_unit_directions'}->{'prev'})) { |
| my $global_commands; |
| |
| my $document = $self->get_info('document'); |
| if (defined($document)) { |
| $global_commands = $document->global_commands_information(); |
| } |
| |
| if (!(defined($global_commands) |
| and exists($global_commands->{'maketitle'}))) { |
| $result .= $self->get_info('title_titlepage'); |
| } |
| if (not exists($output_unit->{'tree_unit_directions'}) |
| or not exists($output_unit->{'tree_unit_directions'}->{'next'})) { |
| # only one unit, use simplified formatting |
| $result .= $content; |
| # if there is one unit it also means that there is no formatting |
| # of footnotes in a separate unit. And if footnotestyle is end |
| # the footnotes won't be done in format_element_footer either. |
| $result |
| .= &{$self->formatting_function('format_footnotes_segment')}($self); |
| $result .= $self->get_conf('DEFAULT_RULE') ."\n" |
| if ($self->get_conf('PROGRAM_NAME_IN_FOOTER') |
| and defined($self->get_conf('DEFAULT_RULE'))); |
| # do it here, as it is won't be done at end of page in |
| # format_element_footer |
| my $closed_strings = $self->close_registered_sections_level( |
| $self->current_filename(), 0); |
| $result .= join('', @{$closed_strings}); |
| return $result; |
| } |
| } |
| $result .= $content; |
| my $unit_command; |
| if (exists($output_unit->{'unit_command'})) { |
| $unit_command = $output_unit->{'unit_command'}; |
| } |
| $result .= &{$self->formatting_function('format_element_footer')}($self, $type, |
| $output_unit, $content, $unit_command); |
| |
| return $result; |
| } |
| |
| $default_output_units_conversion{'unit'} = \&_convert_unit_type; |
| |
| sub _contents_shortcontents_in_title($) { |
| my $self = shift; |
| |
| my $result = ''; |
| |
| my $document = $self->get_info('document'); |
| my $sections_list; |
| if (defined($document)) { |
| $sections_list = $document->sections_list(); |
| } |
| |
| if (defined($sections_list) |
| and scalar(@{$sections_list}) > 1 |
| and $self->get_conf('CONTENTS_OUTPUT_LOCATION') eq 'after_title') { |
| foreach my $cmdname ('shortcontents', 'contents') { |
| if ($self->get_conf($cmdname)) { |
| my $contents_text = _contents_inline_element($self, $cmdname, undef); |
| if ($contents_text ne '') { |
| $result .= $contents_text; |
| my $rule = $self->get_conf('DEFAULT_RULE'); |
| if (defined($rule)) { |
| $result .= $rule ."\n"; |
| } |
| } |
| } |
| } |
| } |
| return $result; |
| } |
| |
| sub _format_maketile($$) { |
| my ($self, $document) = @_; |
| |
| my $document_info |
| = Texinfo::Convert::Utils::get_document_documentinfo($document); |
| |
| if (defined($document_info)) { |
| my @contents; |
| my $titlepage_text |
| = $self->html_attribute_class('div', ['maketitle-titlepage']).">\n"; |
| foreach my $cmdname ('title', 'subtitle', 'author') { |
| if (exists($document_info->{$cmdname})) { |
| push @contents, @{$document_info->{$cmdname}}; |
| } |
| } |
| my $element = Texinfo::TreeElement::new({'contents' => \@contents}); |
| # we do not need to collect the author commands in documentinfo, so |
| # we use a little trick to initialize the authors number to -1 |
| # to mean that we are in documentinfo |
| _open_quotation_titlepage_stack($self, -1); |
| my $quotation_titlepage_nr = $self->get_shared_conversion_state('quotation', |
| 'quotation_titlepage_stack'); |
| $titlepage_text .= $self->convert_tree($element, 'format maketitle'); |
| $quotation_titlepage_nr--; |
| $self->set_shared_conversion_state('quotation', |
| 'quotation_titlepage_stack', |
| $quotation_titlepage_nr); |
| $titlepage_text .= "</div>\n"; |
| return $titlepage_text; |
| } |
| return undef; |
| } |
| |
| # Convert @titlepage. Falls back to simpletitle. |
| sub _default_format_titlepage($) { |
| my $self = shift; |
| |
| my $titlepage_text; |
| my $global_commands; |
| |
| my $document = $self->get_info('document'); |
| if (defined($document)) { |
| $global_commands = $document->global_commands_information(); |
| } |
| |
| if (defined($global_commands) and exists($global_commands->{'titlepage'})) { |
| # we do not need to collect the author commands in titlepage, so |
| # we use a little trick to initialize the authors number to -1 |
| # to mean that we are in titlepage |
| _open_quotation_titlepage_stack($self, -1); |
| $titlepage_text = $self->convert_tree( |
| Texinfo::TreeElement::new( |
| {'contents' => $global_commands->{'titlepage'}->{'contents'}}), |
| 'convert titlepage'); |
| my $quotation_titlepage_nr = $self->get_shared_conversion_state('quotation', |
| 'quotation_titlepage_stack'); |
| $quotation_titlepage_nr--; |
| $self->set_shared_conversion_state('quotation', |
| 'quotation_titlepage_stack', |
| $quotation_titlepage_nr); |
| } elsif (defined($global_commands) |
| and exists($global_commands->{'maketitle'}) |
| and defined($document)) { |
| $titlepage_text = _format_maketile($self, $document); |
| } else { |
| my $simpletitle_tree = $self->get_info('simpletitle_tree'); |
| if (defined($simpletitle_tree)) { |
| my $simpletitle_command_name |
| = $self->get_info('simpletitle_command_name'); |
| my $title_text |
| = $self->convert_tree_new_formatting_context($simpletitle_tree, |
| "$simpletitle_command_name simpletitle"); |
| $titlepage_text |
| = &{$self->formatting_function('format_heading_text')}($self, |
| $simpletitle_command_name, |
| [$simpletitle_command_name], $title_text, 0); |
| } |
| } |
| my $result = ''; |
| if (defined($titlepage_text)) { |
| $result .= $titlepage_text; |
| my $rule = $self->get_conf('DEFAULT_RULE'); |
| if (defined($rule)) { |
| $result .= $rule."\n"; |
| } |
| } |
| $result .= _contents_shortcontents_in_title($self); |
| return $result; |
| } |
| |
| sub _default_format_title_titlepage($) { |
| my $self = shift; |
| |
| if ($self->get_conf('SHOW_TITLE')) { |
| if ($self->get_conf('USE_TITLEPAGE_FOR_TITLE')) { |
| return &{$self->formatting_function('format_titlepage')}($self); |
| } else { |
| my $result = ''; |
| my $simpletitle_tree = $self->get_info('simpletitle_tree'); |
| if (defined($simpletitle_tree)) { |
| my $simpletitle_command_name |
| = $self->get_info('simpletitle_command_name'); |
| my $title_text |
| = $self->convert_tree_new_formatting_context($simpletitle_tree, |
| "$simpletitle_command_name simpletitle"); |
| $result |
| .= &{$self->formatting_function('format_heading_text')}($self, |
| $simpletitle_command_name, |
| [$simpletitle_command_name], $title_text, 0); |
| } |
| $result .= _contents_shortcontents_in_title($self); |
| return $result; |
| } |
| } |
| return ''; |
| } |
| |
| # for output units, both normal and special |
| sub _default_format_element_footer($$$$;$) { |
| my ($self, $type, $output_unit, $content, $command) = @_; |
| |
| my $result = ''; |
| my $is_top = $self->unit_is_top_output_unit($output_unit); |
| my $next_is_top = (exists($output_unit->{'tree_unit_directions'}->{'next'}) |
| and $self->unit_is_top_output_unit( |
| $output_unit->{'tree_unit_directions'}->{'next'})); |
| my $next_is_special |
| = (exists($output_unit->{'tree_unit_directions'}->{'next'}) |
| and exists($output_unit->{'tree_unit_directions'}->{'next'} |
| ->{'unit_type'}) |
| and $output_unit->{'tree_unit_directions'}->{'next'} |
| ->{'unit_type'} eq 'special_unit'); |
| |
| my $is_end_page = (!exists($output_unit->{'tree_unit_directions'}->{'next'}) |
| or (exists($output_unit->{'unit_filename'}) |
| and $output_unit->{'unit_filename'} |
| ne $output_unit->{'tree_unit_directions'}->{'next'} |
| ->{'unit_filename'} |
| and $self->count_elements_in_filename('remaining', |
| $output_unit->{'unit_filename'}) == 1)); |
| |
| my $is_special = (defined($output_unit->{'unit_type'}) |
| and $output_unit->{'unit_type'} eq 'special_unit'); |
| |
| my $split = $self->get_conf('SPLIT'); |
| if (($is_end_page or $next_is_top or $next_is_special or $is_top) |
| and $self->get_conf('VERTICAL_HEAD_NAVIGATION') |
| and (!defined($split) or $split ne 'node' |
| or $self->get_conf('HEADERS') or $is_special or $is_top)) { |
| $result .= "</td> |
| </tr> |
| </table>"."\n"; |
| } |
| |
| my $buttons; |
| |
| if ($is_end_page) { |
| my $closed_strings = $self->close_registered_sections_level( |
| $self->current_filename(), 0); |
| $result .= join('', @{$closed_strings}); |
| |
| my $split = $self->get_conf('SPLIT'); |
| |
| # setup buttons for navigation footer |
| if (($is_top or $is_special) |
| and ($split or !$self->get_conf('MONOLITHIC')) |
| and (($self->get_conf('HEADERS') |
| or (defined($split) and $split ne 'node')))) { |
| if ($is_top) { |
| $buttons = $self->get_conf('TOP_FOOTER_BUTTONS'); |
| } else { |
| $buttons = $self->get_conf('MISC_BUTTONS'); |
| } |
| } elsif (defined($split) and $split eq 'section') { |
| $buttons = $self->get_conf('SECTION_FOOTER_BUTTONS'); |
| } elsif (defined($split) and $split eq 'chapter') { |
| $buttons = $self->get_conf('CHAPTER_FOOTER_BUTTONS'); |
| } elsif (defined($split) and $split eq 'node') { |
| if ($self->get_conf('HEADERS')) { |
| my $no_footer_word_count; |
| if ($self->get_conf('WORDS_IN_PAGE')) { |
| $content = '' if (!defined($content)); |
| # NOTE it would have been better to skip a leading space, but |
| # it cannot happen as the content should start with an HTML element. |
| # splitting at [\h\v] may have been relevant, but then the result |
| # would be different from XS code result and could give different |
| # results in perl in some cases. |
| # NOTE it seems that NO-BREAK SPACE and NEXT LINE (NEL) may |
| # not be in \h and \v in some case, but not sure when. |
| # It is supposed to be explained but it is not very clear |
| # https://perldoc.perl.org/perlrecharclass#Whitespace |
| # [\h\v]+ does not match on solaris 11 with perl 5.10.1, not sure |
| # why. |
| #my @cnt = split(/[\h\v]+/, $content); |
| # Use an explicit list to match the same in all versions of perl. |
| # TODO starting in Perl v5.14 could be replaced by \s\cK (with /a) |
| # TODO starting in Perl v5.18 could be replaced by \s (with /a) |
| my @cnt = split(/[\t\n\f\r \cK]+/, $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); |
| } |
| } |
| } |
| # NOTE the following condition is almost a duplication of the |
| # condition appearing in end_page except that the file counter |
| # needs not to be 1 |
| if (!exists($output_unit->{'tree_unit_directions'}->{'next'}) |
| or (exists($output_unit->{'unit_filename'}) |
| and $output_unit->{'unit_filename'} |
| ne $output_unit->{'tree_unit_directions'}->{'next'} |
| ->{'unit_filename'})) { |
| my $footnotestyle = $self->get_conf('footnotestyle'); |
| if (!defined($footnotestyle) or $footnotestyle ne 'separate') { |
| $result |
| .= &{$self->formatting_function('format_footnotes_segment')}($self); |
| } |
| } |
| |
| if ($buttons or !$is_end_page |
| or $self->get_conf('PROGRAM_NAME_IN_FOOTER')) { |
| my $rule; |
| my $split = $self->get_conf('SPLIT'); |
| if (!$is_end_page and ($is_top or $next_is_top or ($next_is_special |
| and !$is_special))) { |
| $rule = $self->get_conf('BIG_RULE'); |
| } elsif (!$buttons or $is_top or $is_special |
| or ($is_end_page and defined($split) |
| and ($split eq 'chapter' or $split eq 'section')) |
| or (defined($split) and $split eq 'node' |
| and $self->get_conf('HEADERS'))) { |
| $rule = $self->get_conf('DEFAULT_RULE'); |
| } |
| $result .= "$rule\n" if (defined($rule) and $rule ne ''); |
| } |
| |
| if ($buttons) { |
| my $cmdname; |
| $cmdname = $command->{'cmdname'} if (defined($command) |
| and exists($command->{'cmdname'})); |
| $result .= &{$self->formatting_function('format_navigation_panel')}($self, |
| $buttons, $cmdname, $command); |
| } |
| return $result; |
| } |
| |
| sub _default_format_css_lines($;$) { |
| my ($self, $filename) = @_; |
| |
| return '' if ($self->get_conf('NO_CSS')); |
| |
| my $css_refs = $self->get_conf('CSS_REFS'); |
| my $css_element_classes = $self->html_get_css_elements_classes($filename); |
| my $css_import_lines = $self->css_get_info('imports'); |
| my $css_rule_lines = $self->css_get_info('rules'); |
| |
| return '' if !@$css_import_lines and !@$css_element_classes |
| and !@$css_rule_lines |
| and (!defined($css_refs) or !@$css_refs); |
| |
| my $css_text = "<style type=\"text/css\">\n"; |
| $css_text .= join('', @$css_import_lines) . "\n" |
| if (@$css_import_lines); |
| foreach my $element_class (@$css_element_classes) { |
| my $css_style = $self->css_get_selector_style($element_class); |
| $css_text .= "$element_class {$css_style}\n" |
| if defined($css_style); |
| } |
| $css_text .= join('', @$css_rule_lines) . "\n" |
| if (@$css_rule_lines); |
| $css_text .= "</style>\n"; |
| foreach my $ref (@$css_refs) { |
| $css_text .= $self->close_html_lone_element( |
| '<link rel="stylesheet" type="text/css" href="'. |
| $self->url_protect_url_text($ref).'"')."\n"; |
| } |
| return $css_text; |
| } |
| |
| # Output a list of the nodes immediately below this one |
| sub _mini_toc($$) { |
| my ($self, $section_relations) = @_; |
| |
| my $result = ''; |
| my $entry_index = 0; |
| |
| if (defined($section_relations) |
| and exists($section_relations->{'section_children'}) |
| and scalar(@{$section_relations->{'section_children'}})) { |
| $result .= $self->html_attribute_class('ul', ['mini-toc']).">\n"; |
| |
| foreach my $section_relations |
| (@{$section_relations->{'section_children'}}) { |
| my $section = $section_relations->{'element'}; |
| my $text = $self->command_text($section, 'text_nonumber'); |
| # could happen with empty sectioning command |
| next if (!defined($text) or $text eq ''); |
| |
| $entry_index++; |
| my $accesskey = ''; |
| $accesskey = " accesskey=\"$entry_index\"" |
| if ($self->get_conf('USE_ACCESSKEY') and $entry_index < 10); |
| |
| my $href = $self->command_href($section); |
| $result .= "<li>"; |
| if (defined($href)) { |
| $result .= "<a href=\"$href\"$accesskey>$text</a>"; |
| } else { |
| $result .= $text; |
| } |
| $result .= "</li>\n"; |
| } |
| $result .= "</ul>\n"; |
| } |
| return $result; |
| } |
| |
| # No equivalent function in C, corresponding code is inlined |
| sub _has_contents_or_shortcontents($) { |
| my $self = shift; |
| |
| my $global_commands; |
| |
| my $document = $self->get_info('document'); |
| if (defined($document)) { |
| $global_commands = $document->global_commands_information(); |
| } |
| |
| foreach my $cmdname ('contents', 'shortcontents') { |
| if (defined($global_commands) and exists($global_commands->{$cmdname})) { |
| return 1; |
| } |
| } |
| return 0; |
| } |
| |
| sub _default_format_contents($$;$$) { |
| my ($self, $cmdname, $command, $filename) = @_; |
| |
| $filename = $self->current_filename() if (!defined($filename)); |
| |
| my $document = $self->get_info('document'); |
| my $sections_list; |
| my $sectioning_root; |
| if (defined($document)) { |
| $sections_list = $document->sections_list(); |
| $sectioning_root = $document->sectioning_root(); |
| } |
| return '' |
| if (!defined($sections_list) or !scalar(@$sections_list) |
| # this should not happen with $sections_list as set from Structuring |
| # sectioning_structure, but could happen with another source. |
| # We consider that if sectioning_root is set as usual, all the |
| # fields are set consistently with what sectioning_structure would |
| # have set. |
| or !defined($sectioning_root)); |
| |
| my $is_contents; |
| $is_contents = 1 if ($cmdname eq 'contents'); |
| |
| my $min_root_level = $sectioning_root->{'section_children'}->[0] |
| ->{'element'}->{'extra'}->{'section_level'}; |
| my $max_root_level = $min_root_level; |
| foreach my $top_relations (@{$sectioning_root->{'section_children'}}) { |
| my $top_section = $top_relations->{'element'}; |
| $min_root_level = $top_section->{'extra'}->{'section_level'} |
| if ($top_section->{'extra'}->{'section_level'} < $min_root_level); |
| $max_root_level = $top_section->{'extra'}->{'section_level'} |
| if ($top_section->{'extra'}->{'section_level'} > $max_root_level); |
| } |
| # chapter level elements are considered top-level here. |
| $max_root_level = 1 if ($max_root_level < 1); |
| #print STDERR "ROOT_LEVEL Max: $max_root_level, Min: $min_root_level\n"; |
| my @toc_ul_classes; |
| push @toc_ul_classes, 'toc-numbered-mark' |
| if ($self->get_conf('NUMBER_SECTIONS')); |
| |
| my $result = ''; |
| if ($is_contents and !defined($self->get_conf('BEFORE_TOC_LINES')) |
| or (!$is_contents |
| and !defined($self->get_conf('BEFORE_SHORT_TOC_LINES')))) { |
| $result .= $self->html_attribute_class('div', [$cmdname]).">\n"; |
| } elsif($is_contents) { |
| $result .= $self->get_conf('BEFORE_TOC_LINES'); |
| } else { |
| $result .= $self->get_conf('BEFORE_SHORT_TOC_LINES'); |
| } |
| |
| my $has_toplevel_contents; |
| if (@{$sectioning_root->{'section_children'}} > 1) { |
| $result .= $self->html_attribute_class('ul', \@toc_ul_classes) .">\n"; |
| $has_toplevel_contents = 1; |
| } |
| |
| my $link_to_toc = (!$is_contents and $self->get_conf('SHORT_TOC_LINK_TO_TOC') |
| and ($self->get_conf('contents')) |
| and ($self->get_conf('CONTENTS_OUTPUT_LOCATION') ne 'inline' |
| or _has_contents_or_shortcontents($self))); |
| |
| foreach my $top_relations (@{$sectioning_root->{'section_children'}}) { |
| my $section_relations = $top_relations; |
| SECTION: |
| while (defined($section_relations)) { |
| my $section = $section_relations->{'element'}; |
| if ($section->{'cmdname'} ne 'top') { |
| my $text = $self->command_text($section); |
| my $href; |
| if ($link_to_toc) { |
| $href = $self->command_contents_href($section, 'contents', $filename); |
| } else { |
| $href = $self->command_href($section, $filename); |
| } |
| my $toc_id = $self->command_contents_target($section, $cmdname); |
| if ($text ne '') { |
| # no indenting for shortcontents |
| $result .= (' ' x |
| (2*($section->{'extra'}->{'section_level'} - $min_root_level))) |
| if ($is_contents); |
| $result .= "<li>"; |
| if ($toc_id ne '' or defined($href)) { |
| $result .= "<a"; |
| if ($toc_id ne '') { |
| $result .= " id=\"$toc_id\""; |
| } |
| if (defined($href)) { |
| $result .= " href=\"$href\""; |
| } |
| if (exists($section_relations->{'associated_node'}) |
| and $section_relations->{'associated_node'} |
| ->{'element'}->{'extra'}->{'isindex'}) { |
| $result .= ' rel="index"'; |
| } |
| $result .= ">$text</a>"; |
| } else { |
| $result .= $text; |
| } |
| } |
| } elsif (exists($section_relations->{'section_children'}) |
| and scalar(@{$section_relations->{'section_children'}}) |
| and $has_toplevel_contents) { |
| $result .= "<li>"; |
| } |
| # for shortcontents don't do child if child is not toplevel |
| if (exists($section_relations->{'section_children'}) |
| and ($is_contents |
| or $section->{'extra'}->{'section_level'} < $max_root_level)) { |
| # no indenting for shortcontents |
| $result .= "\n" |
| . ' ' x (2*($section->{'extra'}->{'section_level'} - $min_root_level)) |
| if ($is_contents); |
| $result .= $self->html_attribute_class('ul', \@toc_ul_classes) .">\n"; |
| $section_relations = $section_relations->{'section_children'}->[0]; |
| } elsif (exists($section_relations->{'section_directions'}) |
| and exists($section_relations->{'section_directions'}->{'next'}) |
| and $section->{'cmdname'} ne 'top') { |
| $result .= "</li>\n"; |
| last if ($section_relations eq $top_relations); |
| $section_relations |
| = $section_relations->{'section_directions'}->{'next'}; |
| } else { |
| #last if ($section eq $top_section); |
| if ($section_relations eq $top_relations) { |
| $result .= "</li>\n" unless ($section->{'cmdname'} eq 'top'); |
| last; |
| } |
| while (exists($section_relations->{'section_directions'}) |
| and exists($section_relations->{'section_directions'}->{'up'})) { |
| $section_relations |
| = $section_relations->{'section_directions'}->{'up'}; |
| $section = $section_relations->{'element'}; |
| |
| $result .= "</li>\n" |
| . ' ' x (2*($section->{'extra'}->{'section_level'} - $min_root_level)) |
| . "</ul>"; |
| if ($section_relations eq $top_relations) { |
| $result .= "</li>\n" if ($has_toplevel_contents); |
| last SECTION; |
| } |
| if (exists($section_relations->{'section_directions'}) |
| and exists($section_relations->{'section_directions'} |
| ->{'next'})) { |
| $result .= "</li>\n"; |
| $section_relations |
| = $section_relations->{'section_directions'}->{'next'}; |
| last; |
| } |
| } |
| } |
| } |
| } |
| if (scalar(@{$sectioning_root->{'section_children'}}) > 1) { |
| $result .= "\n</ul>"; |
| } |
| if ($is_contents and !defined($self->get_conf('AFTER_TOC_LINES')) |
| or (!$is_contents |
| and !defined($self->get_conf('AFTER_SHORT_TOC_LINES')))) { |
| $result .= "\n</div>\n"; |
| } elsif ($is_contents) { |
| $result .= $self->get_conf('AFTER_TOC_LINES'); |
| } else { |
| $result .= $self->get_conf('AFTER_SHORT_TOC_LINES'); |
| } |
| return $result; |
| } |
| |
| sub _default_format_program_string($) { |
| my $self = shift; |
| |
| if (defined($self->get_conf('PROGRAM')) |
| and $self->get_conf('PROGRAM') ne '' |
| and defined($self->get_conf('PACKAGE_URL'))) { |
| return $self->convert_tree( |
| $self->cdt('This document was generated on @emph{@today{}} using @uref{{program_homepage}, @emph{{program}}}.', |
| { 'program_homepage' => Texinfo::TreeElement::new( |
| {'text' => $self->get_conf('PACKAGE_URL')}), |
| 'program' => Texinfo::TreeElement::new( |
| {'text' => $self->get_conf('PROGRAM')}) }), |
| 'Tr program string program'); |
| } else { |
| return $self->convert_tree( |
| $self->cdt('This document was generated on @emph{@today{}}.'), |
| 'Tr program string date'); |
| } |
| } |
| |
| sub _default_format_end_file($$$) { |
| my ($self, $filename, $output_unit) = @_; |
| |
| my $result = ''; |
| if ($self->get_conf('PROGRAM_NAME_IN_FOOTER')) { |
| $result .= "<p>\n "; |
| my $open = $self->html_attribute_class('span', ['program-in-footer']); |
| $result .= $open.'>' if ($open ne ''); |
| |
| my $program_string |
| = &{$self->formatting_function('format_program_string')}($self); |
| $result .= $program_string; |
| |
| $result .= '</span>' if ($open ne ''); |
| $result .= "\n</p>"; |
| } |
| $result .= "\n\n"; |
| |
| my $pre_body_close = $self->get_conf('PRE_BODY_CLOSE'); |
| $result .= $pre_body_close if (defined($pre_body_close)); |
| |
| my $jslicenses = $self->get_info('jslicenses'); |
| if (defined($jslicenses) |
| and ((exists($jslicenses->{'infojs'}) |
| and scalar(keys %{$jslicenses->{'infojs'}})) |
| or (($self->get_file_information('mathjax', $filename) |
| or !$self->get_conf('SPLIT')) |
| and (exists($jslicenses->{'mathjax'}) |
| and scalar(keys %{$jslicenses->{'mathjax'}}))))) { |
| my $js_setting = $self->get_conf('JS_WEBLABELS'); |
| my $js_path = $self->get_conf('JS_WEBLABELS_FILE'); |
| if (defined($js_setting) and defined($js_path) |
| and ($js_setting eq 'generate' or $js_setting eq 'reference')) { |
| $result .= |
| '<a href="'.$self->url_protect_url_text($js_path).'" rel="jslicense"><small>' |
| .$self->convert_tree($self->cdt('JavaScript license information'), |
| 'Tr JS license header') |
| .'</small></a>'; |
| } |
| } |
| |
| return "$result |
| </body> |
| </html> |
| "; |
| } |
| |
| sub _root_html_element_attributes_string($) { |
| my $self = shift; |
| |
| if (defined($self->get_conf('HTML_ROOT_ELEMENT_ATTRIBUTES')) |
| and $self->get_conf('HTML_ROOT_ELEMENT_ATTRIBUTES') ne '') { |
| return ' '.$self->get_conf('HTML_ROOT_ELEMENT_ATTRIBUTES'); |
| } |
| return ''; |
| } |
| |
| # This is used for normal output files and other files, like |
| # redirection file headers. $COMMAND is the tree element for |
| # a @node that is being output in the file. |
| sub _file_header_information($$;$) { |
| my ($self, $command, $filename) = @_; |
| |
| my $title; |
| my $command_description; |
| |
| my $title_string = $self->get_info('title_string'); |
| if (defined($command)) { |
| my $command_string; |
| if ($self->get_conf('SECTION_NAME_IN_TITLE') |
| and exists($command->{'cmdname'}) |
| and $command->{'cmdname'} eq 'node') { |
| my $associated_title_command; |
| my $document = $self->get_info('document'); |
| if (defined($document)) { |
| my $nodes_list = $document->nodes_list(); |
| my $node_relations |
| = $nodes_list->[$command->{'extra'}->{'node_number'} -1]; |
| $associated_title_command |
| = $node_relations->{'associated_title_command'}; |
| |
| if (defined($associated_title_command)) { |
| $command_string |
| = $self->command_text($associated_title_command, 'string'); |
| } |
| } |
| } |
| if (!defined($command_string) or $command_string eq '') { |
| # happens for @node and special_unit_element. |
| # Also for @anchor and @namedanchor for redirection files. |
| # TODO use command_name? If SECTION_NAME_IN_TITLE? |
| $command_string = $self->command_text($command, 'string'); |
| } |
| if (defined($command_string) and $command_string ne '' |
| and $command_string ne $title_string) { |
| # TRANSLATORS: sectioning element title for the page header |
| my $title_tree = $self->cdt('{element_text} ({title})', |
| {'title' => |
| Texinfo::TreeElement::new({'text' => $title_string, |
| 'type' => '_converted'}), |
| 'element_text' => |
| Texinfo::TreeElement::new({'text' => $command_string, |
| 'type' => '_converted'})}); |
| |
| my $context_str = 'file_header_title-element-'; |
| if (exists($command->{'cmdname'})) { |
| $context_str .= '@'.$command->{'cmdname'}; |
| } elsif (exists($command->{'type'})) { |
| $context_str .= $command->{'type'}; |
| } |
| # NOTE 'element_title' is not unique although this could be called |
| # for each file. We are in string context, though, so it is |
| # probably not important. |
| $title |
| = $self->convert_tree_new_formatting_context($title_tree, |
| $context_str, $CTXF_string, 'element_title'); |
| } |
| $command_description = $self->command_description($command, 'string'); |
| } |
| $title = $title_string if (!defined($title)); |
| |
| my $keywords = $command_description; |
| $keywords = $title if (not defined($keywords) or $keywords eq ''); |
| |
| my $description = $self->get_info('documentdescription_string'); |
| $description = $command_description |
| if (not defined($description) or $description eq ''); |
| $description = $title |
| if (not defined($description) or $description eq ''); |
| $description = $self->close_html_lone_element( |
| "<meta name=\"description\" content=\"$description\"" ) |
| if ($description ne ''); |
| my $encoding = ''; |
| $encoding |
| = $self->close_html_lone_element( |
| "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=". |
| $self->get_conf('OUTPUT_ENCODING_NAME')."\"" ) |
| if (defined($self->get_conf('OUTPUT_ENCODING_NAME')) |
| and ($self->get_conf('OUTPUT_ENCODING_NAME') ne '')); |
| |
| my $date = $self->get_info('date_in_header'); |
| $date = '' if (!defined($date)); |
| |
| my $css_lines = &{$self->formatting_function('format_css_lines')}($self, |
| $filename); |
| |
| my $doctype = $self->get_conf('DOCTYPE'); |
| $doctype = '' if (!defined($doctype)); |
| my $root_html_element_attributes |
| = _root_html_element_attributes_string($self); |
| my $body_attributes = $self->get_conf('BODY_ELEMENT_ATTRIBUTES'); |
| if (!defined($body_attributes)) { |
| $body_attributes = ''; |
| } elsif ($body_attributes =~ /^[^ ]/) { |
| $body_attributes = ' '.$body_attributes; |
| } |
| if ($self->get_conf('HTML_MATH') and $self->get_conf('HTML_MATH') eq 'mathjax' |
| and $self->get_file_information('mathjax', $filename)) { |
| $body_attributes .= ' class="tex2jax_ignore"'; |
| } |
| my $copying_comment = $self->get_info('copying_comment'); |
| $copying_comment = '' |
| if (not defined($copying_comment)); |
| my $after_body_open = $self->get_conf('AFTER_BODY_OPEN'); |
| $after_body_open = '' if (!defined($after_body_open)); |
| my $program_and_version = $self->get_conf('PACKAGE_AND_VERSION'); |
| $program_and_version = '' if (!defined($program_and_version)); |
| my $program_homepage = $self->get_conf('PACKAGE_URL'); |
| $program_homepage = '' if (!defined($program_homepage)); |
| my $program = $self->get_conf('PROGRAM'); |
| my $generator = ''; |
| if (defined($program) and $program ne '') { |
| $generator = |
| $self->close_html_lone_element( |
| "<meta name=\"Generator\" content=\"$program\"") . "\n"; |
| } |
| |
| my $documentinfo_metadata = $self->get_info('documentinfo_metadata'); |
| $documentinfo_metadata = '' if (not defined($documentinfo_metadata)); |
| |
| my $extra_head = ''; |
| $extra_head = $self->get_conf('EXTRA_HEAD') |
| if (defined($self->get_conf('EXTRA_HEAD'))); |
| |
| if (defined($self->get_conf('INFO_JS_DIR'))) { |
| if (!$self->get_conf('SPLIT')) { |
| $self->converter_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 |
| } |
| |
| my $protected_jsdir = $self->url_protect_url_text($jsdir); |
| |
| $extra_head .= $self->close_html_lone_element( |
| '<link rel="stylesheet" type="text/css" href="'. |
| $protected_jsdir.'info.css"')."\n" |
| .'<script src="'.$protected_jsdir |
| .'modernizr.js" type="text/javascript"></script>'."\n" |
| .'<script src="'.$protected_jsdir |
| .'info.js" type="text/javascript"></script>'; |
| } |
| } |
| if ((defined($self->get_conf('HTML_MATH')) |
| and $self->get_conf('HTML_MATH') eq 'mathjax') |
| and ($self->get_file_information('mathjax', $filename))) { |
| my $mathjax_script = $self->get_conf('MATHJAX_SCRIPT'); |
| |
| my $default_mathjax_configuration = |
| " options: { |
| skipHtmlTags: {'[-]': ['pre']}, // do not skip pre |
| ignoreHtmlClass: 'tex2jax_ignore', |
| processHtmlClass: 'tex2jax_process' |
| }, |
| tex: { |
| processEscapes: false, // do not use \\\$ to produce a literal dollar sign |
| processEnvironments: false, // do not process \\begin{xxx}...\\end{xxx} outside math mode |
| processRefs: false, // do not process \\ref{...} outside of math mode |
| displayMath: [ // start/end delimiter pairs for display math |
| ['\\\\[', '\\\\]'] |
| ], |
| },"; |
| |
| $extra_head .= |
| "<script type='text/javascript'> |
| MathJax = { |
| $default_mathjax_configuration |
| }; |
| "; |
| |
| my $mathjax_configuration = $self->get_conf('MATHJAX_CONFIGURATION'); |
| if (defined($mathjax_configuration)) { |
| $extra_head .= |
| "var MathJax_conf = { |
| $mathjax_configuration |
| }; |
| |
| for (let component in MathJax_conf) { |
| if (!MathJax.hasOwnProperty(component)) { |
| MathJax[component] = MathJax_conf[component]; |
| } else { |
| for (let field in MathJax_conf[component]) { |
| MathJax[component][field] = MathJax_conf[component][field]; |
| } |
| } |
| } |
| "; |
| } |
| |
| $extra_head .= '</script><script type="text/javascript" id="MathJax-script" async |
| src="'.$self->url_protect_url_text($mathjax_script).'"> |
| </script>'; |
| |
| } |
| |
| return ($title, $description, $keywords, $encoding, $date, $css_lines, |
| $doctype, $root_html_element_attributes, $body_attributes, |
| $copying_comment, $after_body_open, $extra_head, |
| $program_and_version, $program_homepage, $program, $generator, |
| $documentinfo_metadata); |
| } |
| |
| sub _get_links($$$$) { |
| my ($self, $filename, $output_unit, $node_command) = @_; |
| |
| my $links = ''; |
| if ($self->get_conf('USE_LINKS')) { |
| my $link_directions = $self->get_conf('LINKS_DIRECTIONS'); |
| return $links if (!defined($link_directions)); |
| foreach my $link_direction (@$link_directions) { |
| my $link_href = $self->from_element_direction($link_direction, 'href', |
| $output_unit, $filename, $node_command); |
| #print STDERR "$link_direction -> " |
| # .(defined($link_href) ? $link_href : 'UNDEF')."\n"; |
| if (defined($link_href) and $link_href ne '') { |
| my $link_string = $self->from_element_direction($link_direction, |
| 'string', $output_unit); |
| my $link_title = ''; |
| $link_title = " title=\"$link_string\"" if (defined($link_string)); |
| my $rel = ''; |
| my $button_rel |
| = $self->direction_string($link_direction, 'rel', 'string'); |
| $rel = " rel=\"".$button_rel.'"' if (defined($button_rel)); |
| $links .= $self->close_html_lone_element( |
| "<link href=\"$link_href\"${rel}${link_title}")."\n"; |
| } |
| } |
| } |
| return $links; |
| } |
| |
| sub _default_format_begin_file($$$) { |
| my ($self, $filename, $output_unit) = @_; |
| |
| my ($node_command, $command_for_title); |
| if (defined($output_unit)) { |
| if (exists($output_unit->{'unit_node'})) { |
| $node_command = $output_unit->{'unit_node'}->{'element'}; |
| } |
| |
| my $element_command = $output_unit->{'unit_command'}; |
| if ($self->get_conf('SPLIT') and defined($element_command)) { |
| $command_for_title = $element_command; |
| } |
| } |
| |
| my ($title, $description, $keywords, $encoding, $date, $css_lines, $doctype, |
| $root_html_element_attributes, $body_attributes, $copying_comment, |
| $after_body_open, $extra_head, $program_and_version, $program_homepage, |
| $program, $generator, $documentinfo_metadata) |
| = _file_header_information($self, $command_for_title, $filename); |
| |
| my $links = _get_links($self, $filename, $output_unit, $node_command); |
| |
| my $keywords_output = ''; |
| if (defined($keywords)) { |
| $keywords_output = $self->close_html_lone_element( |
| "<meta name=\"keywords\" content=\"$keywords\"")."\n"; |
| } |
| |
| my $result = "$doctype |
| <html${root_html_element_attributes}> |
| <!-- Created by $program_and_version, $program_homepage --> |
| <head> |
| $encoding |
| $copying_comment<title>$title</title> |
| |
| $description\n". |
| $keywords_output. |
| $self->close_html_lone_element( |
| "<meta name=\"resource-type\" content=\"document\"")."\n". |
| $self->close_html_lone_element( |
| "<meta name=\"distribution\" content=\"global\"") . "\n" . |
| ${generator} . ${date} . |
| $self->close_html_lone_element( |
| "<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"")."\n". |
| $documentinfo_metadata. |
| " |
| ${links}$css_lines |
| $extra_head |
| </head> |
| |
| <body$body_attributes> |
| $after_body_open"; |
| |
| return $result; |
| } |
| |
| sub _default_format_node_redirection_page($$;$) { |
| my ($self, $command, $filename) = @_; |
| |
| # TODO use command_name? Only if xrefautomaticsectiontitle is on? |
| my $name = $self->command_text($command); |
| my $href = $self->command_href($command, $filename); |
| my $direction = "<a href=\"$href\">$name</a>"; |
| my $string = $self->convert_tree( |
| $self->cdt('The node you are looking for is at {href}.', |
| { 'href' => |
| Texinfo::TreeElement::new({'type' => '_converted', |
| 'text' => $direction })}), |
| 'Tr redirection sentence'); |
| |
| my ($title, $description, $keywords, $encoding, $date, $css_lines, $doctype, |
| $root_html_element_attributes, $body_attributes, $copying_comment, |
| $after_body_open, $extra_head, $program_and_version, $program_homepage, |
| $program, $generator, $documentinfo_metadata) |
| = _file_header_information($self, $command, $filename); |
| |
| my $keywords_output = ''; |
| if (defined($keywords)) { |
| $keywords_output = $self->close_html_lone_element( |
| "<meta name=\"keywords\" content=\"$keywords\"")."\n"; |
| } |
| |
| my $result = "$doctype |
| <html${root_html_element_attributes}> |
| <!-- Created by $program_and_version, $program_homepage --> |
| <!-- This file redirects to the location of a node or anchor --> |
| <head> |
| $encoding |
| $copying_comment<title>$title</title> |
| |
| $description\n". |
| $keywords_output. |
| $self->close_html_lone_element( |
| "<meta name=\"resource-type\" content=\"document\"")."\n". |
| $self->close_html_lone_element( |
| "<meta name=\"distribution\" content=\"global\"") . "\n" . |
| ${generator} . ${date} . "$css_lines\n". |
| $self->close_html_lone_element( |
| "<meta http-equiv=\"Refresh\" content=\"0; url=$href\"")."\n". |
| $self->close_html_lone_element( |
| "<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"")."\n". |
| $documentinfo_metadata. |
| "$extra_head |
| </head> |
| |
| <body$body_attributes> |
| $after_body_open |
| <p>$string</p> |
| </body> |
| "; |
| return $result; |
| } |
| |
| sub _default_format_single_footnote($$$$$$) { |
| my ($self, $command, $footid, $number_in_doc, $href, $mark) = @_; |
| |
| my $footnote_text |
| = $self->convert_tree_new_formatting_context($command->{'contents'}->[0], |
| "$command->{'cmdname'} $number_in_doc $footid"); |
| chomp ($footnote_text); |
| $footnote_text .= "\n"; |
| |
| return $self->html_attribute_class('h5', ['footnote-body-heading']) . '>'. |
| "<a id=\"$footid\" href=\"$href\">($mark)</a></h5>\n" . $footnote_text; |
| } |
| |
| sub _default_format_footnotes_sequence($) { |
| my $self = shift; |
| |
| my $pending_footnotes = $self->get_pending_footnotes(); |
| my $result = ''; |
| foreach my $pending_footnote_info_array (@$pending_footnotes) { |
| my ($command, $footid, $docid, $number_in_doc, |
| $footnote_location_filename, $multi_expanded_region) |
| = @$pending_footnote_info_array; |
| my $footnote_location_href = $self->footnote_location_href($command, undef, |
| $docid, $footnote_location_filename); |
| |
| my $footnote_mark; |
| if ($self->get_conf('NUMBER_FOOTNOTES')) { |
| $footnote_mark = $number_in_doc; |
| } else { |
| $footnote_mark = $self->get_conf('NO_NUMBER_FOOTNOTE_SYMBOL'); |
| $footnote_mark = '' if (!defined($footnote_mark)); |
| } |
| |
| # NOTE the @-commands in @footnote that are formatted differently depending |
| # on multi_expanded_region($self) cannot know that the original context |
| # of the @footnote in the main document was $multi_expanded_region. |
| # We do not want to set multi_expanded in customizable code. However, it |
| # could be possible to set a shared_conversion_state based on $multi_expanded_region |
| # and have all the conversion functions calling multi_expanded_region($self) |
| # also check the shared_conversion_state. The special situations |
| # with those @-commands in @footnote in multi expanded |
| # region do not justify this additional code and complexity. The consequences |
| # should only be redundant anchors HTML elements. |
| |
| $result .= &{$self->formatting_function('format_single_footnote')}($self, |
| $command, $footid, $number_in_doc, |
| $footnote_location_href, $footnote_mark); |
| } |
| return $result; |
| } |
| |
| sub _default_format_footnotes_segment($) { |
| my $self = shift; |
| |
| my $foot_lines |
| = &{$self->formatting_function('format_footnotes_sequence')}($self); |
| return '' if ($foot_lines eq ''); |
| my $class = $self->special_unit_info('class', 'footnotes'); |
| my $result = $self->html_attribute_class('div', [$class.'-segment']).">\n"; |
| $result .= $self->get_conf('DEFAULT_RULE') . "\n" |
| if (defined($self->get_conf('DEFAULT_RULE')) |
| and $self->get_conf('DEFAULT_RULE') ne ''); |
| my $footnote_heading = $self->special_unit_info_text('heading', 'footnotes'); |
| my $level = $self->get_conf('FOOTNOTE_END_HEADER_LEVEL'); |
| $result .= &{$self->formatting_function('format_heading_text')}($self, undef, |
| [$class.'-heading'], $footnote_heading, $level)."\n"; |
| $result .= $foot_lines; |
| $result .= "</div>\n"; |
| return $result; |
| } |
| |
| sub _default_format_special_body_about($$$) { |
| my ($self, $special_type, $element) = @_; |
| |
| my $about = ''; |
| if ($self->get_conf('PROGRAM_NAME_IN_ABOUT')) { |
| $about .= "<p>\n "; |
| $about .= &{$self->formatting_function('format_program_string')}($self); |
| $about .= "\n</p>\n"; |
| } |
| |
| $about .= "<p>\n"; |
| |
| my $buttons = $self->get_conf('SECTION_BUTTONS'); |
| |
| if (!defined($buttons)) { |
| $about .= $self->convert_tree( |
| $self->cdt('There are no buttons for this document.'), 'ABOUT'). "\n"; |
| $about .= "</p>\n"; |
| return $about; |
| } |
| |
| $about .= $self->convert_tree( |
| $self->cdt(' The buttons in the navigation panels have the following meaning:'), |
| 'ABOUT') |
| . "\n"; |
| my $table = $self->html_attribute_class('table', ['direction-about']).'>'; |
| $about .= <<EOT; |
| </p> |
| $table |
| <tr> |
| EOT |
| my $button_th = $self->html_attribute_class('th', |
| ['button-direction-about']).'>'; |
| my $name_th = $self->html_attribute_class('th', |
| ['name-direction-about']).'>'; |
| my $description_th = $self->html_attribute_class('th', |
| ['description-direction-about']).'>'; |
| my $example_th = $self->html_attribute_class('th', |
| ['example-direction-about']).'>'; |
| # TRANSLATORS: direction column header in the navigation help |
| $about .= " $button_th " |
| . $self->convert_tree($self->cdt('Button'), 'ABOUT') |
| ." </th>\n". |
| # TRANSLATORS: button label column header in the navigation help |
| " $name_th " . $self->convert_tree($self->cdt('Name'), 'ABOUT') |
| . " </th>\n" . |
| # TRANSLATORS: direction description column header in the navigation help |
| " $description_th " . $self->convert_tree($self->cdt('Go to'), 'ABOUT') |
| . " </th>\n" . |
| # TRANSLATORS: section reached column header in the navigation help |
| " $example_th " |
| . $self->convert_tree($self->cdt('From 1.2.3 go to'), 'ABOUT') |
| ."</th>\n". " </tr>\n"; |
| |
| my $active_icons; |
| if ($self->get_conf('ICONS')) { |
| $active_icons = $self->get_conf('ACTIVE_ICONS'); |
| } |
| |
| foreach my $button_spec (@{$buttons}) { |
| next if (defined($self->global_direction_text($button_spec)) |
| or ref($button_spec) eq 'CODE' |
| or ref($button_spec) eq 'SCALAR' |
| or (ref($button_spec) eq 'ARRAY' and scalar(@$button_spec) != 2)); |
| my $direction; |
| if (ref($button_spec) eq 'ARRAY') { |
| $direction = $button_spec->[0]; |
| } else { |
| $direction = $button_spec; |
| } |
| $about .= " <tr>\n ".$self->html_attribute_class('td', |
| ['button-direction-about']) .'>'; |
| # if the button spec is an array we do not know what the button |
| # looks like, so we do not show the button but still show explanations. |
| if (ref($button_spec) ne 'ARRAY') { |
| if ($active_icons and $active_icons->{$direction}) { |
| my $button_name_string |
| = $self->direction_string($direction, 'button', 'string'); |
| $about |
| .= &{$self->formatting_function('format_button_icon_img')}($self, |
| $button_name_string, $active_icons->{$direction}) |
| } else { |
| my $direction_text = $self->direction_string($direction, 'text'); |
| $direction_text = '' if (!defined($direction_text)); |
| $about .= ' ['. $direction_text .'] '; |
| } |
| } |
| $about .= "</td>\n"; |
| # same order for getting the direction strings as in C code |
| my $button_name = $self->direction_string($direction, 'button'); |
| $button_name = '' if (!defined($button_name)); |
| my $direction_description |
| = $self->direction_string($direction, 'description'); |
| $direction_description = '' if (!defined($direction_description)); |
| my $direction_example = $self->direction_string($direction, 'example'); |
| $direction_example = '' if (!defined($direction_example)); |
| my $description_td = $self->html_attribute_class('td', |
| ['description-direction-about']).'>'; |
| my $example_td = $self->html_attribute_class('td', |
| ['example-direction-about']).'>'; |
| $about .= |
| ' '.$self->html_attribute_class('td', ['name-direction-about']).'>' |
| ."$button_name</td> |
| ${description_td}$direction_description</td> |
| ${example_td}$direction_example</td> |
| </tr> |
| "; |
| } |
| |
| $about .= <<EOT; |
| </table> |
| |
| <p> |
| EOT |
| $about .= $self->convert_tree( |
| $self->cdt(' where the @strong{ Example } assumes that the current position is at @strong{ Subsubsection One-Two-Three } of a document of the following structure:'), |
| 'ABOUT') . "\n"; |
| |
| # where the <strong> Example </strong> assumes that the current position |
| # is at <strong> Subsubsection One-Two-Three </strong> of a document of |
| # the following structure: |
| $about .= <<EOT; |
| </p> |
| |
| <ul> |
| EOT |
| my $non_breaking_space = $self->get_info('non_breaking_space'); |
| # TRANSLATORS: example name of section for section 1 |
| $about .= ' <li> 1. ' . $self->convert_tree($self->cdt('Section One'), |
| 'ABOUT') . "\n" . |
| " <ul>\n" . |
| # TRANSLATORS: example name of section for section 1.1 |
| ' <li>1.1 ' . $self->convert_tree($self->cdt('Subsection One-One'), |
| 'ABOUT') . "\n"; |
| $about .= <<EOT; |
| <ul> |
| <li>...</li> |
| </ul> |
| </li> |
| EOT |
| $about .= ' <li>1.2 ' . |
| # TRANSLATORS: example name of section for section 1.2 |
| $self->convert_tree($self->cdt('Subsection One-Two'), 'ABOUT') |
| . "\n" . |
| " <ul>\n" . |
| ' <li>1.2.1 ' . |
| # TRANSLATORS: example name of section for section 1.2.1 |
| $self->convert_tree($self->cdt('Subsubsection One-Two-One'), 'ABOUT') |
| . "</li>\n" . |
| ' <li>1.2.2 ' . |
| # TRANSLATORS: example name of section for section 1.2.2 |
| $self->convert_tree($self->cdt('Subsubsection One-Two-Two'), 'ABOUT') |
| . "</li>\n" . |
| ' <li>1.2.3 ' . |
| # TRANSLATORS: example name of section for section 1.2.3 |
| $self->convert_tree($self->cdt('Subsubsection One-Two-Three'), |
| 'ABOUT') |
| . " $non_breaking_space $non_breaking_space\n" |
| . |
| ' <strong><== ' . |
| $self->convert_tree($self->cdt('Current Position'), 'ABOUT') |
| . " </strong></li>\n" . |
| ' <li>1.2.4 ' . |
| # TRANSLATORS: example name of section for section 1.2.4 |
| $self->convert_tree($self->cdt('Subsubsection One-Two-Four'), 'ABOUT') |
| . "</li>\n" . |
| " </ul>\n" . |
| " </li>\n" . |
| ' <li>1.3 ' . |
| # TRANSLATORS: example name of section for section 1.3 |
| $self->convert_tree($self->cdt('Subsection One-Three'), 'ABOUT') |
| . "\n"; |
| $about .= <<EOT; |
| <ul> |
| <li>...</li> |
| </ul> |
| </li> |
| EOT |
| $about .= ' <li>1.4 ' . |
| # TRANSLATORS: example name of section for section 1.4 |
| $self->convert_tree($self->cdt('Subsection One-Four'), 'ABOUT') |
| . "</li>\n"; |
| |
| $about .= <<EOT; |
| </ul> |
| </li> |
| </ul> |
| EOT |
| return $about; |
| } |
| |
| sub _default_format_special_body_contents($$$) { |
| my ($self, $special_type, $element) = @_; |
| |
| return &{$self->formatting_function('format_contents')}($self, 'contents'); |
| } |
| |
| sub _default_format_special_body_shortcontents($$$) { |
| my ($self, $special_type, $element) = @_; |
| |
| return &{$self->formatting_function('format_contents')}($self, |
| 'shortcontents'); |
| } |
| |
| sub _default_format_special_body_footnotes($$$) { |
| my ($self, $special_type, $element) = @_; |
| |
| return &{$self->formatting_function('format_footnotes_sequence')}($self); |
| } |
| |
| # can be set through Texinfo::Config::texinfo_register_file_id_setting_function |
| my %customizable_file_id_setting_references; |
| foreach my $customized_reference ('external_target_split_name', |
| 'external_target_non_split_name', |
| 'label_target_name', 'node_file_name', |
| 'redirection_file_names', |
| 'sectioning_command_target_name', |
| 'special_unit_target_file_name', 'unit_file_name') { |
| $customizable_file_id_setting_references{$customized_reference} = 1; |
| } |
| |
| # Functions accessed with e.g. 'format_heading_text'. |
| # used in Texinfo::Config |
| %default_formatting_references = ( |
| 'format_begin_file' => \&_default_format_begin_file, |
| 'format_button' => \&_default_format_button, |
| 'format_button_icon_img' => \&_default_format_button_icon_img, |
| 'format_css_lines' => \&_default_format_css_lines, |
| 'format_comment' => \&_default_format_comment, |
| 'format_contents' => \&_default_format_contents, |
| 'format_element_header' => \&_default_format_element_header, |
| 'format_element_footer' => \&_default_format_element_footer, |
| 'format_end_file' => \&_default_format_end_file, |
| 'format_footnotes_segment' => \&_default_format_footnotes_segment, |
| 'format_footnotes_sequence' => \&_default_format_footnotes_sequence, |
| 'format_single_footnote' => \&_default_format_single_footnote, |
| 'format_heading_text' => \&_default_format_heading_text, |
| 'format_navigation_header' => \&_default_format_navigation_header, |
| 'format_navigation_panel' => \&_default_format_navigation_panel, |
| 'format_node_redirection_page' => \&_default_format_node_redirection_page, |
| 'format_program_string' => \&_default_format_program_string, |
| 'format_protect_text' => \&_default_format_protect_text, |
| 'format_separate_anchor' => \&_default_format_separate_anchor, |
| 'format_titlepage' => \&_default_format_titlepage, |
| 'format_title_titlepage' => \&_default_format_title_titlepage, |
| 'format_translate_message' => undef, |
| ); |
| |
| # not up for customization |
| %default_css_string_formatting_references = ( |
| 'format_protect_text' => \&_default_css_string_format_protect_text, |
| ); |
| |
| %defaults_format_special_unit_body_contents = ( |
| 'contents' => \&_default_format_special_body_contents, |
| 'about' => \&_default_format_special_body_about, |
| 'footnotes' => \&_default_format_special_body_footnotes, |
| 'shortcontents' => \&_default_format_special_body_shortcontents, |
| ); |
| |
| |
| |
| # converter API implementation for function without XS overrides. |
| |
| sub converter_initialize($) { |
| my $self = shift; |
| |
| # beginning of initialization done either in Perl or XS |
| _html_converter_initialize_beginning($self); |
| |
| $self->{'output_units_conversion'} = {}; |
| my $customized_output_units_conversion |
| = Texinfo::Config::GNUT_get_output_units_conversion(); |
| $customized_output_units_conversion = {} |
| if (!defined($customized_output_units_conversion)); |
| foreach my $type (keys(%default_output_units_conversion)) { |
| if (exists($customized_output_units_conversion->{$type})) { |
| $self->{'output_units_conversion'}->{$type} |
| = $customized_output_units_conversion->{$type}; |
| } else { |
| $self->{'output_units_conversion'}->{$type} |
| = $default_output_units_conversion{$type}; |
| } |
| } |
| |
| $self->{'types_conversion'} = {}; |
| my $customized_types_conversion |
| = Texinfo::Config::GNUT_get_types_conversion(); |
| $customized_types_conversion = {} |
| if (!defined($customized_types_conversion)); |
| foreach my $type (keys(%default_types_conversion)) { |
| if (exists($customized_types_conversion->{$type})) { |
| $self->{'types_conversion'}->{$type} |
| = $customized_types_conversion->{$type}; |
| } else { |
| $self->{'types_conversion'}->{$type} |
| = $default_types_conversion{$type}; |
| } |
| } |
| |
| $self->{'types_open'} = {}; |
| my $customized_types_open = Texinfo::Config::GNUT_get_types_open(); |
| $customized_types_open = {} if (!defined($customized_types_open)); |
| foreach my $type (keys(%default_types_conversion)) { |
| if (exists($customized_types_open->{$type})) { |
| $self->{'types_open'}->{$type} |
| = $customized_types_open->{$type}; |
| } elsif (exists($default_types_open{$type})) { |
| $self->{'types_open'}->{$type} |
| = $default_types_open{$type}; |
| } |
| } |
| |
| $self->{'code_types'} = {}; |
| foreach my $type (keys(%default_code_types)) { |
| $self->{'code_types'}->{$type} = $default_code_types{$type}; |
| } |
| $self->{'pre_class_types'} = {}; |
| foreach my $type (keys(%default_pre_class_types)) { |
| $self->{'pre_class_types'}->{$type} = $default_pre_class_types{$type}; |
| } |
| |
| my $customized_code_types = Texinfo::Config::GNUT_get_types_code_info(); |
| if (defined($customized_code_types)) { |
| foreach my $type (keys(%$customized_code_types)) { |
| $self->{'code_types'}->{$type} = $customized_code_types->{$type}; |
| } |
| } |
| |
| my $customized_pre_class_types = Texinfo::Config::GNUT_get_types_pre_class(); |
| if (defined($customized_pre_class_types)) { |
| foreach my $type (keys(%$customized_pre_class_types)) { |
| $self->{'pre_class_types'}->{$type} |
| = $customized_pre_class_types->{$type}; |
| } |
| } |
| |
| $self->{'upper_case_commands'} = {}; |
| foreach my $cmdname (keys(%default_upper_case_commands)) { |
| $self->{'upper_case_commands'}->{$cmdname} |
| = $default_upper_case_commands{$cmdname}; |
| } |
| my $customized_upper_case_commands |
| = Texinfo::Config::GNUT_get_upper_case_commands_info(); |
| if (defined($customized_upper_case_commands)) { |
| foreach my $cmdname (keys(%$customized_upper_case_commands)) { |
| $self->{'upper_case_commands'}->{$cmdname} |
| = $customized_upper_case_commands->{$cmdname}; |
| } |
| } |
| |
| $self->{'commands_conversion'} = {}; |
| my $customized_commands_conversion |
| = Texinfo::Config::GNUT_get_commands_conversion(); |
| $customized_commands_conversion = {} |
| if (!defined($customized_commands_conversion)); |
| foreach my $cmdname (keys(%line_commands), keys(%brace_commands), |
| keys (%block_commands), keys(%nobrace_commands)) { |
| if (exists($customized_commands_conversion->{$cmdname})) { |
| $self->{'commands_conversion'}->{$cmdname} |
| = $customized_commands_conversion->{$cmdname}; |
| } else { |
| my $format_menu = $self->get_conf('FORMAT_MENU'); |
| if ($format_menu ne 'menu' and $format_menu ne 'menu_no_detailmenu' |
| and ($cmdname eq 'menu' or $cmdname eq 'detailmenu')) { |
| $self->{'commands_conversion'}->{$cmdname} = undef; |
| } elsif (exists($format_raw_commands{$cmdname}) |
| and !$self->{'expanded_formats'}->{$cmdname}) { |
| $self->{'commands_conversion'}->{$cmdname} = undef; |
| } elsif (exists($default_commands_conversion{$cmdname})) { |
| $self->{'commands_conversion'}->{$cmdname} |
| = $default_commands_conversion{$cmdname}; |
| } |
| } |
| } |
| |
| $self->{'commands_open'} = {}; |
| my $customized_commands_open |
| = Texinfo::Config::GNUT_get_commands_open(); |
| $customized_commands_open = {} if (!defined($customized_commands_open)); |
| foreach my $cmdname (keys(%line_commands), keys(%brace_commands), |
| keys (%block_commands), keys(%nobrace_commands)) { |
| if (exists($customized_commands_open->{$cmdname})) { |
| $self->{'commands_open'}->{$cmdname} |
| = $customized_commands_open->{$cmdname}; |
| } elsif (exists($default_commands_open{$cmdname})) { |
| $self->{'commands_open'}->{$cmdname} |
| = $default_commands_open{$cmdname}; |
| } |
| } |
| |
| # get all the customization |
| my %style_commands_customized_formatting_info; |
| foreach my $cmdname (keys(%default_style_commands_formatting)) { |
| foreach my $context (@style_commands_contexts) { |
| my $style_commands_formatting_info |
| = Texinfo::Config::GNUT_get_style_command_formatting($cmdname, $context); |
| if (defined($style_commands_formatting_info)) { |
| if (!exists($style_commands_customized_formatting_info{$cmdname})) { |
| $style_commands_customized_formatting_info{$cmdname} = {}; |
| } |
| $style_commands_customized_formatting_info{$cmdname}->{$context} |
| = $style_commands_formatting_info; |
| } |
| } |
| } |
| |
| $self->{'style_commands_formatting'} = {}; |
| foreach my $cmdname (keys(%default_style_commands_formatting)) { |
| $self->{'style_commands_formatting'}->{$cmdname} = {}; |
| foreach my $context (@style_commands_contexts) { |
| if (exists($style_commands_customized_formatting_info{$cmdname}) |
| and $style_commands_customized_formatting_info{$cmdname}->{$context}) { |
| $self->{'style_commands_formatting'}->{$cmdname}->{$context} |
| = $style_commands_customized_formatting_info{$cmdname}->{$context}; |
| } elsif (exists($default_style_commands_formatting{$cmdname}->{$context})) { |
| $self->{'style_commands_formatting'}->{$cmdname}->{$context} |
| = $default_style_commands_formatting{$cmdname}->{$context}; |
| } |
| } |
| } |
| |
| my %customized_accent_entities; |
| |
| foreach my $accent_command |
| (keys(%Texinfo::Convert::Converter::xml_accent_entities)) { |
| my ($accent_command_entity, $accent_command_text_with_entities) |
| = Texinfo::Config::GNUT_get_accent_command_formatting($accent_command); |
| if (defined($accent_command_entity) |
| or defined($accent_command_text_with_entities)) { |
| $customized_accent_entities{$accent_command} = [$accent_command_entity, |
| $accent_command_text_with_entities]; |
| } |
| } |
| |
| $self->{'accent_entities'} = {}; |
| foreach my $accent_command |
| (keys(%Texinfo::Convert::Converter::xml_accent_entities)) { |
| $self->{'accent_entities'}->{$accent_command} = []; |
| |
| my ($accent_command_entity, $accent_command_text_with_entities); |
| if (exists($customized_accent_entities{$accent_command})) { |
| ($accent_command_entity, $accent_command_text_with_entities) |
| = @{$customized_accent_entities{$accent_command}}; |
| } |
| |
| if (not defined($accent_command_entity) |
| and defined($Texinfo::Convert::Converter::xml_accent_text_with_entities{ |
| $accent_command})) { |
| $accent_command_entity |
| = $Texinfo::Convert::Converter::xml_accent_entities{$accent_command}; |
| } |
| if (not defined($accent_command_text_with_entities) |
| and defined($Texinfo::Convert::Converter::xml_accent_text_with_entities{ |
| $accent_command})) { |
| $accent_command_text_with_entities |
| = $Texinfo::Convert::Converter::xml_accent_text_with_entities{$accent_command}; |
| } |
| # an empty string means no formatting |
| if (defined($accent_command_entity)) { |
| $self->{'accent_entities'}->{$accent_command} = [$accent_command_entity, |
| $accent_command_text_with_entities]; |
| } |
| } |
| #print STDERR Data::Dumper->Dump([$self->{'accent_entities'}]); |
| |
| # get customization only at that point, as the defaults may be changed |
| # with the encoding |
| my $customized_no_arg_commands_formatting = {}; |
| foreach my $cmdname (keys(%{$default_no_arg_commands_formatting{'normal'}})) { |
| $customized_no_arg_commands_formatting->{$cmdname} = {}; |
| foreach my $context (@no_args_commands_contexts, 'translated_to_convert') { |
| my $no_arg_command_customized_formatting |
| = Texinfo::Config::GNUT_get_no_arg_command_formatting($cmdname, |
| $context); |
| if (defined($no_arg_command_customized_formatting)) { |
| $customized_no_arg_commands_formatting->{$cmdname}->{$context} |
| = $no_arg_command_customized_formatting; |
| } |
| } |
| } |
| |
| $self->{'customized_no_arg_commands_formatting'} |
| = $customized_no_arg_commands_formatting; |
| |
| $self->{'file_id_setting'} = {}; |
| my $customized_file_id_setting_references |
| = Texinfo::Config::GNUT_get_file_id_setting_references(); |
| if (defined($customized_file_id_setting_references)) { |
| # first check the validity of the names |
| foreach my $custom_file_id_setting |
| (sort(keys(%{$customized_file_id_setting_references}))) { |
| if (!exists($customizable_file_id_setting_references{ |
| $custom_file_id_setting})) { |
| $self->converter_document_warn( |
| sprintf(__("Unknown file and id setting function: %s"), |
| $custom_file_id_setting)); |
| } else { |
| $self->{'file_id_setting'}->{$custom_file_id_setting} |
| = $customized_file_id_setting_references->{$custom_file_id_setting}; |
| } |
| } |
| } |
| |
| my $customized_formatting_references |
| = Texinfo::Config::GNUT_get_formatting_references(); |
| # first check that all the customized_formatting_references |
| # are in default_formatting_references |
| if (defined($customized_formatting_references)) { |
| foreach my $custom_formatting_ref |
| (sort(keys(%{$customized_formatting_references}))) { |
| if (!exists($default_formatting_references{$custom_formatting_ref})) { |
| $self->converter_document_warn( |
| sprintf(__("Unknown formatting function: %s"), |
| $custom_formatting_ref)); |
| } |
| } |
| } else { |
| $customized_formatting_references = {}; |
| } |
| |
| $self->{'formatting_function'} = {}; |
| foreach my $formatting_reference (keys(%default_formatting_references)) { |
| if (defined($customized_formatting_references->{$formatting_reference})) { |
| $self->{'formatting_function'}->{$formatting_reference} |
| = $customized_formatting_references->{$formatting_reference}; |
| } else { |
| $self->{'formatting_function'}->{$formatting_reference} |
| = $default_formatting_references{$formatting_reference}; |
| } |
| } |
| |
| my $customized_special_unit_info |
| = Texinfo::Config::GNUT_get_special_unit_info(); |
| $customized_special_unit_info = {} |
| if (!defined($customized_special_unit_info)); |
| |
| # Note that with XS this information is not actually used, as |
| # it is only accessed through functions that are overriden |
| $self->{'special_unit_info'} = {}; |
| foreach my $type (keys(%default_special_unit_info)) { |
| $self->{'special_unit_info'}->{$type} = {}; |
| foreach my $special_unit_variety |
| (keys(%{$default_special_unit_info{$type}})) { |
| if (exists($customized_special_unit_info->{$type}) |
| and exists($customized_special_unit_info |
| ->{$type}->{$special_unit_variety})) { |
| $self->{'special_unit_info'}->{$type}->{$special_unit_variety} |
| = $customized_special_unit_info->{$type}->{$special_unit_variety}; |
| } else { |
| $self->{'special_unit_info'}->{$type}->{$special_unit_variety} |
| = $default_special_unit_info{$type}->{$special_unit_variety}; |
| } |
| } |
| } |
| |
| # Note that with XS this information is not actually used, as |
| # it is only accessed through functions that are overriden |
| $self->{'translated_special_unit_info_texinfo'} = {}; |
| $self->{'translated_special_unit_info_tree'} = {}; |
| foreach my $type (keys(%default_translated_special_unit_info)) { |
| $self->{'translated_special_unit_info_texinfo'}->{$type} = {}; |
| $self->{'translated_special_unit_info_tree'}->{$type} = {}; |
| foreach my $special_unit_variety |
| (keys(%{$default_translated_special_unit_info{$type}})) { |
| if (exists($customized_special_unit_info->{$type}) |
| and exists($customized_special_unit_info |
| ->{$type}->{$special_unit_variety})) { |
| $self->{'translated_special_unit_info_texinfo'}->{$type} |
| ->{$special_unit_variety} |
| = $customized_special_unit_info->{$type}->{$special_unit_variety}; |
| } else { |
| $self->{'translated_special_unit_info_texinfo'}->{$type} |
| ->{$special_unit_variety} |
| = $default_translated_special_unit_info{$type} |
| ->{$special_unit_variety}; |
| } |
| } |
| } |
| |
| my $customized_special_unit_body |
| = Texinfo::Config::GNUT_get_formatting_special_unit_body_references(); |
| |
| $self->{'special_unit_body'} = {}; |
| foreach my $special_unit_variety (keys(%defaults_format_special_unit_body_contents)) { |
| $self->{'special_unit_body'}->{$special_unit_variety} |
| = $defaults_format_special_unit_body_contents{$special_unit_variety}; |
| } |
| foreach my $special_unit_variety (keys(%$customized_special_unit_body)) { |
| $self->{'special_unit_body'}->{$special_unit_variety} |
| = $customized_special_unit_body->{$special_unit_variety}; |
| } |
| |
| # "directions" not associated to output units, but associated to text. |
| $self->{'global_texts_directions'} = {}; |
| foreach my $direction (@text_directions_order) { |
| $self->{'global_texts_directions'}->{$direction} = 1; |
| } |
| |
| $self->{'all_directions'} = {}; |
| foreach my $direction (@all_directions_except_special_units) { |
| $self->{'all_directions'}->{$direction} = 1; |
| } |
| |
| $self->{'customized_text_directions'} |
| = Texinfo::Config::GNUT_get_text_directions(); |
| |
| if (defined($self->{'customized_text_directions'})) { |
| foreach my $direction (keys(%{$self->{'customized_text_directions'}})) { |
| if (!exists($self->{'all_directions'}->{$direction})) { |
| $self->{'global_texts_directions'}->{$direction} = 1; |
| $self->{'all_directions'}->{$direction} = 1; |
| } |
| } |
| } |
| |
| $self->{'customized_global_directions'} |
| = Texinfo::Config::GNUT_get_global_directions(); |
| |
| if (defined($self->{'customized_global_directions'})) { |
| foreach my $direction (keys(%{$self->{'customized_global_directions'}})) { |
| if (exists($self->{'all_directions'}->{$direction}) |
| and not grep {$_ eq $direction} @global_directions_order) { |
| $self->converter_document_warn(sprintf(__( |
| "keep direction %s, do not set to `%s'"), $direction, |
| $self->{'customized_global_directions'}->{$direction}) |
| ); |
| delete $self->{'customized_global_directions'}->{$direction}; |
| } else { |
| $self->{'all_directions'}->{$direction} = 1; |
| } |
| } |
| } |
| |
| # customized_global_directions are not used further here, as the output |
| # unit need to be found with the document |
| |
| foreach my $variety (keys(%{$self->{'special_unit_info'}->{'direction'}})) { |
| my $direction = $self->{'special_unit_info'}->{'direction'}->{$variety}; |
| if (defined($direction)) { |
| $self->{'all_directions'}->{$direction} = 1; |
| } |
| } |
| #print STDERR join('|', sort(keys(%all_directions)))."\n"; |
| |
| my $customized_direction_strings |
| = Texinfo::Config::GNUT_get_direction_string_info(); |
| $customized_direction_strings = {} |
| if (!defined($customized_direction_strings)); |
| |
| # Fill the translated direction strings information, corresponding to: |
| # - strings already converted |
| # - strings not already converted |
| # Each of those types of translated strings are translated later on |
| # and the translated values are put in $self->{'direction_strings'}. |
| $self->{'translated_direction_strings'} = {}; |
| foreach my $string_type (keys(%default_translated_directions_strings)) { |
| $self->{'translated_direction_strings'}->{$string_type} = {}; |
| foreach my $direction (keys(%{$self->{'all_directions'}})) { |
| if (exists($customized_direction_strings->{$string_type}) |
| and exists($customized_direction_strings->{$string_type}->{$direction})) { |
| $self->{'translated_direction_strings'}->{$string_type}->{$direction} |
| = $customized_direction_strings->{$string_type}->{$direction}; |
| } else { |
| if (exists($default_translated_directions_strings{$string_type} |
| ->{$direction}) |
| and exists($default_translated_directions_strings{$string_type} |
| ->{$direction}->{'converted'})) { |
| $self->{'translated_direction_strings'}->{$string_type} |
| ->{$direction} = {'converted' => {}}; |
| foreach my $context ('normal', 'string') { |
| $self->{'translated_direction_strings'}->{$string_type} |
| ->{$direction}->{'converted'}->{$context} |
| = $default_translated_directions_strings{$string_type} |
| ->{$direction}->{'converted'}; |
| } |
| } else { |
| $self->{'translated_direction_strings'}->{$string_type}->{$direction} |
| = $default_translated_directions_strings{$string_type}->{$direction}; |
| } |
| } |
| } |
| } |
| |
| # the customization information are not used further here, as |
| # substitute_html_non_breaking_space is used and it depends on the document |
| $self->{'customized_direction_strings'} = $customized_direction_strings; |
| |
| $self->{'stage_handlers'} = Texinfo::Config::GNUT_get_stage_handlers(); |
| |
| _XS_html_converter_get_customization($self, |
| \%default_formatting_references, |
| \%default_css_string_formatting_references, |
| \%default_commands_open, |
| \%default_commands_conversion, |
| \%default_css_string_commands_conversion, |
| \%default_types_open, |
| \%default_types_conversion, |
| \%default_css_string_types_conversion, |
| \%default_output_units_conversion, |
| \%defaults_format_special_unit_body_contents, |
| $customized_upper_case_commands, |
| $customized_code_types, |
| $customized_pre_class_types, |
| \%customized_accent_entities, |
| \%style_commands_customized_formatting_info, |
| $customized_no_arg_commands_formatting, |
| $customized_special_unit_info, |
| $customized_direction_strings |
| ); |
| |
| return $self; |
| } |
| |
| # remove data that leads to cycles related to output units and references |
| # to output units. |
| sub converter_release_output_units($) { |
| my $self = shift; |
| |
| # remove references to output units |
| if (exists($self->{'global_units_directions'})) { |
| %{$self->{'global_units_directions'}} = (); |
| } |
| |
| # Cannot do that, the content is still needed by the Converter |
| #@{$self->{'document_units'}} = (); |
| $self->{'document_units'} = []; |
| } |
| |
| # remove data that leads to cycles and references to elements. |
| sub converter_destroy($) { |
| my $self = shift; |
| |
| if (exists($self->{'converter_info'})) { |
| foreach my $key ('document', 'simpletitle_tree', 'title_tree') { |
| delete $self->{'converter_info'}->{$key}; |
| } |
| } |
| |
| delete $self->{'current_node'}; |
| delete $self->{'current_root_command'}; |
| |
| # a separate cache used if the user defines the translate_message function. |
| delete $self->{'translation_cache'}; |
| |
| # remove shared conversion states pointing to elements |
| if (exists($self->{'shared_conversion_state'})) { |
| if (exists($self->{'shared_conversion_state'}->{'nodedescription'}) |
| and exists($self->{'shared_conversion_state'}->{'nodedescription'} |
| ->{'formatted_nodedescriptions'})) { |
| delete $self->{'shared_conversion_state'}->{'nodedescription'} |
| ->{'formatted_nodedescriptions'}; |
| } |
| if (exists($self->{'shared_conversion_state'}->{'quotation'}) |
| and exists($self->{'shared_conversion_state'}->{'quotation'} |
| ->{'elements_authors'})) { |
| delete $self->{'shared_conversion_state'}->{'quotation'} |
| ->{'elements_authors'}; |
| } |
| } |
| |
| if (exists($self->{'no_arg_commands_formatting'})) { |
| foreach my $cmdname (keys(%{$self->{'no_arg_commands_formatting'}})) { |
| my $no_arg_command_ctx = $self->{'no_arg_commands_formatting'}->{$cmdname}; |
| if (defined($no_arg_command_ctx->{'translated_tree'})) { |
| my $tree = $no_arg_command_ctx->{'translated_tree'}; |
| # always a copy |
| Texinfo::ManipulateTree::tree_remove_parents($tree); |
| } |
| } |
| } |
| |
| delete $self->{'translated_special_unit_info_tree'}; |
| |
| if (exists($self->{'targets'})) { |
| foreach my $command (keys(%{$self->{'targets'}})) { |
| my $target = $self->{'targets'}->{$command}; |
| # can be tree elements or results of translations through cdt |
| delete $target->{'tree'}; |
| delete $target->{'tree_nonumber'}; |
| # tree elements |
| delete $target->{'name_tree'}; |
| delete $target->{'name_tree_nonumber'}; |
| delete $target->{'root_element_command'}; |
| delete $target->{'node_command'}; |
| } |
| } |
| } |
| |
| # wrapper around convert_tree setting some context |
| sub set_context_convert_tree($$$;$) { |
| my ($self, $tree, $context_type, $explanation) = @_; |
| |
| my $code_context_set; |
| if (defined($context_type)) { |
| if ($context_type & $CTXF_code) { |
| _set_code_context($self, 1); |
| $code_context_set = 1; |
| } elsif ($context_type & $CTXF_normal) { |
| _set_code_context($self, 0); |
| $code_context_set = 1; |
| } |
| } |
| my $result = $self->convert_tree($tree, $explanation); |
| |
| if ($code_context_set) { |
| _pop_code_context($self); |
| } |
| return $result; |
| } |
| |
| # Call convert_tree out of the main conversion flow. |
| sub convert_tree_new_formatting_context($$$;$$$$) { |
| my ($self, $tree, $context_string, $context_type, $multiple_pass, |
| $document_global_context, $block_command) = @_; |
| |
| _new_document_context($self, $context_string, $context_type, |
| $document_global_context, $block_command); |
| |
| my $context_string_str = "C($context_string)"; |
| my $multiple_pass_str = ''; |
| |
| if (defined($multiple_pass)) { |
| _set_multiple_conversions($self, $multiple_pass); |
| $multiple_pass_str = '|M'; |
| } |
| |
| print STDERR "new_fmt_ctx ${context_string_str}${multiple_pass_str}\n" |
| if ($self->get_conf('DEBUG')); |
| my $result = $self->convert_tree($tree, "new_fmt_ctx ${context_string_str}"); |
| |
| if (defined($multiple_pass)) { |
| _unset_multiple_conversions($self); |
| } |
| |
| _pop_document_context($self); |
| |
| return $result; |
| } |
| |
| # the entry point for _convert |
| sub convert_tree($$;$) { |
| my ($self, $tree, $explanation) = @_; |
| |
| # when formatting accents, goes through xml_accent without |
| # explanation, as explanation is not in the standard API, but |
| # otherwise the coverage of explanations should be pretty good |
| #cluck if (! defined($explanation)); |
| #print STDERR "CONVERT_TREE".(defined($explanation) ? " ".$explanation : '')."\n" |
| # if ($self->get_conf('DEBUG')); |
| return _convert($self, $tree, $explanation); |
| } |
| |
| # implementation of convert_tree. |
| |
| # Convert tree element $ELEMENT, and return HTML text for the output files. |
| # $EXPLANATION is only used for debug. |
| sub _convert($$;$); |
| sub _convert($$;$) { |
| my ($self, $element, $explanation) = @_; |
| |
| if (!defined($element)) { |
| cluck('BUG: _convert: element UNDEF'); |
| return ''; |
| } |
| |
| # to help debug and trace |
| my $command_type = ''; |
| if (exists($element->{'cmdname'})) { |
| $command_type = "\@$element->{'cmdname'} "; |
| } |
| if (exists($element->{'type'})) { |
| $command_type .= $element->{'type'}; |
| } |
| |
| # cache return value of get_conf for speed |
| my $debug = $self->get_conf('DEBUG'); |
| |
| if ($debug) { |
| #cluck() if (!defined($explanation)); |
| $explanation = 'NO EXPLANATION' if (!defined($explanation)); |
| my $contexts_str = _debug_print_html_contexts($self); |
| print STDERR "ELEMENT($explanation) ".$contexts_str |
| . ": " . Texinfo::Common::debug_print_element($element) . "\n"; |
| } |
| |
| if (ref($element) ne 'HASH' and ref($element) ne 'Texinfo::TreeElement') { |
| cluck "_convert: tree element not a HASH\n"; |
| return ''; |
| } |
| |
| # Process text |
| if (exists($element->{'text'})) { |
| if (exists($element->{'type'}) |
| and exists($self->{'types_conversion'}->{$element->{'type'}}) |
| and !defined($self->{'types_conversion'}->{$element->{'type'}})) { |
| if ($debug) { |
| print STDERR "IGNORE TEXT $command_type\n"; |
| } |
| return ''; |
| } |
| my $result; |
| # already converted to html, keep it as is |
| if (exists($element->{'type'}) and $element->{'type'} eq '_converted') { |
| $result = $element->{'text'}; |
| } else { |
| $result = &{$self->{'types_conversion'}->{'text'}} ($self, |
| $element->{'type'}, |
| $element, |
| $element->{'text'}); |
| } |
| print STDERR "DO TEXT => `$result'\n" if $debug; |
| return $result; |
| } |
| |
| if ((exists($element->{'type'}) |
| and exists($self->{'types_conversion'}->{$element->{'type'}}) |
| and !defined($self->{'types_conversion'}->{$element->{'type'}})) |
| or (exists($element->{'cmdname'}) |
| and exists($self->{'commands_conversion'}->{$element->{'cmdname'}}) |
| and !defined($self->{'commands_conversion'}->{$element->{'cmdname'}}))) { |
| if ($debug) { |
| print STDERR "IGNORED $command_type\n"; |
| } |
| 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 (exists($element->{'cmdname'}) |
| and !((exists($element->{'type'}) |
| and $element->{'type'} eq 'definfoenclose_command') |
| or (exists($element->{'type'}) |
| and $element->{'type'} eq 'index_entry_command'))) { |
| my $command_name = $element->{'cmdname'}; |
| |
| my $data_command_name; |
| if ($command_name eq 'item' |
| and exists($element->{'contents'}) |
| and exists($element->{'contents'}->[0]->{'type'}) |
| and $element->{'contents'}->[0]->{'type'} eq 'line_arg') { |
| $data_command_name = 'item_LINE'; |
| } else { |
| $data_command_name = $command_name; |
| } |
| |
| if (exists($root_commands{$command_name})) { |
| $self->{'current_root_command'} = $element; |
| } |
| if (exists($self->{'commands_conversion'}->{$command_name})) { |
| my $convert_to_latex |
| = _open_command_update_context($self, $command_name); |
| my $result = ''; |
| if (defined($self->{'commands_open'}->{$command_name})) { |
| $result .= &{$self->{'commands_open'}->{$command_name}}($self, |
| $command_name, $element); |
| } |
| my $content_formatted = ''; |
| if (exists($element->{'contents'}) |
| and (exists($root_commands{$command_name}) |
| or exists($block_commands{$command_name}) |
| or $command_name eq 'tab' or $command_name eq 'headitem' |
| or $data_command_name eq 'item')) { |
| if ($convert_to_latex) { |
| # displaymath |
| $content_formatted |
| = Texinfo::Convert::LaTeX::convert_to_latex_math(undef, |
| Texinfo::TreeElement::new({'contents' => $element->{'contents'}}), |
| $self->{'options_latex_math'}); |
| } else { |
| my $contents_nr = scalar(@{$element->{'contents'}}); |
| for (my $idx = 0; $idx < $contents_nr; $idx++) { |
| $content_formatted |
| .= _convert($self, $element->{'contents'}->[$idx], |
| "$command_type c[$idx]"); |
| } |
| } |
| } |
| my $args_formatted; |
| # contents could be not set for brace commands without braces |
| if ((exists($brace_commands{$command_name}) |
| and exists($element->{'contents'})) |
| or (exists($line_commands{$command_name}) |
| and $line_commands{$command_name} eq 'line') |
| or (($command_name eq 'item' or $command_name eq 'itemx') |
| and exists($element->{'contents'}) |
| and exists($element->{'contents'}->[0]->{'type'}) |
| and $element->{'contents'}->[0]->{'type'} eq 'line_arg') |
| or ($command_name eq 'quotation' |
| or $command_name eq 'smallquotation') |
| or $command_name eq 'float' |
| or $command_name eq 'cartouche') { |
| my $arguments_list; |
| if (exists($element->{'contents'}->[0]->{'type'}) |
| and $element->{'contents'}->[0]->{'type'} eq 'arguments_line') { |
| $arguments_list = $element->{'contents'}->[0]->{'contents'}; |
| } else { |
| $arguments_list = $element->{'contents'}; |
| } |
| |
| $args_formatted = []; |
| my @args_specification; |
| @args_specification = @{$html_default_commands_args{$command_name}} |
| if (exists($html_default_commands_args{$command_name})); |
| my $spec_nr = scalar(@args_specification); |
| my $arg_idx = -1; |
| foreach my $arg (@{$arguments_list}) { |
| $arg_idx++; |
| my $arg_spec; |
| if ($arg_idx < $spec_nr) { |
| $arg_spec = $args_specification[$arg_idx]; |
| } |
| if (Texinfo::Common::empty_spaces_argument($arg)) { |
| push @$args_formatted, undef; |
| next; |
| } |
| # NOTE here commands with empty array reference in array |
| # reference associated to command in html_default_commands_args |
| # do not have $arg_spec reset to normal, such that their argument |
| # is not converted here |
| $arg_spec = ['normal'] if (!defined($arg_spec)); |
| my $arg_formatted = {'arg_tree' => $arg}; |
| foreach my $arg_type (@$arg_spec) { |
| my $explanation = "$command_type A[$arg_idx]$arg_type"; |
| if ($arg_type eq 'normal') { |
| if ($convert_to_latex) { |
| $arg_formatted->{'normal'} |
| = Texinfo::Convert::LaTeX::convert_to_latex_math(undef, $arg, |
| $self->{'options_latex_math'}); |
| } else { |
| $arg_formatted->{'normal'} |
| = _convert($self, $arg, $explanation); |
| } |
| } elsif ($arg_type eq 'monospace') { |
| _set_code_context($self, 1); |
| $arg_formatted->{$arg_type} = _convert($self, $arg, $explanation); |
| _pop_code_context($self); |
| } elsif ($arg_type eq 'string') { |
| _new_document_context($self, $command_type, $CTXF_string); |
| $arg_formatted->{$arg_type} = _convert($self, $arg, $explanation); |
| #_unset_string_context($self); |
| _pop_document_context($self); |
| } elsif ($arg_type eq 'monospacestring') { |
| _new_document_context($self, $command_type, |
| $CTXF_string | $CTXF_code); |
| $arg_formatted->{$arg_type} = _convert($self, $arg, $explanation); |
| _pop_document_context($self); |
| } elsif ($arg_type eq 'monospacetext') { |
| Texinfo::Convert::Text::set_options_code( |
| $self->{'convert_text_options'}); |
| $arg_formatted->{$arg_type} |
| = Texinfo::Convert::Text::convert_to_text($arg, |
| $self->{'convert_text_options'}); |
| Texinfo::Convert::Text::reset_options_code( |
| $self->{'convert_text_options'}); |
| } elsif ($arg_type eq 'filenametext') { |
| |
| Texinfo::Convert::Text::set_options_code( |
| $self->{'convert_text_options'}); |
| # Always use encoded characters for file names |
| Texinfo::Convert::Text::set_options_encoding_if_not_ascii($self, |
| $self->{'convert_text_options'}); |
| $arg_formatted->{$arg_type} |
| = Texinfo::Convert::Text::convert_to_text($arg, |
| $self->{'convert_text_options'}); |
| Texinfo::Convert::Text::reset_options_code( |
| $self->{'convert_text_options'}); |
| Texinfo::Convert::Text::reset_options_encoding( |
| $self->{'convert_text_options'}); |
| } elsif ($arg_type eq 'url') { |
| Texinfo::Convert::Text::set_options_code( |
| $self->{'convert_text_options'}); |
| # set the encoding to UTF-8 to always have a string that |
| # is suitable for percent encoding. |
| Texinfo::Convert::Text::set_options_encoding( |
| $self->{'convert_text_options'}, 'utf-8'); |
| $arg_formatted->{$arg_type} |
| = Texinfo::Convert::Text::convert_to_text($arg, |
| $self->{'convert_text_options'}); |
| Texinfo::Convert::Text::reset_options_code( |
| $self->{'convert_text_options'}); |
| Texinfo::Convert::Text::reset_options_encoding( |
| $self->{'convert_text_options'}); |
| } elsif ($arg_type eq 'raw') { |
| _set_raw_context($self); |
| $arg_formatted->{$arg_type} = _convert($self, $arg, $explanation); |
| _unset_raw_context($self); |
| } |
| } |
| push @$args_formatted, $arg_formatted; |
| } |
| } |
| |
| _convert_command_update_context($self, $command_name); |
| |
| # TODO remove some time in the future of 2024, it is not used |
| # in texi2any and have never been documented. It may be used in 3rd |
| # party codes, though. |
| if ($element->{'cmdname'} eq 'node') { |
| $self->{'current_node'} = $element; |
| } |
| # args are formatted, now format the command itself |
| if (defined($args_formatted)) { |
| if (!defined($self->{'commands_conversion'}->{$command_name})) { |
| print STDERR "No command_conversion for $command_name\n"; |
| } else { |
| $result .= &{$self->{'commands_conversion'}->{$command_name}}($self, |
| $command_name, $element, $args_formatted, $content_formatted); |
| } |
| } else { |
| $result .= &{$self->{'commands_conversion'}->{$command_name}}($self, |
| $command_name, $element, undef, $content_formatted); |
| } |
| if ($command_name eq 'documentlanguage') { |
| $self->converter_set_documentlanguage( |
| $self->get_conf('documentlanguage')); |
| _translate_names($self); |
| } elsif ($command_name eq 'documentscript') { |
| $self->converter_set_documentscript( |
| $self->get_conf('documentscript')); |
| _translate_names($self); |
| } elsif ($command_name eq 'documentlanguagevariant') { |
| my $language_variants |
| = Texinfo::Common::documentlanguagevariant_variants($element); |
| $self->converter_set_documentlanguagevariant($language_variants); |
| _translate_names($self); |
| } |
| return $result; |
| } else { |
| print STDERR "Command not converted: $command_name\n" |
| if ($self->get_conf('VERBOSE') or $self->get_conf('DEBUG')); |
| if (exists($root_commands{$command_name})) { |
| delete $self->{'current_root_command'}; |
| } |
| return ''; |
| } |
| } elsif (exists($element->{'type'})) { |
| |
| my $result = ''; |
| my $type_name = $element->{'type'}; |
| |
| _open_type_update_context($self, $type_name); |
| |
| if (defined($self->{'types_open'}->{$type_name})) { |
| $result .= &{$self->{'types_open'}->{$type_name}}($self, |
| $type_name, $element); |
| } |
| |
| my $content_formatted = ''; |
| if ($type_name eq 'definfoenclose_command') { |
| if (exists($element->{'contents'})) { |
| $content_formatted = _convert($self, $element->{'contents'}->[0], |
| "DEFINFOENCLOSE_ARG"); |
| } |
| } elsif (exists($element->{'contents'}) |
| and $type_name ne 'untranslated_def_line_arg') { |
| my $content_idx = 0; |
| foreach my $content (@{$element->{'contents'}}) { |
| $content_formatted |
| .= _convert($self, $content, "$command_type c[$content_idx]"); |
| $content_idx++; |
| } |
| } |
| |
| _convert_type_update_context($self, $type_name); |
| |
| if (defined($self->{'types_conversion'}->{$type_name})) { |
| $result .= &{$self->{'types_conversion'}->{$type_name}} ($self, |
| $type_name, |
| $element, |
| $content_formatted); |
| } else { |
| $result .= $content_formatted; |
| } |
| print STDERR "DO type ($type_name) => `$result'\n" if $debug; |
| return $result; |
| # no type, no cmdname, but contents. |
| } elsif (exists($element->{'contents'})) { |
| # this happens inside accents, for section/node names, for @images. |
| my $content_formatted = ''; |
| my $content_idx = 0; |
| foreach my $content (@{$element->{'contents'}}) { |
| $content_formatted .= _convert($self, $content, |
| "$command_type C[$content_idx]"); |
| $content_idx++; |
| } |
| print STDERR "UNNAMED HOLDER => `$content_formatted'\n" if $debug; |
| return $content_formatted; |
| } else { |
| print STDERR "UNNAMED empty\n" if $debug; |
| if (defined($self->{'types_conversion'}->{''})) { |
| return &{$self->{'types_conversion'}->{''}} ($self, $element); |
| } else { |
| return ''; |
| } |
| } |
| print STDERR "DEBUG: HERE!($element)\n"; |
| } |
| |
| 1; |
| |
| # The documentation of the customization API is in the texi2any_api |
| # Texinfo manual, which also includes documentation of functions and |
| # data from multiple modules. |
| |
| __END__ |
| # Automatically generated from Convert_format_template.pod |
| |
| =head1 NAME |
| |
| Texinfo::Convert::HTML - Convert Texinfo tree to HTML |
| |
| =head1 SYNOPSIS |
| |
| my $converter |
| = Texinfo::Convert::HTML->converter({'NUMBER_SECTIONS' => 0}); |
| |
| # output to files |
| $converter->output($document); |
| # no header nor footer output |
| my $converted = $converter->convert($document); |
| |
| $converter->output_internal_links(); # HTML only |
| |
| =head1 NOTES |
| |
| The Texinfo Perl module main purpose is to be used in C<texi2any> to convert |
| Texinfo to other formats. There is no promise of API stability. |
| |
| =head1 DESCRIPTION |
| |
| Texinfo::Convert::HTML converts a Texinfo tree to HTML. |
| |
| =head1 METHODS |
| |
| =over |
| |
| =item $converter = Texinfo::Convert::HTML->converter($options) |
| |
| Initialize converter from Texinfo to HTML. |
| |
| The I<$options> hash reference holds Texinfo customization options for the |
| converter. These options should be Texinfo customization options |
| that can be passed to the converter. Most of the customization options are |
| described in the Texinfo manual or in the customization API manual. Those |
| customization options, when appropriate, override the document content. |
| |
| See L<Texinfo::Convert::Converter> for more information. |
| |
| =item $converter->output($document) |
| |
| Convert a Texinfo parsed document I<$document> and output the result in files as |
| described in the Texinfo manual. |
| |
| =item $result = $converter->convert($document) |
| |
| Convert a Texinfo parsed document I<$document> 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. In general it is better to call this function when conversion |
| is already ongoing, as it requires an association to a document and a suitably |
| initialized converter formatting state. |
| |
| =item $result = $converter->output_internal_links() |
| X<C<output_internal_links>> |
| |
| Returns text representing the links in the document. The format should |
| follow the C<--internal-links> option of the C<texi2any> |
| specification. This is only supported in (and relevant for) HTML. |
| |
| =back |
| |
| =head1 AUTHOR |
| |
| Patrice Dumas, E<lt>bug-texinfo@gnu.orgE<gt> |
| |
| =head1 COPYRIGHT AND LICENSE |
| |
| Copyright 2010- Free Software Foundation, Inc. See the source file for |
| all copyright years. |
| |
| This library is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 3 of the License, or (at |
| your option) any later version. |
| |
| =cut |