From 5de83ec843cdc88e6adc42b1fe3f0ec48c52b7f3 Mon Sep 17 00:00:00 2001 From: Norbert Preining Date: Wed, 9 Nov 2022 03:02:50 +0000 Subject: CTAN sync 202211090302 --- .../texinfo/tp/Texinfo/Convert/Plaintext.pm | 1796 +++++++++++--------- 1 file changed, 992 insertions(+), 804 deletions(-) (limited to 'macros/texinfo/texinfo/tp/Texinfo/Convert/Plaintext.pm') diff --git a/macros/texinfo/texinfo/tp/Texinfo/Convert/Plaintext.pm b/macros/texinfo/texinfo/tp/Texinfo/Convert/Plaintext.pm index 7402eb9f99..949bba1b84 100644 --- a/macros/texinfo/texinfo/tp/Texinfo/Convert/Plaintext.pm +++ b/macros/texinfo/texinfo/tp/Texinfo/Convert/Plaintext.pm @@ -1,6 +1,6 @@ # Plaintext.pm: output tree as text with filling. # -# Copyright 2010-2020 Free Software Foundation, Inc. +# Copyright 2010-2022 Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -16,6 +16,8 @@ # along with this program. If not, see . # # Original author: Patrice Dumas +# +# Names of methods overriden in Texinfo::Convert::Info begin with format_. package Texinfo::Convert::Plaintext; @@ -24,17 +26,24 @@ use 5.00405; # See comment at start of HTML.pm use if $] >= 5.012, feature => qw(unicode_strings); +use if $] >= 5.014, re => '/a'; + use strict; -use Texinfo::Convert::Converter; +# To check if there is no erroneous autovivification +#no autovivification qw(fetch delete exists store strict); + +use Texinfo::Commands; use Texinfo::Common; use Texinfo::Convert::Texinfo; -use Texinfo::Convert::Paragraph; - +use Texinfo::Convert::Utils; use Texinfo::Convert::Text; +use Texinfo::Convert::Utils; +use Texinfo::Convert::Converter; +use Texinfo::Convert::Paragraph; require Exporter; -use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); +use vars qw($VERSION @ISA); @ISA = qw(Texinfo::Convert::Converter); # Some extra initialization for the first time this module is loaded. @@ -51,69 +60,73 @@ sub import { goto &Exporter::import; } -%EXPORT_TAGS = ( 'all' => [ qw( - convert - output -) ] ); +$VERSION = '7.0'; -@EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); -@EXPORT = qw( -); - -$VERSION = '6.8'; +# commands that are of use for formatting. +my %formatted_line_commands = %Texinfo::Common::formatted_line_commands; +my %formatted_nobrace_commands = %Texinfo::Common::formatted_nobrace_commands; +my %formattable_line_commands = %Texinfo::Common::formattable_line_commands; +my %brace_commands = %Texinfo::Commands::brace_commands; -# misc commands that are of use for formatting. -my %formatting_misc_commands = %Texinfo::Convert::Text::formatting_misc_commands; my $NO_NUMBER_FOOTNOTE_SYMBOL = '*'; +# documentlanguage is used through gdt(). my @informative_global_commands = ('paragraphindent', 'firstparagraphindent', -'frenchspacing', 'documentencoding', 'footnotestyle', 'documentlanguage', -'contents', 'shortcontents', 'summarycontents', 'deftypefnnewline'); +'frenchspacing', 'footnotestyle', 'documentlanguage', 'deftypefnnewline'); my %informative_commands; foreach my $informative_command (@informative_global_commands) { $informative_commands{$informative_command} = 1; } -my %no_brace_commands = %Texinfo::Common::no_brace_commands; +# used to pass to Texinfo::Convert::Info +sub get_informative_global_commands($) +{ + my $self = shift; + return @informative_global_commands; +} + my %brace_no_arg_commands; -foreach my $command (keys (%Texinfo::Common::brace_commands)) { +foreach my $command (keys (%brace_commands)) { $brace_no_arg_commands{$command} = 1 - if ($Texinfo::Common::brace_commands{$command} eq '0'); -} -my %accent_commands = %Texinfo::Common::accent_commands; -my %misc_commands = %Texinfo::Common::misc_commands; -my %sectioning_commands = %Texinfo::Common::sectioning_commands; -my %def_commands = %Texinfo::Common::def_commands; -my %ref_commands = %Texinfo::Common::ref_commands; -my %block_commands = %Texinfo::Common::block_commands; -my %menu_commands = %Texinfo::Common::menu_commands; -my %root_commands = %Texinfo::Common::root_commands; -my %preformatted_commands = %Texinfo::Common::preformatted_commands; -my %math_commands = %Texinfo::Common::math_commands; + if ($brace_commands{$command} eq 'noarg'); +} +my %accent_commands = %Texinfo::Commands::accent_commands; +my %line_commands = %Texinfo::Commands::line_commands; +my %nobrace_commands = %Texinfo::Commands::nobrace_commands; +my %sectioning_heading_commands = %Texinfo::Commands::sectioning_heading_commands; +my %def_commands = %Texinfo::Commands::def_commands; +my %ref_commands = %Texinfo::Commands::ref_commands; +my %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 %default_index_commands = %Texinfo::Commands::default_index_commands; +my %letter_no_arg_commands = %Texinfo::Commands::letter_no_arg_commands; + +my %nobrace_symbol_text = %Texinfo::Common::nobrace_symbol_text; my %explained_commands = %Texinfo::Common::explained_commands; my %inline_format_commands = %Texinfo::Common::inline_format_commands; -my %inline_commands = %Texinfo::Common::inline_commands; -my %item_container_commands = %Texinfo::Common::item_container_commands; -my %raw_commands = %Texinfo::Common::raw_commands; -my %format_raw_commands = %Texinfo::Common::format_raw_commands; -my %code_style_commands = %Texinfo::Common::code_style_commands; -my %regular_font_style_commands = %Texinfo::Common::regular_font_style_commands; -my %preformatted_code_commands = %Texinfo::Common::preformatted_code_commands; -my %default_index_commands = %Texinfo::Common::default_index_commands; -my %letter_no_arg_commands = %Texinfo::Common::letter_no_arg_commands; - -foreach my $kept_command(keys (%informative_commands), - keys (%default_index_commands), - 'verbatiminclude', 'insertcopying', - 'listoffloats', 'printindex', ) { - $formatting_misc_commands{$kept_command} = 1; +my %brace_code_commands = %Texinfo::Common::brace_code_commands; + +my @contents_commands = ('contents', 'shortcontents', 'summarycontents'); + +foreach my $kept_command (keys (%informative_commands), @contents_commands, + keys (%default_index_commands), keys(%formattable_line_commands)) { + $formatted_line_commands{$kept_command} = 1; +} + +# formatted/formattable @-commands not formatted in Plaintext/Info +foreach my $non_formatted_line_command ('page', 'need', 'vskip', + 'author', 'subtitle', 'title') { + delete $formatted_line_commands{$non_formatted_line_command}; } foreach my $def_command (keys(%def_commands)) { - $formatting_misc_commands{$def_command} = 1 if ($misc_commands{$def_command}); + $formatted_line_commands{$def_command} = 1 if ($line_commands{$def_command}); } # There are 6 stacks that define the context. @@ -152,6 +165,16 @@ foreach my $def_command (keys(%def_commands)) { # paragraph number incremented with paragraphs, center, listoffloats # and block commands except: html and such, group, raggedright, menu*, float +my %menu_commands; + +my %format_raw_commands; +foreach my $block_command (keys(%block_commands)) { + $menu_commands{$block_command} = 1 + if ($block_commands{$block_command} eq 'menu'); + $format_raw_commands{$block_command} = 1 + if ($block_commands{$block_command} eq 'format_raw'); +} + my %default_preformatted_context_commands = (%preformatted_commands, %format_raw_commands); foreach my $preformatted_command ('verbatim', keys(%menu_commands)) { @@ -165,13 +188,19 @@ foreach my $block_math_command (keys(%math_commands)) { } } -my %ignored_misc_commands; -foreach my $misc_command (keys(%misc_commands)) { - $ignored_misc_commands{$misc_command} = 1 - unless ($formatting_misc_commands{$misc_command}); +my %ignored_line_commands; +foreach my $line_command (keys(%line_commands)) { + $ignored_line_commands{$line_command} = 1 + unless ($formatted_line_commands{$line_command}); +} + +my %ignored_nobrace_commands; +foreach my $nobrace_command (keys(%nobrace_commands)) { + $ignored_nobrace_commands{$nobrace_command} = 1 + unless ($formatted_nobrace_commands{$nobrace_command}); } -my %ignored_commands = %ignored_misc_commands; +my %ignored_commands = (%ignored_line_commands, %ignored_nobrace_commands); foreach my $ignored_brace_commands ('caption', 'shortcaption', 'hyphenation', 'sortas') { $ignored_commands{$ignored_brace_commands} = 1; @@ -217,7 +246,7 @@ my %flush_commands = ( # used to determine the first line, in fact. my %advance_paragraph_count_commands; foreach my $command (keys(%block_commands)) { - next if ($menu_commands{$command} + next if ($menu_commands{$command} or $block_commands{$command} eq 'raw'); $advance_paragraph_count_commands{$command} = 1; } @@ -249,15 +278,15 @@ my %upper_case_commands = ( ); my %ignorable_space_types; -foreach my $type ('empty_line_after_command', - 'empty_spaces_after_command', 'spaces_at_end', - 'empty_spaces_before_argument', 'empty_spaces_before_paragraph', - 'empty_spaces_after_close_brace') { +foreach my $type ('ignorable_spaces_after_command', + 'spaces_at_end', + 'spaces_before_paragraph', + 'spaces_after_close_brace') { $ignorable_space_types{$type} = 1; } my %ignored_types; -foreach my $type ('preamble', +foreach my $type ('preamble_before_beginning', 'preamble_before_setfilename') { $ignored_types{$type} = 1; } @@ -297,7 +326,7 @@ my %non_quoted_commands_when_nested; # if @documentencoding utf-8 is used. foreach my $quoted_command (@quoted_commands) { $style_map{$quoted_command} = ["'", "'"]; - if ($code_style_commands{$quoted_command}) { + if ($brace_code_commands{$quoted_command}) { $non_quoted_commands_when_nested{$quoted_command} = 1; } } @@ -319,24 +348,23 @@ for my $index_style_command ('strong', 'emph', 'sub', 'sup', 'key') { # in those commands, there is no addition of double space after a dot. # math is special my %no_punctation_munging_commands; -foreach my $command ('var', 'cite', 'dmn', keys(%code_style_commands)) { +foreach my $command ('var', 'cite', 'dmn', keys(%brace_code_commands)) { $no_punctation_munging_commands{$command} = 1; } my %defaults = ( 'ENABLE_ENCODING' => 1, + 'ASCII_PUNCTUATION' => 0, 'FORMAT_MENU' => 'nomenu', #'EXTENSION' => 'info', 'EXTENSION' => 'txt', #'USE_SETFILENAME_EXTENSION' => 1, - 'INFO_SPECIAL_CHARS_WARNING' => 1, - - #'OUTFILE' => undef, + 'INFO_SPECIAL_CHARS_WARNING' => undef, + # different from the default, which is undef 'OUTFILE' => '-', - 'SUBDIR' => undef, 'documentlanguage' => undef, - 'output_format' => '', + 'converted_format' => '', 'USE_NODES' => 1, ); @@ -364,23 +392,11 @@ sub push_top_formatter($$) $self->{'formatters'}->[-1]->{'_top_formatter'} = 1; } - -my %contents_commands = ( - 'contents' => 1, - 'shortcontents' => 1, - 'summarycontents' => 1, -); - sub converter_defaults($$) { return %defaults; } -sub converter_global_commands($) -{ - return @informative_global_commands; -} - sub converter_initialize($) { my $self = shift; @@ -401,6 +417,8 @@ sub converter_initialize($) = %default_preformatted_context_commands; $self->{'footnote_index'} = 0; $self->{'pending_footnotes'} = []; + $self->{'index_entry_node_colon'} = {}; + $self->{'index_entries_no_node'} = {}; foreach my $format (keys(%format_raw_commands)) { $self->{'ignored_commands'}->{$format} = 1 @@ -409,17 +427,22 @@ sub converter_initialize($) %{$self->{'style_map'}} = %style_map; $self->{'convert_text_options'} - = {Texinfo::Common::_convert_text_options($self)}; + = {Texinfo::Convert::Text::copy_options_for_convert_text($self)}; + + if ($self->get_conf('ASCII_PUNCTUATION')) { + $self->{'convert_text_options'}->{'ascii_punctuation'} = 1; + # cache to avoid calling get_conf + $self->{'ascii_punctuation'} = 1; + } else { + $self->{'ascii_punctuation'} = 0; + } - if ($self->get_conf('ENABLE_ENCODING') and $self->get_conf('OUTPUT_ENCODING_NAME') + if ($self->get_conf('ENABLE_ENCODING') + and $self->get_conf('OUTPUT_ENCODING_NAME') and $self->get_conf('OUTPUT_ENCODING_NAME') eq 'utf-8') { # cache this to avoid redoing calls to get_conf $self->{'to_utf8'} = 1; - if (!$self->{'extra'}->{'documentencoding'}) { - # Do not use curly quotes or some other unnecessary non-ASCII characters - # if '@documentencoding UTF-8' is not given. - $self->{'convert_text_options'}->{'no_extra_unicode'} = 1; - } else { + if (!$self->{'ascii_punctuation'}) { foreach my $quoted_command (@quoted_commands) { # Directed single quotes $self->{'style_map'}->{$quoted_command} = ["\x{2018}", "\x{2019}"]; @@ -444,6 +467,26 @@ sub converter_initialize($) $self->{'fillcolumn'} = $self->get_conf('FILLCOLUMN'); # else it's already set via the defaults } + + if ($self->get_conf('INFO_SPECIAL_CHARS_QUOTE')) { + $self->{'info_special_chars_quote'} + = $self->get_conf('INFO_SPECIAL_CHARS_QUOTE'); + if (!defined($self->get_conf('INFO_SPECIAL_CHARS_WARNING'))) { + $self->set_conf('INFO_SPECIAL_CHARS_WARNING', 0); + } + } else { + $self->{'info_special_chars_quote'} = ''; + if (!defined($self->get_conf('INFO_SPECIAL_CHARS_WARNING'))) { + $self->set_conf('INFO_SPECIAL_CHARS_WARNING', 1); + } + } + if ($self->get_conf('INFO_SPECIAL_CHARS_WARNING')) { + $self->{'info_special_chars_warning'} + = $self->get_conf('INFO_SPECIAL_CHARS_WARNING'); + } else { + $self->{'info_special_chars_warning'} = ''; + } + # This needs to be here to take into account $self->{'fillcolumn'}. $self->push_top_formatter('_Root_context'); # some caching to avoid calling get_conf @@ -459,33 +502,33 @@ sub converter_initialize($) return $self; } -sub _count_context_bug_message($$$) +sub count_context_bug_message($$$) { my ($self, $precision, $element) = @_; if (scalar(@{$self->{'count_context'}}) != 1) { my $element_text; if ($element) { - $element_text - = Texinfo::Structuring::_print_element_command_texi($element); + $element_text + = Texinfo::Structuring::root_or_external_element_cmd_texi($element); } else { $element_text = ''; } - $self->_bug_message("Too much count_context ${precision}(". + $self->present_bug_message("Too much count_context ${precision}(". scalar(@{$self->{'count_context'}}). "): ". $element_text, $element); die; } } -sub _convert_element($$) +sub _convert_root_element($$) { my ($self, $element) = @_; my $result = ''; $result .= $self->_convert($element); - $self->_count_context_bug_message('', $element); - $result .= $self->_footnotes($element); - $self->_count_context_bug_message('footnotes ', $element); + $self->count_context_bug_message('', $element); + $result .= $self->process_footnotes($element); + $self->count_context_bug_message('footnotes ', $element); return $result; } @@ -496,17 +539,19 @@ sub convert($$) my $result = ''; - my $elements = Texinfo::Structuring::split_by_node($root); + my $tree_units = Texinfo::Structuring::split_by_node($root); + $self->{'seenmenus'} = {}; $self->{'empty_lines_count'} = 1; - if (!defined($elements)) { + $self->{'index_entries_line_location'} = {}; + if (!defined($tree_units)) { $result = $self->_convert($root); - $self->_count_context_bug_message('no element '); - my $footnotes = $self->_footnotes(); - $self->_count_context_bug_message('no element footnotes '); + $self->count_context_bug_message('no element '); + my $footnotes = $self->process_footnotes(); + $self->count_context_bug_message('no element footnotes '); $result .= $footnotes; } else { - foreach my $node (@$elements) { - my $node_text = _convert_element($self, $node); + foreach my $node_root_element (@$tree_units) { + my $node_text = _convert_root_element($self, $node_root_element); $result .= $node_text; } } @@ -514,14 +559,24 @@ sub convert($$) return $result; } +# the initialization of module specific state is not done in output() +# as output() is generic, so it needs to be done here sub convert_tree($$) { my ($self, $root) = @_; - $self->{'empty_lines_count'} = 1; + if (!defined($self->{'empty_lines_count'})) { + # setting to 1 ensures that nothing is done, as there is + # something done (a newline added) if equal to 0. + $self->{'empty_lines_count'} = 1; + } + $self->{'seenmenus'} = {} + if (!$self->{'seenmenus'}); + $self->{'index_entries_line_location'} = {} + if (!$self->{'index_entries_line_location'}); my $result; - if ($root->{'type'} and $root->{'type'} eq 'element') { - $result = _convert_element($self, $root); + if ($root->{'type'} and $root->{'type'} eq 'unit') { + $result = _convert_root_element($self, $root); } else { $result = $self->_convert($root); } @@ -535,20 +590,18 @@ sub _protect_sentence_ends ($) { my $text = shift; # Avoid suppressing end of sentence, by inserting a control character # in front of the full stop. The choice of BS for this is arbitrary. - $text =~ s/(?<=[^[:upper:]]) + $text =~ s/(?<=[^\p{Upper}]) (?=[$end_sentence][$after_punctuation]*(?:\s|$)) - /\x08/gx; + /\x08/xg; # Also insert a control character at end of string, to protect a full stop # that may follow later. - #$text =~ s/(?<=[^[:upper:]][$after_punctuation]*)$/\x08/; + #$text =~ s/(?<=[^\p{Upper}\s][$after_punctuation]*)$/\x08/; # Perl doesn't support "variable length lookbehind" $text = reverse $text; - $text =~ s/^(?=[$after_punctuation]* - (?:[^[:upper:]\s]|[\x{202f}\x{00a0}])) - /\x08/x; + $text =~ s/^(?=[$after_punctuation]*(?:[^\p{Upper}\s]))/\x08/; $text = reverse $text; return $text; @@ -582,7 +635,7 @@ sub _process_text($$$) } if ($self->{'to_utf8'} - and $self->{'extra'}->{'documentencoding'}) { + and !$self->{'ascii_punctuation'}) { return Texinfo::Convert::Unicode::unicode_text($text, $context->{'font_type_stack'}->[-1]->{'monospace'}); } elsif (!$context->{'font_type_stack'}->[-1]->{'monospace'}) { @@ -595,8 +648,11 @@ sub new_formatter($$;$) { my ($self, $type, $conf) = @_; - my $first_indent_length = $conf->{'first_indent_length'}; - delete $conf->{'first_indent_length'}; + my $first_indent_length; + if ($conf) { + $first_indent_length = $conf->{'first_indent_length'}; + delete $conf->{'first_indent_length'}; + } my $container; my $container_conf = { @@ -605,6 +661,9 @@ sub new_formatter($$;$) }; $container_conf->{'frenchspacing'} = 1 if ($self->{'conf'}->{'frenchspacing'} eq 'on'); + #if ($self->get_conf('frenchspacing') eq 'on'); + # access 'conf' hash directly for efficiency + $container_conf->{'counter'} = $self->{'text_element_context'}->[-1]->{'counter'} if (defined($self->{'text_element_context'}->[-1]->{'counter'})); @@ -655,7 +714,15 @@ sub new_formatter($$;$) 'font_type_stack' => [{}], 'w' => 0, 'type' => $type, 'frenchspacing_stack' => [$self->{'conf'}->{'frenchspacing'}], - 'suppress_styles' => $conf->{'suppress_styles'}}; + #'frenchspacing_stack' => [$self->get_conf('frenchspacing')], + # access 'conf' hash directly for efficiency + 'suppress_styles' => undef, + 'no_added_eol' => undef}; + if ($conf) { + foreach my $configuration ('suppress_styles', 'no_added_eol') { + $formatter->{$configuration} = $conf->{$configuration}; + } + } if ($type eq 'unfilled') { foreach my $context (reverse(@{$self->{'context'}})) { @@ -675,6 +742,31 @@ sub new_formatter($$;$) return $formatter; } +# intercept messages, in case some Texinfo is processed twice +sub converter_line_warn($$$$) +{ + my $self = shift; + my $configuration_information = shift; + my $text = shift; + my $error_location_info = shift; + + if (!$self->{'silent'}) { + $self->line_warn($configuration_information, $text, $error_location_info); + } +} + +sub converter_line_error($$$$) +{ + my $self = shift; + my $configuration_information = shift; + my $text = shift; + my $error_location_info = shift; + + if (!$self->{'silent'}) { + $self->line_error($configuration_information, $text, $error_location_info); + } +} + sub convert_line($$;$) { my ($self, $converted, $conf) = @_; @@ -687,7 +779,7 @@ sub convert_line($$;$) return $text; } -sub convert_unfilled($$;$) +sub _convert_unfilled($$;$) { my ($self, $converted, $conf) = @_; my $formatter = $self->new_formatter('unfilled', $conf); @@ -708,7 +800,7 @@ sub count_bytes($$) $self->{'output_perl_encoding'}); } -sub _add_text_count($$) +sub add_text_to_count($$) { my ($self, $text) = @_; if (!$self->{'count_context'}->[-1]->{'pending_text'}) { @@ -725,8 +817,8 @@ sub _add_lines_count($$) # Update $SELF->{'count_context'}->[-1]->{'bytes'} by counting the text that # hasn't been counted yet. It is faster to count the text all together than -# piece by piece in _add_text_count. -sub _update_count_context($) +# piece by piece in add_text_to_count. +sub update_count_context($) { my $self = shift; if ($self->{'count_context'}->[-1]->{'pending_text'}) { @@ -738,33 +830,31 @@ sub _update_count_context($) } } -# Save the line and byte offset of $ROOT. -sub _add_location($$) +# Save the line and byte offset of $ELEMENT. +sub add_location($$) { - my ($self, $root) = @_; + my ($self, $element) = @_; my $location = { 'lines' => $self->{'count_context'}->[-1]->{'lines'} }; push @{$self->{'count_context'}->[-1]->{'locations'}}, $location; - if (!($root->{'extra'} and $root->{'extra'}->{'index_entry'})) { - _update_count_context($self); + if (!($element->{'extra'} and $element->{'extra'}->{'index_entry'})) { + update_count_context($self); $location->{'bytes'} = $self->{'count_context'}->[-1]->{'bytes'}; - $location->{'root'} = $root; + $location->{'root'} = $element; } else { - $location->{'index_entry'} = $root; + $location->{'index_entry'} = $element; } return $location; } -sub _add_image($$$$;$) +sub add_image($$$$;$) { - my ($self, $root, $lines_count, $image_width, $no_align) = @_; + my ($self, $element, $lines_count, $image_width, $no_align) = @_; push @{$self->{'count_context'}->[-1]->{'images'}}, { 'lines' => $self->{'count_context'}->[-1]->{'lines'}, 'lines_count' => $lines_count, 'image_width' => $image_width, 'no_align' => $no_align, - # may be used for debugging? - #'_ref' => $root, }; } @@ -787,7 +877,7 @@ sub _update_locations_counts($$) { my ($self, $locations) = @_; - _update_count_context($self); + update_count_context($self); foreach my $location (@$locations) { $location->{'bytes'} += $self->{'count_context'}->[-1]->{'bytes'} if (defined($location->{'bytes'})); @@ -800,7 +890,7 @@ sub _add_newline_if_needed($) { my $self = shift; if (defined($self->{'empty_lines_count'}) and $self->{'empty_lines_count'} == 0) { - _add_text_count($self, "\n"); + add_text_to_count($self, "\n"); _add_lines_count($self, 1); $self->{'empty_lines_count'} = 1; return "\n"; @@ -808,12 +898,40 @@ sub _add_newline_if_needed($) { return ''; } +sub _open_code($) +{ + my $formatter = shift; + + if (!$formatter->{'font_type_stack'}->[-1]->{'monospace'}) { + push @{$formatter->{'font_type_stack'}}, {'monospace' => 1}; + } else { + $formatter->{'font_type_stack'}->[-1]->{'monospace'}++; + } + push @{$formatter->{'frenchspacing_stack'}}, 'on'; + set_space_protection($formatter->{'container'}, undef, undef, undef, 1); +} + +sub _close_code($) +{ + my $formatter = shift; + + $formatter->{'font_type_stack'}->[-1]->{'monospace'}--; + pop @{$formatter->{'font_type_stack'}} + if !$formatter->{'font_type_stack'}->[-1]->{'monospace'}; + pop @{$formatter->{'frenchspacing_stack'}}; + my $frenchspacing = 0; + $frenchspacing = 1 if ($formatter->{'frenchspacing_stack'}->[-1] eq 'on'); + set_space_protection($formatter->{'container'}, undef, + undef, undef, $frenchspacing); +} + my $footnote_indent = 3; -sub _footnotes($;$) +sub process_footnotes($;$) { my ($self, $element) = @_; - $element = undef if ($element and $element->{'extra'}->{'no_node'}); + $element = undef if ($element and (not defined ($element->{'extra'}) + or not defined $element->{'extra'}->{'unit_command'})); my $result = ''; if (scalar(@{$self->{'pending_footnotes'}})) { @@ -821,33 +939,33 @@ sub _footnotes($;$) if ($self->get_conf('footnotestyle') eq 'end' or !defined($element)) { my $footnotes_header = " ---------- Footnotes ----------\n\n"; $result .= $footnotes_header; - _add_text_count($self, $footnotes_header); + add_text_to_count($self, $footnotes_header); _add_lines_count($self, 2); $self->{'empty_lines_count'} = 1; } else { - my $node_contents = [@{$element->{'extra'}->{'node'}->{'extra'}->{'node_content'}}, + my $node_contents = [@{$element->{'extra'}->{'unit_command'}->{'extra'}->{'node_content'}}, {'text' => '-Footnotes'}]; my $footnotes_node = { 'cmdname' => 'node', - 'node_up' => $element->{'extra'}->{'node'}, + 'structure' => {'node_up' => $element->{'extra'}->{'unit_command'}}, 'extra' => {'node_content' => $node_contents } }; - $result .= $self->_node($footnotes_node); - $self->{'node'} = $footnotes_node; + $result .= $self->format_node($footnotes_node); + $self->{'current_node'} = $footnotes_node; } while (@{$self->{'pending_footnotes'}}) { my $footnote = shift (@{$self->{'pending_footnotes'}}); # If nested within another footnote and footnotestyle is separate, # the element here will be the parent element and not the footnote - # element, while the pxref will point to the name with the + # element, while the pxref will point to the name with the # footnote node taken into account. Not really problematic as # nested footnotes are not right. if ($element) { - my $node_contents = [@{$element->{'extra'}->{'node'}->{'extra'}->{'node_content'}}, + my $node_contents = [@{$element->{'extra'}->{'unit_command'}->{'extra'}->{'node_content'}}, {'text' => "-Footnote-$footnote->{'number'}"}]; - $self->_add_location({'cmdname' => 'anchor', + $self->add_location({'cmdname' => 'anchor', 'extra' => {'node_content' => $node_contents } }); } @@ -863,9 +981,9 @@ sub _footnotes($;$) my $footnote_text = ' ' x $footnote_indent . "($formatted_footnote_number) "; $result .= $footnote_text; - $self->{'text_element_context'}->[-1]->{'counter'} += + $self->{'text_element_context'}->[-1]->{'counter'} += Texinfo::Convert::Unicode::string_width($footnote_text); - _add_text_count($self, $footnote_text); + add_text_to_count($self, $footnote_text); $self->{'empty_lines_count'} = 0; $result .= $self->_convert($footnote->{'root'}->{'args'}->[0]); @@ -922,7 +1040,7 @@ sub _align_lines($$$$$$) }# else { # Happens in Info with the special construct as, in that # case, there are no lines! So no error... - # $self->_bug_message("more than one image with lines on $image->{'lines'}"); + # $self->present_bug_message("more than one image with lines on $image->{'lines'}"); # in that case, the $image->{'lines'} is not in sync with the # lines count. So the second image will be treated as simple text. #} @@ -1021,7 +1139,7 @@ sub _align_environment($$$$) { my ($self, $result, $max, $align) = @_; - _update_count_context($self); + update_count_context($self); my $counts = pop @{$self->{'count_context'}}; my $bytes_count; ($result, $bytes_count) = $self->_align_lines($result, $max, @@ -1034,42 +1152,45 @@ sub _align_environment($$$$) return $result; } -sub _contents($$$) +sub format_contents($$$) { my ($self, $section_root, $contents_or_shortcontents) = @_; my $contents = 1 if ($contents_or_shortcontents eq 'contents'); # no sections - return ('', 0) if (!$section_root or !$section_root->{'section_childs'}); - my $root_level = $section_root->{'section_childs'}->[0]->{'level'}; - foreach my $top_section(@{$section_root->{'section_childs'}}) { - $root_level = $top_section->{'level'} - if ($top_section->{'level'} < $root_level); + return ('', 0) if (!$section_root + or !$section_root->{'structure'}->{'section_childs'}); + my $root_level = $section_root->{'structure'}->{'section_childs'}->[0] + ->{'structure'}->{'section_level'}; + foreach my $top_section (@{$section_root->{'structure'}->{'section_childs'}}) { + $root_level = $top_section->{'structure'}->{'section_level'} + if ($top_section->{'structure'}->{'section_level'} < $root_level); } my $result = ''; my $lines_count = 0; # This is done like that because the tree may not be well formed if # there is a @part after a @chapter for example. - foreach my $top_section (@{$section_root->{'section_childs'}}) { + foreach my $top_section (@{$section_root->{'structure'}->{'section_childs'}}) { my $section = $top_section; SECTION: while ($section) { push @{$self->{'count_context'}}, {'lines' => 0, 'bytes' => 0}; my $section_title_tree; - if (defined($section->{'number'}) + if (defined($section->{'structure'}->{'section_number'}) and ($self->get_conf('NUMBER_SECTIONS') or !defined($self->get_conf('NUMBER_SECTIONS')))) { - if ($section->{'cmdname'} eq 'appendix' and $section->{'level'} == 1) { + if ($section->{'cmdname'} eq 'appendix' + and $section->{'structure'}->{'section_level'} == 1) { $section_title_tree = $self->gdt('Appendix {number} {section_title}', - {'number' => {'text' => $section->{'number'}}, - 'section_title' + {'number' => {'text' => $section->{'structure'}->{'section_number'}}, + 'section_title' => {'contents' => $section->{'args'}->[0]->{'contents'}}}); } else { $section_title_tree = $self->gdt('{number} {section_title}', - {'number' => {'text' => $section->{'number'}}, - 'section_title' + {'number' => {'text' => $section->{'structure'}->{'section_number'}}, + 'section_title' => {'contents' => $section->{'args'}->[0]->{'contents'}}}); } } else { @@ -1082,23 +1203,23 @@ sub _contents($$$) my $text = $section_title; chomp ($text); $text .= "\n"; - my $repeat_count = 2 * ($section->{'level'} - ($root_level+1)); + my $repeat_count = 2 * ($section->{'structure'}->{'section_level'} - ($root_level+1)); ($result .= (' ' x $repeat_count)) if $repeat_count > 0; $result .= $text; $lines_count++; - if ($section->{'section_childs'} - and ($contents or $section->{'level'} < $root_level+1)) { - $section = $section->{'section_childs'}->[0]; - } elsif ($section->{'section_next'}) { + if ($section->{'structure'}->{'section_childs'} + and ($contents or $section->{'structure'}->{'section_level'} < $root_level+1)) { + $section = $section->{'structure'}->{'section_childs'}->[0]; + } elsif ($section->{'structure'}->{'section_next'}) { last if ($section eq $top_section); - $section = $section->{'section_next'}; + $section = $section->{'structure'}->{'section_next'}; } else { last if ($section eq $top_section); - while ($section->{'section_up'}) { - $section = $section->{'section_up'}; + while ($section->{'structure'}->{'section_up'}) { + $section = $section->{'structure'}->{'section_up'}; last SECTION if ($section eq $top_section); - if ($section->{'section_next'}) { - $section = $section->{'section_next'}; + if ($section->{'structure'}->{'section_next'}) { + $section = $section->{'structure'}->{'section_next'}; last; } } @@ -1114,10 +1235,10 @@ sub _menu($$) if ($menu_command->{'cmdname'} eq 'menu') { my $result = "* Menu:\n\n"; - _add_text_count($self, $result); + add_text_to_count($self, $result); _add_lines_count($self, 2); - if ($self->{'node'}) { - $self->{'seenmenus'}->{$self->{'node'}} = 1; + if ($self->{'current_node'}) { + $self->{'seenmenus'}->{$self->{'current_node'}} = 1; } return $result; } else { @@ -1125,30 +1246,32 @@ sub _menu($$) } } -sub _printindex($$) +sub format_printindex($$) { my ($self, $printindex) = @_; - return $self->_printindex_formatted($printindex); + return $self->process_printindex($printindex); } sub _normalize_top_node($) { - my $node = shift; - return Texinfo::Common::normalize_top_node_name($node); + my $node_name = shift; + return Texinfo::Common::normalize_top_node_name($node_name); } # convert and cache a node name. $NODE is a node element. -sub _node_line($$) +sub node_line($$) { my ($self, $node) = @_; + $self->{'node_lines_text'} = {} if (!$self->{'node_lines_text'}); if (!$self->{'node_lines_text'}->{$node}) { my $node_text = {'type' => '_code', 'contents' => $node->{'extra'}->{'node_content'}}; push @{$self->{'count_context'}}, {'lines' => 0, 'bytes' => 0}; - $self->{'node_lines_text'}->{$node}->{'text'} - = _normalize_top_node($self->convert_line($node_text, - {'suppress_styles' => 1})); - _update_count_context($self); + $self->{'node_lines_text'}->{$node} + = {'text' => _normalize_top_node($self->convert_line($node_text, + {'suppress_styles' => 1, + 'no_added_eol' => 1,}))}; + update_count_context($self); my $end_context = pop @{$self->{'count_context'}}; $self->{'node_lines_text'}->{$node}->{'count'} = $end_context->{'bytes'}; @@ -1159,7 +1282,7 @@ sub _node_line($$) my $index_length_to_node = 41; -sub _printindex_formatted($$;$) +sub process_printindex($$;$) { my ($self, $printindex, $in_info) = @_; @@ -1173,47 +1296,41 @@ sub _printindex_formatted($$;$) } # this is not redone for each index, only once - if (!defined($self->{'index_entries'}) and $self->{'parser'}) { + my $index_names = $self->{'indices_information'}; + if (!defined($self->{'index_entries'}) and $index_names) { - my $index_names = $self->{'parser'}->indices_information(); my $merged_index_entries = Texinfo::Structuring::merge_indices($index_names); - $self->{'index_entries'} - = Texinfo::Structuring::sort_indices($self->{'parser'}, - $merged_index_entries, $index_names); - $self->{'index_names'} = $index_names; + my $index_entries_sort_strings; + ($self->{'index_entries'}, $index_entries_sort_strings) + = Texinfo::Structuring::sort_indices($self, $self, + $merged_index_entries); } if (!$self->{'index_entries'} or !$self->{'index_entries'}->{$index_name} or ! @{$self->{'index_entries'}->{$index_name}}) { return ''; } - my $result = ''; - $result .= _add_newline_if_needed($self); - if ($in_info) { - my $info_printindex_magic = "\x{00}\x{08}[index\x{00}\x{08}]\n"; - $result .= $info_printindex_magic; - _add_text_count($self, $info_printindex_magic); - _add_lines_count($self, 1); - } - my $heading = "* Menu:\n\n"; - - $result .= $heading; - _add_text_count($self, $heading); - _add_lines_count($self, 2); - # first determine the line numbers for the spacing of their formatting my %line_nrs; my %entry_nodes; my $max_index_line_nr_string_length = 0; my %ignored_entries; foreach my $entry (@{$self->{'index_entries'}->{$index_name}}) { + # FIXME format in a way instead of ignoring + if ($entry->{'entry_element'}->{'extra'} + and ($entry->{'entry_element'}->{'extra'}->{'seeentry'} + or $entry->{'entry_element'}->{'extra'}->{'seealso'})) { + $ignored_entries{$entry} = 1; + next; + } my $line_nr; - if (defined ($self->{'index_entries_line_location'}->{$entry->{'command'}})) { - $line_nr = $self->{'index_entries_line_location'}->{$entry->{'command'}}->{'lines'}; + if ($self->{'index_entries_line_location'} + and defined($self->{'index_entries_line_location'}->{$entry->{'entry_element'}})) { + $line_nr = $self->{'index_entries_line_location'}->{$entry->{'entry_element'}}->{'lines'}; # ignore index entries in special regions that haven't been seen - } elsif ($entry->{'region'}) { + } elsif ($entry->{'entry_region'}) { $ignored_entries{$entry} = 1; next; } @@ -1221,10 +1338,12 @@ sub _printindex_formatted($$;$) my $node; # priority given to the location determined dynamically as the # index entry may be in footnote. - if (defined($self->{'index_entries_line_location'}->{$entry->{'command'}}->{'node'})) { - $node = $self->{'index_entries_line_location'}->{$entry->{'command'}}->{'node'}; - } elsif (defined($entry->{'node'})) { - $node = $entry->{'node'}; + if ($self->{'index_entries_line_location'} + and $self->{'index_entries_line_location'}->{$entry->{'entry_element'}} + and defined($self->{'index_entries_line_location'}->{$entry->{'entry_element'}}->{'node'})) { + $node = $self->{'index_entries_line_location'}->{$entry->{'entry_element'}}->{'node'}; + } elsif (defined($entry->{'entry_node'})) { + $node = $entry->{'entry_node'}; } $entry_nodes{$entry} = $node; if (!defined($node)) { @@ -1242,36 +1361,65 @@ sub _printindex_formatted($$;$) $line_nrs{$entry} = $line_nr; } + return '' if (scalar(keys(%line_nrs)) == 0); + + my $result = ''; + $result .= _add_newline_if_needed($self); + if ($in_info) { + my $info_printindex_magic = "\x{00}\x{08}[index\x{00}\x{08}]\n"; + $result .= $info_printindex_magic; + add_text_to_count($self, $info_printindex_magic); + _add_lines_count($self, 1); + } + my $heading = "* Menu:\n\n"; + + $result .= $heading; + add_text_to_count($self, $heading); + _add_lines_count($self, 2); + # this is used to count entries that are the same my %entry_counts = (); + # Use the same line formatter for all the index entries. This is + # slightly faster than making a new one for each entry. + my $formatter = $self->new_formatter('line', + {'indent' => 0, 'suppress_styles' => 1, + 'no_added_eol' => 1}); + push @{$self->{'formatters'}}, $formatter; + foreach my $entry (@{$self->{'index_entries'}->{$index_name}}) { next if ($ignored_entries{$entry}); - my $entry_tree = {'contents' => $entry->{'content'}}; + my $entry_tree = {'contents' => $entry->{'entry_content'}}; + my $subentries_tree = $self->comma_index_subentries_tree($entry); if ($entry->{'in_code'}) { $entry_tree->{'type'} = '_code'; + $subentries_tree->{'type'} = '_code' + if (defined($subentries_tree)); } else { $entry_tree->{'type'} = 'frenchspacing'; + $subentries_tree->{'type'} = 'frenchspacing' + if (defined($subentries_tree)); } my $entry_text = ''; - my $formatter = $self->new_formatter('line', - {'indent' => 0, 'suppress_styles' => 1}); - push @{$self->{'formatters'}}, $formatter; $entry_text = $self->_convert($entry_tree); - $entry_text .= $self->convert_index_subentries($entry); + $entry_text .= $self->_convert($subentries_tree) + if (defined($subentries_tree)); + $entry_text .= _count_added($self, $formatter->{'container'}, Texinfo::Convert::Paragraph::end($formatter->{'container'})); - pop @{$self->{'formatters'}}; - next if ($entry_text !~ /\S/); - # FIXME protect instead + # No need for protection, the Info readers should find the last : on + # the line. : in the node following the index entry node should be + # protected, however, as done below, such that : in the node are not + # mistaken as being part of the index entry. if ($entry_text =~ /:/ and $self->get_conf('INDEX_SPECIAL_CHARS_WARNING')) { - $self->line_warn (sprintf(__("Index entry in \@%s with : produces invalid Info: %s"), - $entry->{'index_at_command'}, - Texinfo::Convert::Texinfo::convert($entry_tree)), - $entry->{'command'}->{'line_nr'}); + $self->converter_line_warn ($self, + sprintf(__("Index entry in \@%s with : produces invalid Info: %s"), + $entry->{'index_at_command'}, + Texinfo::Convert::Texinfo::convert_to_texinfo($entry_tree)), + $entry->{'entry_element'}->{'source_info'}); } my $entry_nr = ''; @@ -1280,18 +1428,17 @@ sub _printindex_formatted($$;$) } else { $entry_counts{$entry_text}++; $entry_nr = ' <'.$entry_counts{$entry_text}.'>'; - _add_text_count($self, $entry_nr); + add_text_to_count($self, $entry_nr); } my $entry_line = "* $entry_text${entry_nr}: "; - _add_text_count($self, "* ".": "); - #_add_text_count($self, $entry_line); + add_text_to_count($self, "* ".": "); my $line_width = Texinfo::Convert::Unicode::string_width($entry_line); my $entry_line_addition = ''; if ($line_width < $index_length_to_node) { my $spaces = ' ' x ($index_length_to_node - $line_width); $entry_line_addition .= $spaces; - _add_text_count($self, $spaces); + add_text_to_count($self, $spaces); } my $node = $entry_nodes{$entry}; @@ -1301,9 +1448,9 @@ sub _printindex_formatted($$;$) if (!$self->{'outside_of_any_node_text'}) { push @{$self->{'count_context'}}, {'lines' => 0, 'bytes' => 0}; my $node_text = $self->gdt('(outside of any node)'); - $self->{'outside_of_any_node_text'}->{'text'} - = $self->convert_line($node_text); - _update_count_context($self); + $self->{'outside_of_any_node_text'} + = {'text' => $self->convert_line($node_text)}; + update_count_context($self); my $end_context = pop @{$self->{'count_context'}}; $self->{'outside_of_any_node_text'}->{'count'} = $end_context->{'bytes'}; @@ -1315,17 +1462,40 @@ sub _printindex_formatted($$;$) # done by the Parser. # Warn, only once. if (!$self->{'index_entries_no_node'}->{$entry}) { - $self->line_warn(sprintf(__("entry for index `%s' outside of any node"), - $index_name), $entry->{'command'}->{'line_nr'}); + $self->converter_line_warn($self, + sprintf(__("entry for index `%s' outside of any node"), + $index_name), $entry->{'entry_element'}->{'source_info'}); $self->{'index_entries_no_node'}->{$entry} = 1; } } else { - my ($node_line, $byte_count) = $self->_node_line($node); - $entry_line_addition .= $node_line; + my ($node_line, $byte_count) = $self->node_line($node); $self->{'count_context'}->[-1]->{'bytes'} += $byte_count; + # protect characters that need to be protected in menu node entry + # after menu entry name and also :, as the Info readers + # should consider text up to : to be part of the index entry. + if ($node_line =~ /([,\t:]|\.\s)/) { + my $warned_char = $1; + if ($self->{'info_special_chars_warning'}) { + # Warn only once + if (! $self->{'index_entry_node_colon'}->{$node_line}) { + $self->converter_line_warn($self, sprintf(__( + "node name with index entries should not contain `%s'"), + $warned_char), + $node->{'source_info'}); + } + $self->{'index_entry_node_colon'}->{$node_line} = 1; + } + if ($self->{'info_special_chars_quote'}) { + my $pre_quote = "\x{7f}"; + my $post_quote = $pre_quote; + $self->{'count_context'}->[-1]->{'bytes'} += 2; + $node_line = $pre_quote . $node_line . $post_quote; + } + } + $entry_line_addition .= $node_line; } $entry_line_addition .= '.'; - _add_text_count($self, '.'); + add_text_to_count($self, '.'); $entry_line .= $entry_line_addition; $result .= $entry_line; @@ -1345,19 +1515,21 @@ sub _printindex_formatted($$;$) . "$line_part\n"; } _add_lines_count($self, 1); - _add_text_count($self, $line_part); + add_text_to_count($self, $line_part); $result .= $line_part; } + pop @{$self->{'formatters'}}; + $result .= "\n"; - _add_text_count($self, "\n"); + add_text_to_count($self, "\n"); _add_lines_count($self, 1); return $result; } -sub _node($$) +sub format_node($$) { my $self = shift; my $node = shift; @@ -1366,10 +1538,10 @@ sub _node($$) } # no error in plaintext -sub _error_outside_of_any_node($$) +sub format_error_outside_of_any_node($$) { my $self = shift; - my $root = shift; + my $element = shift; } sub _anchor($$) @@ -1378,8 +1550,8 @@ sub _anchor($$) my $anchor = shift; if (!($self->{'multiple_pass'} or $self->{'in_copying_header'})) { - $self->_add_location($anchor); - $self->_error_outside_of_any_node($anchor); + $self->add_location($anchor); + $self->format_error_outside_of_any_node($anchor); } return ''; } @@ -1398,80 +1570,45 @@ sub ensure_end_of_line($$) } $text .= "\n"; $self->{'text_element_context'}->[-1]->{'counter'} = 0; - _add_text_count($self, "\n"); + add_text_to_count($self, "\n"); _add_lines_count($self, 1); return $text; } -sub _image_text($$$) +sub image_formatted_text($$$$) { - my ($self, $root, $basefile) = @_; - - my $txt_file = $self->Texinfo::Common::locate_include_file($basefile.'.txt'); - if (!defined($txt_file)) { - return undef; - } else { - my $filehandle = do { local *FH }; - if (open ($filehandle, $txt_file)) { - my $enc = $root->{'extra'}->{'input_perl_encoding'}; - binmode($filehandle, ":encoding($enc)") - if ($enc); - my $result = ''; - my $max_width = 0; - while (<$filehandle>) { - my $width = Texinfo::Convert::Unicode::string_width($_); - if ($width > $max_width) { - $max_width = $width; - } - $result .= $_; - } - # remove last end of line - chomp ($result); - if (!close ($filehandle)) { - $self->document_warn(sprintf(__("error on closing image text file %s: %s"), - $txt_file, $!)); - } - return ($result, $max_width); - } else { - $self->line_warn(sprintf(__("\@image file `%s' unreadable: %s"), - $txt_file, $!), $root->{'line_nr'}); - } - } - return undef; -} - -sub _image_formatted_text($$$$) -{ - my ($self, $root, $basefile, $text) = @_; + my ($self, $element, $basefile, $text) = @_; my $result; if (defined($text)) { $result = $text; - } elsif (defined($root->{'args'}->[3]) - and @{$root->{'args'}->[3]->{'contents'}}) { - $result = '[' .Texinfo::Convert::Text::convert( - {'contents' => $root->{'args'}->[3]->{'contents'}}, + } elsif (defined($element->{'args'}->[3]) + and $element->{'args'}->[3]->{'contents'} + and @{$element->{'args'}->[3]->{'contents'}}) { + $result = '[' .Texinfo::Convert::Text::convert_to_text( + {'contents' => $element->{'args'}->[3]->{'contents'}}, $self->{'convert_text_options'}) .']'; } else { - $self->line_warn(sprintf(__( + $self->converter_line_warn($self, sprintf(__( "could not find \@image file `%s.txt' nor alternate text"), - $basefile), $root->{'line_nr'}); + $basefile), $element->{'source_info'}); $result = '['.$basefile.']'; } return $result; } -sub _image($$) +sub format_image($$) { - my ($self, $root) = @_; + my ($self, $element) = @_; - if (defined($root->{'args'}->[0]) - and @{$root->{'args'}->[0]->{'contents'}}) { - my $basefile = Texinfo::Convert::Text::convert( - {'contents' => $root->{'args'}->[0]->{'contents'}}, + if (defined($element->{'args'}->[0]) + and $element->{'args'}->[0]->{'contents'} + and @{$element->{'args'}->[0]->{'contents'}}) { + my $basefile = Texinfo::Convert::Text::convert_to_text( + {'contents' => $element->{'args'}->[0]->{'contents'}}, {'code' => 1, %{$self->{'convert_text_options'}}}); - my ($text, $width) = $self->_image_text($root, $basefile); - my $result = $self->_image_formatted_text($root, $basefile, $text); + my ($text, $width) = $self->txt_image_text($element, $basefile); + my $result = $self->image_formatted_text($element, $basefile, $text); my $lines_count = ($result =~ tr/\n/\n/); if (!defined($width)) { $width = Texinfo::Convert::Unicode::string_width($result); @@ -1479,7 +1616,7 @@ sub _image($$) # the last line is part of the image but do not have a new line, # so 1 is added to $lines_count to have the number of lines of # the image - $self->_add_image($root, $lines_count+1, $width); + $self->add_image($element, $lines_count+1, $width); return ($result, $lines_count); } return ('', 0); @@ -1498,35 +1635,39 @@ sub _convert($$); # Convert the Texinfo tree under $ROOT to plain text. sub _convert($$) { - my ($self, $root) = @_; + my ($self, $element) = @_; my $formatter = $self->{'formatters'}->[-1]; - if (($root->{'type'} and $self->{'ignored_types'}->{$root->{'type'}}) - or ($root->{'cmdname'} - and ($self->{'ignored_commands'}->{$root->{'cmdname'}} - or ($inline_commands{$root->{'cmdname'}} - and $root->{'cmdname'} ne 'inlinefmtifelse' - and (($inline_format_commands{$root->{'cmdname'}} - and (!$root->{'extra'}->{'format'} - or !$self->{'expanded_formats_hash'}->{$root->{'extra'}->{'format'}})) - or (!$inline_format_commands{$root->{'cmdname'}} - and !defined($root->{'extra'}->{'expand_index'}))))))) { + if (($element->{'type'} and $self->{'ignored_types'}->{$element->{'type'}}) + or ($element->{'cmdname'} + and ($self->{'ignored_commands'}->{$element->{'cmdname'}} + or ($brace_commands{$element->{'cmdname'}} + and $brace_commands{$element->{'cmdname'}} eq 'inline' + and $element->{'cmdname'} ne 'inlinefmtifelse' + and (($inline_format_commands{$element->{'cmdname'}} + and (!$element->{'extra'}->{'format'} + or !$self->{'expanded_formats_hash'}->{$element->{'extra'}->{'format'}})) + or (!$inline_format_commands{$element->{'cmdname'}} + and !defined($element->{'extra'}->{'expand_index'}))))))) { return ''; } my $result = ''; - my $type = $root->{'type'}; - my $command = $root->{'cmdname'}; + my $type = $element->{'type'}; + my $command = $element->{'cmdname'}; # in ignorable spaces, keep only form feeds. if ($type and $self->{'ignorable_space_types'}->{$type} - and ($type ne 'empty_spaces_before_paragraph' + and ($type ne 'spaces_before_paragraph' or $self->get_conf('paragraphindent') ne 'asis')) { - if ($root->{'text'} =~ /\f/) { - $result = _get_form_feeds($root->{'text'}); + if ($type eq 'spaces_after_close_brace' + and $element->{'text'} =~ /\f/) { + # FIXME also in spaces_before_paragraph? Does not seems to be + # relevant to keep form feeds in other ignorable spaces. + $result = _get_form_feeds($element->{'text'}); } - _add_text_count($self, $result); + add_text_to_count($self, $result); return $result; } @@ -1534,15 +1675,15 @@ sub _convert($$) # of text below to be sure that an empty line is always processed # especially if ($type and ($type eq 'empty_line' - or $type eq 'after_description_line')) { + or $type eq 'after_menu_description_line')) { delete $self->{'text_element_context'}->[-1]->{'counter'}; $self->{'empty_lines_count'}++; if ($self->{'empty_lines_count'} <= 1 or $self->{'preformatted_context_commands'}->{$self->{'context'}->[-1]}) { $result = ""; - if ($root->{'text'} =~ /\f/) { - $result .= _get_form_feeds($root->{'text'}); - _add_text_count($self, $result); + if ($element->{'text'} =~ /\f/) { + $result .= _get_form_feeds($element->{'text'}); + add_text_to_count($self, $result); } $result .= _count_added($self, $formatter->{'container'}, add_text($formatter->{'container'}, "\n")); @@ -1553,49 +1694,48 @@ sub _convert($$) } # process text - if (defined($root->{'text'})) { + if (defined($element->{'text'})) { if (!$type or $type ne 'untranslated') { if (!$formatter->{'_top_formatter'}) { - if ($type and ($type eq 'raw' - or $type eq 'last_raw_newline')) { + if ($type and $type eq 'raw') { $result = _count_added($self, $formatter->{'container'}, - add_next($formatter->{'container'}, $root->{'text'})); + add_next($formatter->{'container'}, $element->{'text'})); } else { - my $text = _process_text($self, $root, $formatter); + my $text = _process_text($self, $element, $formatter); $result = _count_added($self, $formatter->{'container'}, add_text ($formatter->{'container'}, $text)); } return $result; # the following is only possible if paragraphindent is set to asis - } elsif ($type and $type eq 'empty_spaces_before_paragraph') { - _add_text_count($self, $root->{'text'}); - return $root->{'text'}; + } elsif ($type and $type eq 'spaces_before_paragraph') { + add_text_to_count($self, $element->{'text'}); + return $element->{'text'}; # ignore text outside of any format, but warn if ignored text not empty - } elsif ($root->{'text'} =~ /\S/) { - $self->_bug_message("ignored text not empty `$root->{'text'}'", $root); + } elsif ($element->{'text'} =~ /\S/) { + $self->present_bug_message("ignored text not empty `$element->{'text'}'", $element); return ''; } else { # miscellaneous top-level whitespace - possibly after an @image return _count_added($self, $formatter->{'container'}, - add_text($formatter->{'container'}, $root->{'text'})); + add_text($formatter->{'container'}, $element->{'text'})); } } else { - my $tree = $self->gdt($root->{'text'}); + my $tree = $self->gdt($element->{'text'}); my $converted = _convert($self, $tree); return $converted; } } - if ($root->{'extra'}) { - if ($root->{'extra'}->{'missing_argument'} - and (!$root->{'contents'} or !@{$root->{'contents'}})) { + if ($element->{'extra'}) { + if ($element->{'extra'}->{'missing_argument'} + and (!$element->{'contents'} or !@{$element->{'contents'}})) { return ''; } } - if ($root->{'extra'} and $root->{'extra'}->{'index_entry'} + if ($element->{'extra'} and $element->{'extra'}->{'index_entry'} and !$self->{'multiple_pass'} and !$self->{'in_copying_header'}) { - my $location = $self->_add_location($root); + my $location = $self->add_location($element); # remove a 'lines' from $location if at the very end of a node # since it will lead to the next node otherwise. if ($command and $command =~ /index/) { @@ -1634,39 +1774,47 @@ sub _convert($$) # in @insertcopying. # This also covers the case of an index entry in a node added by a # @footnote with footnotestyle separate. - if ($self->{'node'}) { - $location->{'node'} = $self->{'node'}; + if ($self->{'current_node'}) { + $location->{'node'} = $self->{'current_node'}; } - $self->{'index_entries_line_location'}->{$root} = $location; + $self->{'index_entries_line_location'}->{$element} = $location; } my $cell; my $preformatted; if ($command) { my $unknown_command; - if (defined($no_brace_commands{$command})) { + if (defined($nobrace_symbol_text{$command})) { if ($command eq ':') { remove_end_sentence($formatter->{'container'}); return ''; } elsif ($command eq '*') { $result = _count_added($self, $formatter->{'container'}, - add_pending_word($formatter->{'container'})); - $result .= _count_added($self, $formatter->{'container'}, - end_line($formatter->{'container'})); + add_pending_word($formatter->{'container'})); + # added eol in some line oriented constructs, such as @node, menu + # entry and therefore index entry would lead to end of line on + # node pointers line, in tag table, or on menu, all being invalid. + if ($formatter->{'no_added_eol'}) { + $result .= _count_added($self, $formatter->{'container'}, + add_text($formatter->{'container'}, ' ')); + } else { + $result .= _count_added($self, $formatter->{'container'}, + end_line($formatter->{'container'})); + } } elsif ($command eq '.' or $command eq '?' or $command eq '!') { $result .= _count_added($self, $formatter->{'container'}, add_next($formatter->{'container'}, $command)); add_end_sentence($formatter->{'container'}, 1); } elsif ($command eq ' ' or $command eq "\n" or $command eq "\t") { $result .= _count_added($self, $formatter->{'container'}, - add_next($formatter->{'container'}, $no_brace_commands{$command})); + add_next($formatter->{'container'}, $nobrace_symbol_text{$command})); } else { $result .= _count_added($self, $formatter->{'container'}, - add_text($formatter->{'container'}, $no_brace_commands{$command})); + add_text($formatter->{'container'}, $nobrace_symbol_text{$command})); } return $result; } elsif ($command eq 'today') { - my $today = $self->Texinfo::Common::expand_today(); + my $today = $self->Texinfo::Convert::Utils::expand_today(); unshift @{$self->{'current_contents'}->[-1]}, $today; } elsif (exists($brace_no_arg_commands{$command})) { my $text; @@ -1675,7 +1823,7 @@ sub _convert($$) # Don't use Unicode ellipsis character. $text = '...'; } else { - $text = Texinfo::Convert::Text::brace_no_arg_command($root, + $text = Texinfo::Convert::Text::brace_no_arg_command($element, $self->{'convert_text_options'}); } @@ -1729,18 +1877,18 @@ sub _convert($$) $sc = 1; } my $accented_text - = Texinfo::Convert::Text::text_accents($root, $encoding, $sc); + = Texinfo::Convert::Text::text_accents($element, $encoding, $sc); $result .= _count_added($self, $formatter->{'container'}, add_text($formatter->{'container'}, $accented_text)); my $accented_text_original; if ($formatter->{'upper_case'}) { $accented_text_original - = Texinfo::Convert::Text::text_accents($root, $encoding); + = Texinfo::Convert::Text::text_accents($element, $encoding); } if ($accented_text_original - and $accented_text_original !~ /[[:upper:]]/ + and $accented_text_original !~ /\p{Upper}/ or $formatter->{'var'} or $formatter->{'font_type_stack'}->[-1]->{'monospace'}) { allow_end_sentence($formatter->{'container'}); @@ -1753,14 +1901,15 @@ sub _convert($$) if ($accented_text ne ''); return $result; } elsif ($self->{'style_map'}->{$command} - or ($root->{'type'} and $root->{'type'} eq 'definfoenclose_command')) { - if ($code_style_commands{$command}) { + or ($element->{'type'} and $element->{'type'} eq 'definfoenclose_command')) { + if ($brace_code_commands{$command}) { if (!$formatter->{'font_type_stack'}->[-1]->{'monospace'}) { push @{$formatter->{'font_type_stack'}}, {'monospace' => 1}; } else { $formatter->{'font_type_stack'}->[-1]->{'monospace'}++; } - } elsif ($regular_font_style_commands{$command}) { + } elsif ($brace_commands{$command} + and $brace_commands{$command} eq 'style_no_code') { if ($formatter->{'font_type_stack'}->[-1]->{'monospace'}) { push @{$formatter->{'font_type_stack'}}, {'monospace' => 0, 'normal' => 1}; @@ -1783,9 +1932,9 @@ sub _convert($$) if ($formatter->{'w'} == 1); } my ($text_before, $text_after); - if ($root->{'type'} and $root->{'type'} eq 'definfoenclose_command') { - $text_before = $root->{'extra'}->{'begin'}; - $text_after = $root->{'extra'}->{'end'}; + if ($element->{'type'} and $element->{'type'} eq 'definfoenclose_command') { + $text_before = $element->{'extra'}->{'begin'}; + $text_after = $element->{'extra'}->{'end'}; } elsif ($non_quoted_commands_when_nested{$command} and $formatter->{'font_type_stack'}->[-1]->{'code_command'}) { $text_before = ''; @@ -1806,17 +1955,17 @@ sub _convert($$) $result .= _count_added($self, $formatter->{'container'}, add_next($formatter->{'container'}, $text_before, 1)) if ($text_before ne ''); - if ($root->{'args'}) { - $result .= _convert($self, $root->{'args'}->[0]); + if ($element->{'args'}) { + $result .= _convert($self, $element->{'args'}->[0]); if ($command eq 'strong' - and scalar (@{$root->{'args'}->[0]->{'contents'}}) - and $root->{'args'}->[0]->{'contents'}->[0]->{'text'} - and $root->{'args'}->[0]->{'contents'}->[0]->{'text'} =~ /^Note\s/i - and $self->{'output_format'} - and $self->{'output_format'} eq 'info') { - $self->line_warn(__( + and scalar (@{$element->{'args'}->[0]->{'contents'}}) + and $element->{'args'}->[0]->{'contents'}->[0]->{'text'} + and $element->{'args'}->[0]->{'contents'}->[0]->{'text'} =~ /^Note\s/i + and $self->{'converted_format'} + and $self->{'converted_format'} eq 'info') { + $self->converter_line_warn($self, __( "\@strong{Note...} produces a spurious cross-reference in Info; reword to avoid that"), - $root->{'line_nr'}); + $element->{'source_info'}); } } $result .= _count_added($self, $formatter->{'container'}, @@ -1827,12 +1976,13 @@ sub _convert($$) set_space_protection($formatter->{'container'},0,undef) if ($formatter->{'w'} == 0); } - if ($code_style_commands{$command}) { + if ($brace_code_commands{$command}) { $formatter->{'font_type_stack'}->[-1]->{'monospace'}--; allow_end_sentence($formatter->{'container'}); pop @{$formatter->{'font_type_stack'}} if !$formatter->{'font_type_stack'}->[-1]->{'monospace'}; - } elsif ($regular_font_style_commands{$command}) { + } elsif ($brace_commands{$command} + and $brace_commands{$command} eq 'style_no_code') { if ($formatter->{'font_type_stack'}->[-1]->{'normal'}) { $formatter->{'font_type_stack'}->[-1]->{'normal'}--; pop @{$formatter->{'font_type_stack'}} @@ -1864,9 +2014,9 @@ sub _convert($$) add_pending_word($formatter->{'container'}, 1)); # add an empty word so that following spaces aren't lost add_next($formatter->{'container'},''); - my ($image, $lines_count) = $self->_image($root); + my ($image, $lines_count) = $self->format_image($element); _add_lines_count($self, $lines_count); - _add_text_count($self, $image); + add_text_to_count($self, $image); if ($image ne '' and $formatter->{'type'} ne 'paragraph') { $self->{'empty_lines_count'} = 0; } @@ -1875,17 +2025,19 @@ sub _convert($$) } elsif ($command eq 'email') { # nothing is output for email, instead the command is substituted. my @email_contents; - if ($root->{'args'}) { + if ($element->{'args'}) { my $name; my $email; - if (scalar (@{$root->{'args'}}) == 2 - and defined($root->{'args'}->[1]) - and @{$root->{'args'}->[1]->{'contents'}}) { - $name = $root->{'args'}->[1]->{'contents'}; + if (scalar (@{$element->{'args'}}) == 2 + and defined($element->{'args'}->[1]) + and $element->{'args'}->[1]->{'contents'} + and @{$element->{'args'}->[1]->{'contents'}}) { + $name = $element->{'args'}->[1]->{'contents'}; } - if (defined($root->{'args'}->[0]) - and @{$root->{'args'}->[0]->{'contents'}}) { - $email = $root->{'args'}->[0]->{'contents'}; + if (defined($element->{'args'}->[0]) + and $element->{'args'}->[0]->{'contents'} + and @{$element->{'args'}->[0]->{'contents'}}) { + $email = $element->{'args'}->[0]->{'contents'}; } my $prepended; if ($name and $email) { @@ -1903,21 +2055,24 @@ sub _convert($$) } return ''; } elsif ($command eq 'uref' or $command eq 'url') { - if ($root->{'args'}) { - if (scalar(@{$root->{'args'}}) == 3 - and defined($root->{'args'}->[2]) - and @{$root->{'args'}->[2]->{'contents'}}) { + if ($element->{'args'}) { + if (scalar(@{$element->{'args'}}) == 3 + and defined($element->{'args'}->[2]) + and $element->{'args'}->[2]->{'contents'} + and @{$element->{'args'}->[2]->{'contents'}}) { unshift @{$self->{'current_contents'}->[-1]}, - {'contents' => $root->{'args'}->[2]->{'contents'}}; - } elsif (@{$root->{'args'}->[0]->{'contents'}}) { + {'contents' => $element->{'args'}->[2]->{'contents'}}; + } elsif ($element->{'args'}->[0]->{'contents'} + and @{$element->{'args'}->[0]->{'contents'}}) { # no mangling of --- and similar in url. my $url = {'type' => '_code', - 'contents' => $root->{'args'}->[0]->{'contents'}}; - if (scalar(@{$root->{'args'}}) == 2 - and defined($root->{'args'}->[1]) - and @{$root->{'args'}->[1]->{'contents'}}) { + 'contents' => $element->{'args'}->[0]->{'contents'}}; + if (scalar(@{$element->{'args'}}) == 2 + and defined($element->{'args'}->[1]) + and $element->{'args'}->[1]->{'contents'} + and @{$element->{'args'}->[1]->{'contents'}}) { my $prepended = $self->gdt('{text} ({url})', - {'text' => $root->{'args'}->[1]->{'contents'}, + {'text' => $element->{'args'}->[1]->{'contents'}, 'url' => $url }); unshift @{$self->{'current_contents'}->[-1]}, $prepended; } else { @@ -1925,11 +2080,11 @@ sub _convert($$) {'url' => $url}); unshift @{$self->{'current_contents'}->[-1]}, $prepended } - } elsif (scalar(@{$root->{'args'}}) == 2 - and defined($root->{'args'}->[1]) - and @{$root->{'args'}->[1]->{'contents'}}) { + } elsif (scalar(@{$element->{'args'}}) == 2 + and defined($element->{'args'}->[1]) + and @{$element->{'args'}->[1]->{'contents'}}) { unshift @{$self->{'current_contents'}->[-1]}, - {'contents' => $root->{'args'}->[1]->{'contents'}}; + {'contents' => $element->{'args'}->[1]->{'contents'}}; } } return ''; @@ -1941,23 +2096,23 @@ sub _convert($$) } else { $formatted_footnote_number = $NO_NUMBER_FOOTNOTE_SYMBOL; } - push @{$self->{'pending_footnotes'}}, {'root' => $root, + push @{$self->{'pending_footnotes'}}, {'root' => $element, 'number' => $self->{'footnote_index'}} unless ($self->{'multiple_pass'}); if (!$self->{'in_copying_header'}) { - $self->_error_outside_of_any_node($root); + $self->format_error_outside_of_any_node($element); } $result .= _count_added($self, $formatter->{'container'}, add_next($formatter->{'container'}, "($formatted_footnote_number)", 1)); - if ($self->get_conf('footnotestyle') eq 'separate' and $self->{'node'}) { + if ($self->get_conf('footnotestyle') eq 'separate' and $self->{'current_node'}) { $result .= _convert($self, {'contents' => [{'text' => ' ('}, {'cmdname' => 'pxref', 'args' => [ {'type' => 'brace_command_arg', 'contents' => [ - @{$self->{'node'}->{'extra'}->{'node_content'}}, + @{$self->{'current_node'}->{'extra'}->{'node_content'}}, {'text' => "-Footnote-$self->{'footnote_index'}"} ] } @@ -1970,12 +2125,12 @@ sub _convert($$) } elsif ($command eq 'anchor') { $result = _count_added($self, $formatter->{'container'}, add_pending_word($formatter->{'container'})); - $result .= $self->_anchor($root); + $result .= $self->_anchor($element); return $result; } elsif ($ref_commands{$command}) { - if (scalar(@{$root->{'args'}})) { + if (scalar(@{$element->{'args'}})) { my @args; - for my $a (@{$root->{'args'}}) { + for my $a (@{$element->{'args'}}) { if (defined $a->{'contents'} and @{$a->{'contents'}}) { push @args, $a->{'contents'}; } else { @@ -1988,9 +2143,9 @@ sub _convert($$) # NOTE as a consequence, the line numbers appearing in case of errors # correspond to the node lines numbers, and not the @ref. my $node_content; - if ($root->{'extra'} - and $root->{'extra'}->{'label'}) { - $node_content = $root->{'extra'}->{'label'}->{'extra'}->{'node_content'}; + if ($element->{'extra'} + and $element->{'extra'}->{'label'}) { + $node_content = $element->{'extra'}->{'label'}->{'extra'}->{'node_content'}; } else { $node_content = $args[0]; } @@ -1998,13 +2153,13 @@ sub _convert($$) # if it a reference to a float with a label, $arg[1] is # set to '$type $number' or '$number' if there is no type. if (! defined($args[1]) - and $root->{'extra'} - and $root->{'extra'}->{'label'} - and $root->{'extra'}->{'label'}->{'cmdname'} - and $root->{'extra'}->{'label'}->{'cmdname'} eq 'float') { - my $float = $root->{'extra'}->{'label'}; + and $element->{'extra'} + and $element->{'extra'}->{'label'} + and $element->{'extra'}->{'label'}->{'cmdname'} + and $element->{'extra'}->{'label'}->{'cmdname'} eq 'float') { + my $float = $element->{'extra'}->{'label'}; - my $name = $self->_float_type_number($float); + my $name = $self->float_type_number($float); $args[1] = $name->{'contents'}; } if ($command eq 'inforef' and scalar(@args) == 3) { @@ -2059,14 +2214,14 @@ sub _convert($$) .get_pending($self->{'formatters'}->[-1]->{'container'}); my $quoting_required = 0; if ($name_text_checked =~ /:/m) { - if ($self->get_conf('INFO_SPECIAL_CHARS_WARNING')) { - $self->line_warn(sprintf(__( - "\@%s cross-reference name should not contain `:'"), - $command), $root->{'line_nr'}); - } - if ($self->get_conf('INFO_SPECIAL_CHARS_QUOTE')) { - $quoting_required = 1; - } + if ($self->{'info_special_chars_warning'}) { + $self->converter_line_warn($self, sprintf(__( + "\@%s cross-reference name should not contain `:'"), + $command), $element->{'source_info'}); + } + if ($self->{'info_special_chars_quote'}) { + $quoting_required = 1; + } } my $pre_quote = $quoting_required ? "\x{7f}" : ''; my $post_quote = $pre_quote; @@ -2077,100 +2232,91 @@ sub _convert($$) _count_added($self,$self->{'formatters'}[-1]{'container'}, $pre_quote) if $pre_quote; + } - if ($file) { - $result .= _convert($self, {'contents' => $file}); - } - # node name - $self->{'formatters'}->[-1]->{'suppress_styles'} = 1; + if ($file) { + $result .= _convert($self, {'contents' => $file}); + } - my $maybe_file - = get_pending($self->{'formatters'}->[-1]->{'container'}); - my $node_text = _convert($self, {'type' => '_code', - 'contents' => $node_content}); - delete $self->{'formatters'}->[-1]->{'suppress_styles'}; + my $node_line_name; + # Get the node name to be output. + # Due to the way the paragraph formatter holds pending text, converting + # the node name with the current formatter does not yield all the + # converted text. To get the full node name (and no more), we + # can convert in a new context, using convert_line. + # However, it is slow to do this for every node. So in the most + # frequent case when the node name is a simple text element, use + # that text instead. + if (scalar(@{$node_content}) == 1 + and defined($node_content->[0]->{'text'})) { + $node_line_name = $node_content->[0]->{'text'}; + } else { + $self->{'silent'} = 0 if (!defined($self->{'silent'})); + $self->{'silent'}++; + push @{$self->{'count_context'}}, {'lines' => 0, 'bytes' => 0}; + + $node_line_name = $self->convert_line({'type' => '_code', + 'contents' => $node_content}, + {'suppress_styles' => 1, + 'no_added_eol' => 1}); + pop @{$self->{'count_context'}}; + $self->{'silent'}--; + } - my $node_text_checked = $node_text - .get_pending($self->{'formatters'}->[-1]->{'container'}); - $maybe_file =~ s/^\s*//; - $maybe_file = quotemeta $maybe_file; - $node_text_checked =~ s/^\s*$maybe_file//; - $quoting_required = 0; - if ($node_text_checked =~ /([,\t\.])/m ) { - if ($self->get_conf('INFO_SPECIAL_CHARS_WARNING')) { - $self->line_warn(sprintf(__( - "\@%s node name should not contain `%s'"), $command, $1), - $root->{'line_nr'}); - } - if ($self->get_conf('INFO_SPECIAL_CHARS_QUOTE')) { - $quoting_required = 1; - } - } - $pre_quote = $quoting_required ? "\x{7f}" : ''; - $post_quote = $pre_quote; - if ($pre_quote) { - $node_text =~ s/^(\s*)/$1$pre_quote/; - _count_added($self,$self->{'formatters'}[-1]{'container'}, - $pre_quote); - } - $result .= $node_text; - _count_added($self, $self->{'formatters'}[-1]{'container'}, - add_next($self->{'formatters'}->[-1]->{'container'}, $post_quote)) - if $post_quote; - } else { # Label same as node specification - if ($file) { - $result .= _convert($self, {'contents' => $file}); - } - $self->{'formatters'}->[-1]->{'suppress_styles'} = 1; - my $node_text = _convert($self, {'type' => '_code', - 'contents' => $node_content}); - delete $self->{'formatters'}->[-1]->{'suppress_styles'}; + my $check_chars; + if ($name) { + $check_chars = quotemeta ",\t."; + } else { + $check_chars = quotemeta ":"; + } - my $node_text_checked = $node_text - .get_pending($self->{'formatters'}->[-1]->{'container'}); - my $quoting_required = 0; - if ($node_text_checked =~ /:/m) { - if ($self->get_conf('INFO_SPECIAL_CHARS_WARNING')) { - $self->line_warn(sprintf(__( - "\@%s node name should not contain `:'"), $command), - $root->{'line_nr'}); - } - if ($self->get_conf('INFO_SPECIAL_CHARS_QUOTE')) { - $quoting_required = 1; - } + my $quoting_required = 0; + if ($node_line_name =~ /([$check_chars])/m) { + if ($self->{'info_special_chars_warning'}) { + $self->converter_line_warn($self, sprintf(__( + "\@%s node name should not contain `%s'"), $command, $1), + $element->{'source_info'}); } - my $pre_quote = $quoting_required ? "\x{7f}" : ''; - my $post_quote = $pre_quote; - $node_text .= _convert($self, {'contents' => [ - {'text' => "${post_quote}::"}]}); - _count_added($self,$self->{'formatters'}[-1]{'container'}, - $pre_quote) - if $pre_quote; - if ($pre_quote) { - # This is needed to get a pending word. We could use - # add_pending_word, but that would not include following - # punctuation in the word. - my $next = $self->{'current_contents'}->[-1]->[0]; - if ($next) { - $node_text .= _convert($self, $next); - shift @{$self->{'current_contents'}->[-1]}; - } - - $node_text =~ s/^(\s*)/$1$pre_quote/; + if ($self->{'info_special_chars_quote'}) { + $quoting_required = 1; } - $result .= $node_text; } - # we could use $formatter, but in case it was changed in _convert - # we play it safe. - my $pending = $result - .get_pending($self->{'formatters'}->[-1]->{'container'}); - # If command is @xref, the punctuation must always follow the - # command, for other commands it may be in the argument, hence the - # use of $pending. - # FIXME: is @xref really special here? - if ($name and ($command eq 'xref' - or ($pending !~ /[\.,]$/ and $pending !~ /::$/))) { + my $pre_quote = $quoting_required ? "\x{7f}" : ''; + my $post_quote = $pre_quote; + + # node name + my $node_text = ''; + $node_text .= _count_added($self, + $self->{'formatters'}[-1]{'container'}, + add_next($self->{'formatters'}->[-1]->{'container'}, $pre_quote)) + if $pre_quote; + + $self->{'formatters'}->[-1]->{'suppress_styles'} = 1; + $node_text .= _convert($self, {'type' => '_code', + 'contents' => $node_content}); + delete $self->{'formatters'}->[-1]->{'suppress_styles'}; + + $node_text .= _count_added($self, + $self->{'formatters'}[-1]{'container'}, + add_next($self->{'formatters'}->[-1]->{'container'}, $post_quote)) + if $post_quote; + + if (!$name) { + $node_text .= _count_added($self, + $self->{'formatters'}[-1]{'container'}, + add_next($self->{'formatters'}->[-1]->{'container'}, '::')); + } + + $result .= $node_text; + + # Check if punctuation follows the ref command. + # + # FIXME: is @xref really special here? Original comment: + # "If command is @xref, the punctuation must always follow the + # command, for other commands it may be in the argument..." + + if ($name) { my $next = $self->{'current_contents'}->[-1]->[0]; if (!($next and $next->{'text'} and $next->{'text'} =~ /^[\.,]/)) { if ($command eq 'xref') { @@ -2178,12 +2324,13 @@ sub _convert($$) my $text = $next->{'text'}; $text =~ s/^\s*//; my $char = substr($text, 0, 1); - $self->line_warn(sprintf(__( - "`.' or `,' must follow \@xref, not %s"), - $char), $root->{'line_nr'}); + $self->converter_line_warn($self, sprintf(__( + "`.' or `,' must follow \@xref, not %s"), + $char), $element->{'source_info'}); } else { - $self->line_warn(__("`.' or `,' must follow \@xref"), - $root->{'line_nr'}); + $self->converter_line_warn($self, + __("`.' or `,' must follow \@xref"), + $element->{'source_info'}); } } my @added = ({'text' => '.'}); @@ -2207,23 +2354,24 @@ sub _convert($$) } return ''; } elsif ($explained_commands{$command}) { - if ($root->{'args'} - and defined($root->{'args'}->[0]) - and @{$root->{'args'}->[0]->{'contents'}}) { + if ($element->{'args'} + and defined($element->{'args'}->[0]) + and $element->{'args'}->[0]->{'contents'} + and @{$element->{'args'}->[0]->{'contents'}}) { # in abbr spaces never end a sentence. my $argument; if ($command eq 'abbr') { $argument = {'type' => 'frenchspacing', - 'contents' => $root->{'args'}->[0]->{'contents'}}; + 'contents' => $element->{'args'}->[0]->{'contents'}}; } else { - $argument = { 'contents' => $root->{'args'}->[0]->{'contents'}}; + $argument = { 'contents' => $element->{'args'}->[0]->{'contents'}}; } - if (scalar (@{$root->{'args'}}) == 2 - and defined($root->{'args'}->[-1]) - and @{$root->{'args'}->[-1]->{'contents'}}) { + if (scalar (@{$element->{'args'}}) == 2 + and defined($element->{'args'}->[-1]) + and @{$element->{'args'}->[-1]->{'contents'}}) { my $prepended = $self->gdt('{abbr_or_acronym} ({explanation})', {'abbr_or_acronym' => $argument, - 'explanation' => $root->{'args'}->[-1]->{'contents'}}); + 'explanation' => $element->{'args'}->[-1]->{'contents'}}); unshift @{$self->{'current_contents'}->[-1]}, $prepended; return ''; } else { @@ -2235,31 +2383,33 @@ sub _convert($$) } } return ''; - } elsif ($inline_commands{$command}) { + } elsif ($brace_commands{$command} + and $brace_commands{$command} eq 'inline') { my $arg_index = 1; if ($command eq 'inlinefmtifelse' - and (!$root->{'extra'}->{'format'} - or !$self->{'expanded_formats_hash'}->{$root->{'extra'}->{'format'}})) { + and (!$element->{'extra'}->{'format'} + or !$self->{'expanded_formats_hash'}->{$element->{'extra'}->{'format'}})) { $arg_index = 2; } - if (scalar(@{$root->{'args'}}) > $arg_index - and defined($root->{'args'}->[$arg_index]) - and @{$root->{'args'}->[$arg_index]->{'contents'}}) { - my $argument; + if (scalar(@{$element->{'args'}}) > $arg_index + and defined($element->{'args'}->[$arg_index]) + and $element->{'args'}->[$arg_index]->{'contents'} + and @{$element->{'args'}->[$arg_index]->{'contents'}}) { + my $argument = {}; if ($command eq 'inlineraw') { $argument->{'type'} = '_code'; } - $argument->{'contents'} = $root->{'args'}->[$arg_index]->{'contents'}; + $argument->{'contents'} = $element->{'args'}->[$arg_index]->{'contents'}; unshift @{$self->{'current_contents'}->[-1]}, ($argument); } return ''; # condition should actually be that the $command is inline } elsif ($math_commands{$command} and not exists($block_commands{$command})) { push @{$self->{'context'}}, $command; - if ($root->{'args'}) { + if ($element->{'args'}) { $result .= _convert($self, {'type' => 'frenchspacing', 'contents' => [{'type' => '_code', - 'contents' => [$root->{'args'}->[0]]}]}); + 'contents' => [$element->{'args'}->[0]]}]}); } my $old_context = pop @{$self->{'context'}}; die if ($old_context ne $command); @@ -2267,66 +2417,43 @@ sub _convert($$) } elsif ($command eq 'titlefont') { push @{$self->{'count_context'}}, {'lines' => 0, 'bytes' => 0}; $result = $self->convert_line ({'type' => 'frenchspacing', - 'contents' => [$root->{'args'}->[0]]}); + 'contents' => [$element->{'args'}->[0]]}); pop @{$self->{'count_context'}}; - $result = Texinfo::Convert::Text::heading({'level' => 0, - 'cmdname' => 'titlefont'}, $result, $self, - $self->get_conf('NUMBER_SECTIONS'), - ($self->{'format_context'}->[-1]->{'indent_level'}) * $indent_length); + $result = Texinfo::Convert::Text::text_heading( + {'structure' => {'section_level' => 0}, + 'cmdname' => 'titlefont'}, + $result, $self, $self->get_conf('NUMBER_SECTIONS'), + ($self->{'format_context'}->[-1]->{'indent_level'}) * $indent_length); $result =~ s/\n$//; # final newline has its own tree element $self->{'empty_lines_count'} = 0 unless ($result eq ''); - _add_text_count($self, $result); + add_text_to_count($self, $result); _add_lines_count($self, 1); return $result; } elsif ($command eq 'U') { my $arg; - if ($root->{'args'} - and $root->{'args'}->[0] - and $root->{'args'}->[0]->{'contents'} - and $root->{'args'}->[0]->{'contents'}->[0] - and $root->{'args'}->[0]->{'contents'}->[0]->{'text'}) { - $arg = $root->{'args'}->[0]->{'contents'}->[0]->{'text'}; + if ($element->{'args'} + and $element->{'args'}->[0] + and $element->{'args'}->[0]->{'contents'} + and $element->{'args'}->[0]->{'contents'}->[0] + and $element->{'args'}->[0]->{'contents'}->[0]->{'text'}) { + $arg = $element->{'args'}->[0]->{'contents'}->[0]->{'text'}; } if ($arg) { # Syntactic checks on the value were already done in Parser.pm, # but we have one more thing to test: since this is the one # place where we might output actual UTF-8 binary bytes, we have - # to check that chr(hex($arg)) is valid. Perl gives a warning - # and will not output UTF-8 for Unicode non-characters such as - # U+10FFFF. In this case, silently fall back to plain text, on - # the theory that the user wants something. + # to check that it is possible. If not, silently fall back to + # plain text, on the theory that the user wants something. my $res; if ($self->{'to_utf8'}) { - my $error = 0; - # The warning about non-characters is only given when the code - # point is attempted to be output, not just manipulated. - # http://stackoverflow.com/questions/5127725/how-could-i-catch-an-unicode-non-character-warning - # - # Therefore, we have to try to output it within an eval. - # Since opening /dev/null or a temporary file means - # more system-dependent checks, use a string as our - # filehandle. - eval { - use warnings FATAL => qw(all); - my ($fh, $string); - open($fh, ">", \$string) || die "open(U string eval) failed: $!"; - binmode($fh, ":utf8") || die "binmode(U string eval) failed: $!"; - print $fh chr(hex("$arg")); - }; - if ($@) { - warn "\@U chr(hex($arg)) eval failed: $@\n" if ($self->{'DEBUG'}); - $error = 1; - } elsif (hex($arg) > 0x10FFFF) { - # The check above appears not to work in older versions of perl, - # so check the argument is not greater the maximum Unicode code - # point. - $error = 1; - } - if ($error) { - $res = "U+$arg"; - } else { + my $possible_conversion + = Texinfo::Convert::Unicode::check_unicode_point_conversion($arg, + $self->{'DEBUG'}); + if ($possible_conversion) { $res = chr(hex($arg)); # ok to call chr + } else { + $res = "U+$arg"; } } else { $res = "U+$arg"; # not outputting UTF-8 @@ -2340,7 +2467,7 @@ sub _convert($$) } elsif ($command eq 'value') { my $expansion = $self->gdt('@{No value for `{value}\'@}', - {'value' => $root->{'type'}}); + {'value' => $element->{'extra'}->{'flag'}}); if ($formatter->{'_top_formatter'}) { $expansion = {'type' => 'paragraph', 'contents' => [$expansion]}; @@ -2349,9 +2476,9 @@ sub _convert($$) # unshift @{$self->{'current_contents'}->[-1]}, $expansion; #return ''; return $result; - } elsif ($root->{'args'} and $root->{'args'}->[0] - and $root->{'args'}->[0]->{'type'} - and $root->{'args'}->[0]->{'type'} eq 'brace_command_arg') { + } elsif ($element->{'args'} and $element->{'args'}->[0] + and $element->{'args'}->[0]->{'type'} + and $element->{'args'}->[0]->{'type'} eq 'brace_command_arg') { # block commands } elsif (exists($block_commands{$command})) { # remark: @@ -2372,7 +2499,8 @@ sub _convert($$) push @{$self->{'context'}}, $command; } elsif ($flush_commands{$command}) { push @{$self->{'context'}}, $command; - } elsif ($raw_commands{$command} or $block_math_commands{$command}) { + } elsif ($block_commands{$command} eq 'raw' # can only be @verbatim in practice + or $block_math_commands{$command}) { if (!$self->{'formatters'}->[-1]->{'_top_formatter'}) { # reuse the current formatter if not in top level $result .= _count_added($self, $formatter->{'container'}, @@ -2411,28 +2539,28 @@ sub _convert($$) } if ($command eq 'quotation' or $command eq 'smallquotation') { - if ($root->{'args'} and $root->{'args'}->[0] - and $root->{'args'}->[0]->{'contents'} - and @{$root->{'args'}->[0]->{'contents'}}) { + if ($element->{'args'} and $element->{'args'}->[0] + and $element->{'args'}->[0]->{'contents'} + and @{$element->{'args'}->[0]->{'contents'}}) { my $prepended = $self->gdt('@b{{quotation_arg}:} ', - {'quotation_arg' => $root->{'args'}->[0]->{'contents'}}); + {'quotation_arg' => $element->{'args'}->[0]->{'contents'}}); $prepended->{'type'} = 'frenchspacing'; $result .= $self->convert_line($prepended); - $self->{'text_element_context'}->[-1]->{'counter'} += + $self->{'text_element_context'}->[-1]->{'counter'} += Texinfo::Convert::Unicode::string_width($result); $self->{'empty_lines_count'} = 0 unless ($result eq ''); } } elsif ($menu_commands{$command}) { - $result .= $self->_menu($root); + $result .= $self->_menu($element); } elsif ($command eq 'multitable') { my $columnsize; - if ($root->{'extra'}->{'columnfractions'}) { - foreach my $fraction (@{$root->{'extra'}->{'columnfractions'} + if ($element->{'extra'}->{'columnfractions'}) { + foreach my $fraction (@{$element->{'extra'}->{'columnfractions'} ->{'extra'}->{'misc_args'}}) { push @$columnsize, int($fraction * $self->{'text_element_context'}->[-1]->{'max'} +0.5); } - } elsif ($root->{'extra'}->{'prototypes'}) { - foreach my $prototype (@{$root->{'extra'}->{'prototypes'}}) { + } elsif ($element->{'extra'}->{'prototypes'}) { + foreach my $prototype (@{$element->{'extra'}->{'prototypes'}}) { push @{$self->{'count_context'}}, {'lines' => 0, 'bytes' => 0}; my ($formatted_prototype) = $self->convert_line($prototype, {'indent_length' => 0}); @@ -2447,29 +2575,30 @@ sub _convert($$) $self->{'document_context'}->[-1]->{'in_multitable'}++; } elsif ($command eq 'float') { $result .= _add_newline_if_needed($self); - if ($root->{'extra'} and $root->{'extra'}->{'node_content'}) { - $result .= $self->_anchor($root); + if ($element->{'extra'} and $element->{'extra'}->{'node_content'}) { + $result .= $self->_anchor($element); } } } elsif ($command eq 'node') { - $self->{'node'} = $root; - $result .= $self->_node($root); + $self->{'current_node'} = $element; + $result .= $self->format_node($element); $self->{'format_context'}->[-1]->{'paragraph_count'} = 0; - } elsif ($sectioning_commands{$command}) { + } elsif ($sectioning_heading_commands{$command}) { # use settitle for empty @top # ignore @part my $contents; - if ($root->{'args'}->[0]->{'contents'} - and @{$root->{'args'}->[0]->{'contents'}} + if ($element->{'args'}->[0]->{'contents'} + and @{$element->{'args'}->[0]->{'contents'}} and $command ne 'part') { - $contents = $root->{'args'}->[0]->{'contents'}; + $contents = $element->{'args'}->[0]->{'contents'}; } elsif ($command eq 'top' - and $self->{'extra'}->{'settitle'} - and $self->{'extra'}->{'settitle'}->{'args'} - and @{$self->{'extra'}->{'settitle'}->{'args'}} - and $self->{'extra'}->{'settitle'}->{'args'}->[0]->{'contents'} - and @{$self->{'extra'}->{'settitle'}->{'args'}->[0]->{'contents'}}) { - $contents = $self->{'extra'}->{'settitle'}->{'args'}->[0]->{'contents'}; + and $self->{'global_commands'}->{'settitle'} + and $self->{'global_commands'}->{'settitle'}->{'args'} + and @{$self->{'global_commands'}->{'settitle'}->{'args'}} + and $self->{'global_commands'}->{'settitle'}->{'args'}->[0]->{'contents'} + and @{$self->{'global_commands'}->{'settitle'}->{'args'}->[0]->{'contents'}}) { + $contents = + $self->{'global_commands'}->{'settitle'}->{'args'}->[0]->{'contents'}; } if ($contents) { @@ -2480,13 +2609,13 @@ sub _convert($$) # @* leads to an end of line, underlying appears on the line below # over one line my $heading_underlined = - Texinfo::Convert::Text::heading($root, $heading, $self, + Texinfo::Convert::Text::text_heading($element, $heading, $self, $self->get_conf('NUMBER_SECTIONS'), ($self->{'format_context'}->[-1]->{'indent_level'}) * $indent_length); $result .= _add_newline_if_needed($self); $self->{'empty_lines_count'} = 0 unless ($heading_underlined eq ''); - _add_text_count($self, $heading_underlined); + add_text_to_count($self, $heading_underlined); $result .= $heading_underlined; if ($heading_underlined ne '') { _add_lines_count($self, 2); @@ -2495,17 +2624,17 @@ sub _convert($$) } $self->{'format_context'}->[-1]->{'paragraph_count'} = 0; } elsif (($command eq 'item' or $command eq 'itemx') - and $root->{'args'} and $root->{'args'}->[0] - and $root->{'args'}->[0]->{'type'} - and $root->{'args'}->[0]->{'type'} eq 'line_arg') { - if ($root->{'args'} and @{$root->{'args'}} - and $root->{'args'}->[0]->{'contents'}) { + and $element->{'args'} and $element->{'args'}->[0] + and $element->{'args'}->[0]->{'type'} + and $element->{'args'}->[0]->{'type'} eq 'line_arg') { + if ($element->{'args'} and @{$element->{'args'}} + and $element->{'args'}->[0]->{'contents'}) { - my $converted_tree = $self->_table_item_content_tree($root, - $root->{'args'}->[0]->{'contents'}); + my $table_item_tree = $self->table_item_content_tree($element, + $element->{'args'}->[0]->{'contents'}); - $converted_tree->{'type'} = 'frenchspacing'; - $result = $self->convert_line($converted_tree, + $table_item_tree->{'type'} = 'frenchspacing'; + $result = $self->convert_line($table_item_tree, {'indent_level' => $self->{'format_context'}->[-1]->{'indent_level'} -1}); if ($result ne '') { @@ -2513,38 +2642,39 @@ sub _convert($$) $self->{'empty_lines_count'} = 0; } } - } elsif ($command eq 'item' and $root->{'parent'}->{'cmdname'} - and $item_container_commands{$root->{'parent'}->{'cmdname'}}) { + } elsif ($command eq 'item' and $element->{'parent'}->{'cmdname'} + and $block_commands{$element->{'parent'}->{'cmdname'}} + and $block_commands{$element->{'parent'}->{'cmdname'}} eq 'item_container') { $self->{'format_context'}->[-1]->{'paragraph_count'} = 0; my $line = $self->new_formatter('line', {'indent_length' => ($self->{'format_context'}->[-1]->{'indent_level'} -1) * $indent_length - + $item_indent_format_length{$root->{'parent'}->{'cmdname'}}}); + + $item_indent_format_length{$element->{'parent'}->{'cmdname'}}}); push @{$self->{'formatters'}}, $line; - if ($root->{'parent'}->{'cmdname'} eq 'enumerate') { + if ($element->{'parent'}->{'cmdname'} eq 'enumerate') { $result = _count_added($self, $line->{'container'}, add_next($line->{'container'}, Texinfo::Common::enumerate_item_representation( - $root->{'parent'}->{'extra'}->{'enumerate_specification'}, - $root->{'extra'}->{'item_number'}) . '. ')); - } elsif ($root->{'parent'}->{'args'} - and $root->{'parent'}->{'args'}->[0]) { + $element->{'parent'}->{'extra'}->{'enumerate_specification'}, + $element->{'extra'}->{'item_number'}) . '. ')); + } elsif ($element->{'parent'}->{'args'} + and $element->{'parent'}->{'args'}->[0]) { # this is the text prepended to items. - $result = _convert($self, $root->{'parent'}->{'args'}->[0]); + $result = _convert($self, $element->{'parent'}->{'args'}->[0]); $result .= _convert($self, { 'text' => ' ' }); } $result .= _count_added($self, $line->{'container'}, Texinfo::Convert::Paragraph::end($line->{'container'})); pop @{$self->{'formatters'}}; - $self->{'text_element_context'}->[-1]->{'counter'} += + $self->{'text_element_context'}->[-1]->{'counter'} += Texinfo::Convert::Unicode::string_width($result); $self->{'empty_lines_count'} = 0 unless ($result eq ''); # open a multitable cell } elsif ($command eq 'headitem' or $command eq 'item' or $command eq 'tab') { - my $cell_width = $self->{'format_context'}->[-1]->{'columns_size'}->[$root->{'extra'}->{'cell_number'}-1]; + my $cell_width = $self->{'format_context'}->[-1]->{'columns_size'}->[$element->{'extra'}->{'cell_number'}-1]; $self->{'format_context'}->[-1]->{'item_command'} = $command if ($command ne 'tab'); die if (!defined($cell_width)); @@ -2565,13 +2695,13 @@ sub _convert($$) 'locations' => []}; $result = $self->convert_line ( {'type' => 'frenchspacing', - 'contents' => $root->{'args'}->[0]->{'contents'}}, + 'contents' => $element->{'args'}->[0]->{'contents'}}, {'indent_length' => 0}); if ($result ne '') { $result = $self->ensure_end_of_line($result); $result = $self->_align_environment ($result, - $self->{'text_element_context'}->[-1]->{'max'}, 'center'); + $self->{'text_element_context'}->[-1]->{'max'}, 'center'); $self->{'empty_lines_count'} = 0; } else { # it has to be done here, as it is done in _align_environment above @@ -2581,11 +2711,11 @@ sub _convert($$) return $result; } elsif ($command eq 'exdent') { if ($self->{'preformatted_context_commands'}->{$self->{'context'}->[-1]}) { - $result = $self->convert_unfilled({'contents' => $root->{'args'}->[0]->{'contents'}}, + $result = $self->_convert_unfilled({'contents' => $element->{'args'}->[0]->{'contents'}}, {'indent_level' => $self->{'format_context'}->[-1]->{'indent_level'} -1}); } else { - $result = $self->convert_line({'contents' => $root->{'args'}->[0]->{'contents'}}, + $result = $self->convert_line({'contents' => $element->{'args'}->[0]->{'contents'}}, {'indent_level' => $self->{'format_context'}->[-1]->{'indent_level'} -1}); } @@ -2595,26 +2725,28 @@ sub _convert($$) } return $result; } elsif ($command eq 'verbatiminclude') { - my $expansion = $self->Texinfo::Common::expand_verbatiminclude($root); + my $expansion = Texinfo::Convert::Utils::expand_verbatiminclude($self, + $self, $element); unshift @{$self->{'current_contents'}->[-1]}, $expansion if ($expansion); return ''; } elsif ($command eq 'insertcopying') { - if ($self->{'extra'} and $self->{'extra'}->{'copying'}) { + if ($self->{'global_commands'} + and $self->{'global_commands'}->{'copying'}) { unshift @{$self->{'current_contents'}->[-1]}, - {'contents' => $self->{'extra'}->{'copying'}->{'contents'}}; + {'contents' => $self->{'global_commands'}->{'copying'}->{'contents'}}; } return ''; } elsif ($command eq 'printindex') { - $result = $self->_printindex($root); + $result = $self->format_printindex($element); return $result; } elsif ($command eq 'listoffloats') { my $lines_count = 0; - if ($root->{'extra'} and $root->{'extra'}->{'type'} - and defined($root->{'extra'}->{'type'}->{'normalized'}) + if ($element->{'extra'} and $element->{'extra'}->{'type'} + and defined($element->{'extra'}->{'type'}->{'normalized'}) and $self->{'floats'} - and $self->{'floats'}->{$root->{'extra'}->{'type'}->{'normalized'}} - and @{$self->{'floats'}->{$root->{'extra'}->{'type'}->{'normalized'}}}) { + and $self->{'floats'}->{$element->{'extra'}->{'type'}->{'normalized'}} + and @{$self->{'floats'}->{$element->{'extra'}->{'type'}->{'normalized'}}}) { push @{$self->{'count_context'}}, {'lines' => 0, 'bytes' => 0}; if (!$self->{'empty_lines_count'}) { $result .= "\n"; @@ -2622,12 +2754,12 @@ sub _convert($$) } $result .= "* Menu:\n\n"; $lines_count += 2; - foreach my $float (@{$self->{'floats'}->{$root->{'extra'}->{'type'}->{'normalized'}}}) { + foreach my $float (@{$self->{'floats'}->{$element->{'extra'}->{'type'}->{'normalized'}}}) { next if !$float->{'args'} or !$float->{'args'}->[1] or !@{$float->{'args'}->[1]->{'contents'}}; my $float_label_text = $self->convert_line({'type' => '_code', 'contents' => $float->{'args'}->[1]->{'contents'}}); - my $float_entry = $self->_float_type_number($float); + my $float_entry = $self->float_type_number($float); my $float_entry_text = ':'; if (defined($float_entry)) { $float_entry->{'type'} = 'frenchspacing'; @@ -2687,15 +2819,15 @@ sub _convert($$) pop @{$self->{'count_context'}}; } $self->{'format_context'}->[-1]->{'paragraph_count'}++; - _add_text_count($self, $result); + add_text_to_count($self, $result); _add_lines_count($self, $lines_count); return $result; } elsif ($command eq 'sp') { - if ($root->{'extra'}->{'misc_args'}->[0]) { + if ($element->{'extra'}->{'misc_args'}->[0]) { $result = _count_added($self, $formatter->{'container'}, add_pending_word($formatter->{'container'})); # this useless copy avoids perl changing the type to integer! - my $sp_nr = $root->{'extra'}->{'misc_args'}->[0]; + my $sp_nr = $element->{'extra'}->{'misc_args'}->[0]; for (my $i = 0; $i < $sp_nr; $i++) { $result .= _count_added($self, $formatter->{'container'}, end_line($formatter->{'container'})); @@ -2710,10 +2842,10 @@ sub _convert($$) and $self->{'structuring'}->{'sectioning_root'}) { my $lines_count; ($result, $lines_count) - = $self->_contents($self->{'structuring'}->{'sectioning_root'}, + = $self->format_contents($self->{'structuring'}->{'sectioning_root'}, 'contents'); _add_lines_count($self, $lines_count); - _add_text_count($self, $result); + add_text_to_count($self, $result); } return $result; } elsif ($command eq 'shortcontents' @@ -2722,25 +2854,25 @@ sub _convert($$) and $self->{'structuring'}->{'sectioning_root'}) { my $lines_count; ($result, $lines_count) - = $self->_contents($self->{'structuring'}->{'sectioning_root'}, + = $self->format_contents($self->{'structuring'}->{'sectioning_root'}, 'shortcontents'); _add_lines_count($self, $lines_count); - _add_text_count($self, $result); + add_text_to_count($self, $result); } return $result; # all the @-commands that have an information for the formatting, like # @paragraphindent, @frenchspacing... } elsif ($informative_commands{$command}) { - $self->_informative_command($root); + Texinfo::Common::set_informative_command_value($self, $element); return ''; } else { $unknown_command = 1; } if ($unknown_command - and !($root->{'extra'} - and ($root->{'extra'}->{'index_entry'} - or $root->{'extra'}->{'seeentry'} - or $root->{'extra'}->{'seealso'})) + and !($element->{'extra'} + and ($element->{'extra'}->{'index_entry'} + or $element->{'extra'}->{'seeentry'} + or $element->{'extra'}->{'seealso'})) # commands like def*x are not processed above, since only the def_line # associated is processed. If they have no name and no category they # are not considered as index entries either so they have a specific @@ -2754,16 +2886,16 @@ sub _convert($$) # open 'type' constructs. my $paragraph; - if ($root->{'type'}) { - if ($root->{'type'} eq 'paragraph') { + if ($element->{'type'}) { + if ($element->{'type'} eq 'paragraph') { $self->{'empty_lines_count'} = 0; - my $conf; + my $conf = {}; # indent. Not first paragraph. if ($self->{'format_context'}->[-1]->{'cmdname'} eq '_top_format' and $self->get_conf('paragraphindent') ne 'asis' and $self->get_conf('paragraphindent') - and (($root->{'extra'} and $root->{'extra'}->{'indent'}) - or (!($root->{'extra'} and $root->{'extra'}->{'noindent'}) + and (($element->{'extra'} and $element->{'extra'}->{'indent'}) + or (!($element->{'extra'} and $element->{'extra'}->{'noindent'}) and ($self->{'format_context'}->[-1]->{'paragraph_count'} or $self->get_conf('firstparagraphindent') eq 'insert') and !$self->{'text_element_context'}->[-1]->{'counter'}))) { @@ -2778,13 +2910,13 @@ sub _convert($$) push @{$self->{'count_context'}}, {'lines' => 0, 'bytes' => 0, 'locations' => []}; } - } elsif ($root->{'type'} eq 'preformatted' - or $root->{'type'} eq 'rawpreformatted') { + } elsif ($element->{'type'} eq 'preformatted' + or $element->{'type'} eq 'rawpreformatted') { # if in a description reuse the main menu unfilled, to keep things # simpler and avoid having to do a separate count. - if ($root->{'type'} eq 'rawpreformatted' - or !$root->{'parent'}->{'type'} - or $root->{'parent'}->{'type'} ne 'menu_entry_description') { + if ($element->{'type'} eq 'rawpreformatted' + or !$element->{'parent'}->{'type'} + or $element->{'parent'}->{'type'} ne 'menu_entry_description') { $preformatted = $self->new_formatter('unfilled'); push @{$self->{'formatters'}}, $preformatted; if ($self->{'context'}->[-1] eq 'flushright') { @@ -2792,173 +2924,216 @@ sub _convert($$) 'locations' => []}; } } - } elsif ($root->{'type'} eq 'def_line') { - if ($root->{'extra'} and $root->{'extra'}->{'def_parsed_hash'} - and %{$root->{'extra'}->{'def_parsed_hash'}}) { - my $arguments = Texinfo::Common::definition_arguments_content($root); + } elsif ($element->{'type'} eq 'def_line') { + if ($element->{'extra'} and $element->{'extra'}->{'def_parsed_hash'} + and %{$element->{'extra'}->{'def_parsed_hash'}}) { + my $arguments = Texinfo::Convert::Utils::definition_arguments_content($element); my $tree; my $command; - if ($Texinfo::Common::def_aliases{$root->{'extra'}->{'def_command'}}) { - $command = $Texinfo::Common::def_aliases{$root->{'extra'}->{'def_command'}}; + if ($Texinfo::Common::def_aliases{$element->{'extra'}->{'def_command'}}) { + $command = $Texinfo::Common::def_aliases{$element->{'extra'}->{'def_command'}}; } else { - $command = $root->{'extra'}->{'def_command'}; + $command = $element->{'extra'}->{'def_command'}; } my $name; - if ($root->{'extra'}->{'def_parsed_hash'}->{'name'}) { - $name = $root->{'extra'}->{'def_parsed_hash'}->{'name'}; + if ($element->{'extra'}->{'def_parsed_hash'}->{'name'}) { + $name = $element->{'extra'}->{'def_parsed_hash'}->{'name'}; } else { $name = ''; } + + my $omit_def_space = $element->{'extra'}->{'omit_def_name_space'}; if ($command eq 'deffn' or $command eq 'defvr' or $command eq 'deftp' or (($command eq 'deftypefn' or $command eq 'deftypevr') - and !$root->{'extra'}->{'def_parsed_hash'}->{'type'})) { + and !$element->{'extra'}->{'def_parsed_hash'}->{'type'})) { if ($arguments) { - $tree = $self->gdt("\@tie{}-- {category}: {name} {arguments}", { - 'category' => $root->{'extra'}->{'def_parsed_hash'}->{'category'}, + my $strings = { + 'category' => $element->{'extra'}->{'def_parsed_hash'}->{'category'}, 'name' => $name, - 'arguments' => $arguments}); + 'arguments' => $arguments}; + if ($omit_def_space) { + $tree = $self->gdt('@tie{}-- {category}: {name}{arguments}', $strings); + } else { + $tree = $self->gdt('@tie{}-- {category}: {name} {arguments}', $strings); + } } else { - $tree = $self->gdt("\@tie{}-- {category}: {name}", { - 'category' => $root->{'extra'}->{'def_parsed_hash'}->{'category'}, + $tree = $self->gdt('@tie{}-- {category}: {name}', { + 'category' => $element->{'extra'}->{'def_parsed_hash'}->{'category'}, 'name' => $name}); } } elsif ($command eq 'deftypefn' or $command eq 'deftypevr') { if ($arguments) { my $strings = { - 'category' => $root->{'extra'}->{'def_parsed_hash'}->{'category'}, + 'category' => $element->{'extra'}->{'def_parsed_hash'}->{'category'}, 'name' => $name, - 'type' => $root->{'extra'}->{'def_parsed_hash'}->{'type'}, + 'type' => $element->{'extra'}->{'def_parsed_hash'}->{'type'}, 'arguments' => $arguments}; - if ($self->get_conf('deftypefnnewline') eq 'on') { - $tree = $self->gdt("\@tie{}-- {category}:\@*{type}\@*{name} {arguments}", - $strings); + if ($self->get_conf('deftypefnnewline') eq 'on' + and $command eq 'deftypefn') { + if ($omit_def_space) { + $tree = $self->gdt('@tie{}-- {category}:@*{type}@*{name}{arguments}', + $strings); + } else { + $tree = $self->gdt('@tie{}-- {category}:@*{type}@*{name} {arguments}', + $strings); + } } else { - $tree = $self->gdt("\@tie{}-- {category}: {type} {name} {arguments}", - $strings); + if ($omit_def_space) { + $tree = $self->gdt('@tie{}-- {category}: {type} {name}{arguments}', + $strings); + } else { + $tree = $self->gdt('@tie{}-- {category}: {type} {name} {arguments}', + $strings); + } } } else { my $strings = { - 'category' => $root->{'extra'}->{'def_parsed_hash'}->{'category'}, - 'type' => $root->{'extra'}->{'def_parsed_hash'}->{'type'}, + 'category' => $element->{'extra'}->{'def_parsed_hash'}->{'category'}, + 'type' => $element->{'extra'}->{'def_parsed_hash'}->{'type'}, 'name' => $name}; - if ($self->get_conf('deftypefnnewline') eq 'on') { - $tree = $self->gdt("\@tie{}-- {category}:\@*{type}\@*{name}", + if ($self->get_conf('deftypefnnewline') eq 'on' + and $command eq 'deftypefn') { + $tree = $self->gdt('@tie{}-- {category}:@*{type}@*{name}', $strings); } else { - $tree = $self->gdt("\@tie{}-- {category}: {type} {name}", + $tree = $self->gdt('@tie{}-- {category}: {type} {name}', $strings); } } } elsif ($command eq 'defcv' or ($command eq 'deftypecv' - and !$root->{'extra'}->{'def_parsed_hash'}->{'type'})) { + and !$element->{'extra'}->{'def_parsed_hash'}->{'type'})) { if ($arguments) { - $tree = $self->gdt("\@tie{}-- {category} of {class}: {name} {arguments}", { - 'category' => $root->{'extra'}->{'def_parsed_hash'}->{'category'}, + my $strings = { + 'category' => $element->{'extra'}->{'def_parsed_hash'}->{'category'}, 'name' => $name, - 'class' => $root->{'extra'}->{'def_parsed_hash'}->{'class'}, - 'arguments' => $arguments}); + 'class' => $element->{'extra'}->{'def_parsed_hash'}->{'class'}, + 'arguments' => $arguments}; + if ($omit_def_space) { + $tree = $self->gdt('@tie{}-- {category} of {class}: {name}{arguments}', + $strings); + } else { + $tree = $self->gdt('@tie{}-- {category} of {class}: {name} {arguments}', + $strings); + } } else { - $tree = $self->gdt("\@tie{}-- {category} of {class}: {name}", { - 'category' => $root->{'extra'}->{'def_parsed_hash'}->{'category'}, - 'class' => $root->{'extra'}->{'def_parsed_hash'}->{'class'}, + $tree = $self->gdt('@tie{}-- {category} of {class}: {name}', { + 'category' => $element->{'extra'}->{'def_parsed_hash'}->{'category'}, + 'class' => $element->{'extra'}->{'def_parsed_hash'}->{'class'}, 'name' => $name}); } } elsif ($command eq 'defop' or ($command eq 'deftypeop' - and !$root->{'extra'}->{'def_parsed_hash'}->{'type'})) { + and !$element->{'extra'}->{'def_parsed_hash'}->{'type'})) { if ($arguments) { - $tree = $self->gdt("\@tie{}-- {category} on {class}: {name} {arguments}", { - 'category' => $root->{'extra'}->{'def_parsed_hash'}->{'category'}, + my $strings = { + 'category' => $element->{'extra'}->{'def_parsed_hash'}->{'category'}, 'name' => $name, - 'class' => $root->{'extra'}->{'def_parsed_hash'}->{'class'}, - 'arguments' => $arguments}); + 'class' => $element->{'extra'}->{'def_parsed_hash'}->{'class'}, + 'arguments' => $arguments}; + if ($omit_def_space) { + $tree = $self->gdt('@tie{}-- {category} on {class}: {name}{arguments}', + $strings); + } else { + $tree = $self->gdt('@tie{}-- {category} on {class}: {name} {arguments}', + $strings); + } } else { - $tree = $self->gdt("\@tie{}-- {category} on {class}: {name}", { - 'category' => $root->{'extra'}->{'def_parsed_hash'}->{'category'}, - 'class' => $root->{'extra'}->{'def_parsed_hash'}->{'class'}, + $tree = $self->gdt('@tie{}-- {category} on {class}: {name}', { + 'category' => $element->{'extra'}->{'def_parsed_hash'}->{'category'}, + 'class' => $element->{'extra'}->{'def_parsed_hash'}->{'class'}, 'name' => $name}); } } elsif ($command eq 'deftypeop') { if ($arguments) { my $strings = { - 'category' => $root->{'extra'}->{'def_parsed_hash'}->{'category'}, + 'category' => $element->{'extra'}->{'def_parsed_hash'}->{'category'}, 'name' => $name, - 'class' => $root->{'extra'}->{'def_parsed_hash'}->{'class'}, - 'type' => $root->{'extra'}->{'def_parsed_hash'}->{'type'}, + 'class' => $element->{'extra'}->{'def_parsed_hash'}->{'class'}, + 'type' => $element->{'extra'}->{'def_parsed_hash'}->{'type'}, 'arguments' => $arguments}; if ($self->get_conf('deftypefnnewline') eq 'on') { - $tree - = $self->gdt("\@tie{}-- {category} on {class}:\@*{type}\@*{name} {arguments}", - $strings); + if ($omit_def_space) { + $tree + = $self->gdt('@tie{}-- {category} on {class}:@*{type}@*{name}{arguments}', + $strings); + } else { + $tree + = $self->gdt('@tie{}-- {category} on {class}:@*{type}@*{name} {arguments}', + $strings); + } } else { - $tree - = $self->gdt("\@tie{}-- {category} on {class}: {type} {name} {arguments}", - $strings); + if ($omit_def_space) { + $tree + = $self->gdt('@tie{}-- {category} on {class}: {type} {name}{arguments}', + $strings); + } else { + $tree + = $self->gdt('@tie{}-- {category} on {class}: {type} {name} {arguments}', + $strings); + } } } else { my $strings = { - 'category' => $root->{'extra'}->{'def_parsed_hash'}->{'category'}, - 'type' => $root->{'extra'}->{'def_parsed_hash'}->{'type'}, - 'class' => $root->{'extra'}->{'def_parsed_hash'}->{'class'}, + 'category' => $element->{'extra'}->{'def_parsed_hash'}->{'category'}, + 'type' => $element->{'extra'}->{'def_parsed_hash'}->{'type'}, + 'class' => $element->{'extra'}->{'def_parsed_hash'}->{'class'}, 'name' => $name}; if ($self->get_conf('deftypefnnewline') eq 'on') { - $tree - = $self->gdt("\@tie{}-- {category} on {class}:\@*{type}\@*{name}", + $tree + = $self->gdt('@tie{}-- {category} on {class}:@*{type}@*{name}', $strings); } else { - $tree - = $self->gdt("\@tie{}-- {category} on {class}: {type} {name}", + $tree + = $self->gdt('@tie{}-- {category} on {class}: {type} {name}', $strings); } } } elsif ($command eq 'deftypecv') { if ($arguments) { my $strings = { - 'category' => $root->{'extra'}->{'def_parsed_hash'}->{'category'}, + 'category' => $element->{'extra'}->{'def_parsed_hash'}->{'category'}, 'name' => $name, - 'class' => $root->{'extra'}->{'def_parsed_hash'}->{'class'}, - 'type' => $root->{'extra'}->{'def_parsed_hash'}->{'type'}, + 'class' => $element->{'extra'}->{'def_parsed_hash'}->{'class'}, + 'type' => $element->{'extra'}->{'def_parsed_hash'}->{'type'}, 'arguments' => $arguments}; - if ($self->get_conf('deftypefnnewline') eq 'on') { - $tree - = $self->gdt("\@tie{}-- {category} of {class}:\@*{type}\@*{name} {arguments}", + if ($omit_def_space) { + $tree + = $self->gdt('@tie{}-- {category} of {class}: {type} {name}{arguments}', $strings); } else { - $tree - = $self->gdt("\@tie{}-- {category} of {class}: {type} {name} {arguments}", + $tree + = $self->gdt('@tie{}-- {category} of {class}: {type} {name} {arguments}', $strings); } } else { my $strings = { - 'category' => $root->{'extra'}->{'def_parsed_hash'}->{'category'}, - 'type' => $root->{'extra'}->{'def_parsed_hash'}->{'type'}, - 'class' => $root->{'extra'}->{'def_parsed_hash'}->{'class'}, + 'category' => $element->{'extra'}->{'def_parsed_hash'}->{'category'}, + 'type' => $element->{'extra'}->{'def_parsed_hash'}->{'type'}, + 'class' => $element->{'extra'}->{'def_parsed_hash'}->{'class'}, 'name' => $name}; - if ($self->get_conf('deftypefnnewline') eq 'on') { - $tree - = $self->gdt("\@tie{}-- {category} of {class}:\@*{type}\@*{name}", - $strings); - } else { - $tree - = $self->gdt("\@tie{}-- {category} of {class}: {type} {name}", + $tree + = $self->gdt('@tie{}-- {category} of {class}: {type} {name}', $strings); - } } } my $def_paragraph = $self->new_formatter('paragraph', - { 'indent_length' => ($self->{'format_context'}->[-1]->{'indent_level'} -1) *$indent_length, - 'indent_length_next' => (1+$self->{'format_context'}->[-1]->{'indent_level'})*$indent_length, + { 'indent_length' => ($self->{'format_context'}->[-1]->{'indent_level'} -1) + * $indent_length, + 'indent_length_next' => (1+$self->{'format_context'}->[-1]->{'indent_level'}) + * $indent_length, 'suppress_styles' => 1 }); push @{$self->{'formatters'}}, $def_paragraph; + # FIXME the whole line is formatted in code here. In other formats, + # the category is normal text $result .= _convert($self, {'type' => '_code', 'contents' => [$tree]}); $result .= _count_added($self, $def_paragraph->{'container'}, Texinfo::Convert::Paragraph::end($def_paragraph->{'container'})); @@ -2967,12 +3142,13 @@ sub _convert($$) delete $self->{'text_element_context'}->[-1]->{'counter'}; $self->{'empty_lines_count'} = 0; } - } elsif ($root->{'type'} eq 'menu_entry') { + } elsif ($element->{'type'} eq 'menu_entry') { my $entry_name_seen = 0; - foreach my $arg (@{$root->{'args'}}) { + foreach my $arg (@{$element->{'args'}}) { my ($pre_quote, $post_quote); if ($arg->{'type'} eq 'menu_entry_node') { $self->{'formatters'}->[-1]->{'suppress_styles'} = 1; + $self->{'formatters'}->[-1]->{'no_added_eol'} = 1; # Flush a leading space $result .= _count_added($self, $formatter->{'container'}, @@ -2984,33 +3160,36 @@ sub _convert($$) $node_text .= _count_added($self, $formatter->{'container'}, add_pending_word($formatter->{'container'}, 1)); delete $self->{'formatters'}->[-1]->{'suppress_styles'}; + delete $self->{'formatters'}->[-1]->{'no_added_eol'}; $pre_quote = $post_quote = ''; if ($entry_name_seen) { if ($node_text =~ /([,\t]|\.\s)/) { - if ($self->get_conf('INFO_SPECIAL_CHARS_WARNING')) { - $self->line_warn(sprintf(__( + if ($self->{'info_special_chars_warning'}) { + $self->converter_line_warn($self, sprintf(__( "menu entry node name should not contain `%s'"), $1), - $root->{'line_nr'}); + $element->{'source_info'}); + } + if ($self->{'info_special_chars_quote'}) { + $pre_quote = $post_quote = "\x{7f}"; } - } - if ($self->get_conf('INFO_SPECIAL_CHARS_QUOTE')) { - $pre_quote = $post_quote = "\x{7f}"; } } else { if ($node_text =~ /:/) { - if ($self->get_conf('INFO_SPECIAL_CHARS_WARNING')) { - $self->line_warn(__( + if ($self->{'info_special_chars_warning'}) { + $self->converter_line_warn($self, __( "menu entry node name should not contain `:'"), - $root->{'line_nr'}); + $element->{'source_info'}); } - if ($self->get_conf('INFO_SPECIAL_CHARS_QUOTE')) { + if ($self->{'info_special_chars_quote'}) { $pre_quote = $post_quote = "\x{7f}"; } } } $result .= $pre_quote . $node_text . $post_quote; } elsif ($arg->{'type'} eq 'menu_entry_name') { + $self->{'formatters'}->[-1]->{'no_added_eol'} = 1; my $entry_name = _convert($self, $arg); + delete $self->{'formatters'}->[-1]->{'no_added_eol'}; my $formatter = $self->{'formatters'}->[-1]; $entry_name .= _count_added($self, $formatter->{'container'}, @@ -3018,12 +3197,12 @@ sub _convert($$) $entry_name_seen = 1; $pre_quote = $post_quote = ''; if ($entry_name =~ /:/) { - if ($self->get_conf('INFO_SPECIAL_CHARS_WARNING')) { - $self->line_warn(__( + if ($self->{'info_special_chars_warning'}) { + $self->converter_line_warn($self, __( "menu entry name should not contain `:'"), - $root->{'line_nr'}); + $element->{'source_info'}); } - if ($self->get_conf('INFO_SPECIAL_CHARS_QUOTE')) { + if ($self->{'info_special_chars_quote'}) { $pre_quote = $post_quote = "\x{7f}"; } } @@ -3037,8 +3216,8 @@ sub _convert($$) # have been processed yet, and we need to output any pending spaces # before 'end_line' throws them away. The argument to 'add_pending_word' # does this. - if ($root->{'parent'}->{'type'} - and $root->{'parent'}->{'type'} eq 'preformatted') { + if ($element->{'parent'}->{'type'} + and $element->{'parent'}->{'type'} eq 'preformatted') { $result .= _count_added($self, $formatter->{'container'}, add_pending_word($formatter->{'container'}, 1)); @@ -3050,28 +3229,22 @@ sub _convert($$) $result = $self->ensure_end_of_line($result) ; } - } elsif ($root->{'type'} eq 'frenchspacing') { - push @{$formatter->{'frenchspacing_stack'}}, 'on'; - set_space_protection($formatter->{'container'}, undef, undef, undef, 1); - } elsif ($root->{'type'} eq '_code') { - if (!$formatter->{'font_type_stack'}->[-1]->{'monospace'}) { - push @{$formatter->{'font_type_stack'}}, {'monospace' => 1}; - } else { - $formatter->{'font_type_stack'}->[-1]->{'monospace'}++; - } + } elsif ($element->{'type'} eq 'frenchspacing') { push @{$formatter->{'frenchspacing_stack'}}, 'on'; set_space_protection($formatter->{'container'}, undef, undef, undef, 1); - } elsif ($root->{'type'} eq 'bracketed') { + } elsif ($element->{'type'} eq '_code') { + _open_code($formatter); + } elsif ($element->{'type'} eq 'bracketed') { $result .= _count_added($self, $formatter->{'container'}, add_text($formatter->{'container'}, '{')); } } # The processing of contents is done here. - if ($root->{'contents'}) { - my @contents = @{$root->{'contents'}}; + if ($element->{'contents'}) { + my @contents = @{$element->{'contents'}}; push @{$self->{'current_contents'}}, \@contents; - push @{$self->{'current_roots'}}, $root; + push @{$self->{'current_roots'}}, $element; while (@contents) { my $content = shift @contents; my $text = _convert($self, $content); @@ -3084,26 +3257,19 @@ sub _convert($$) } # now closing. First, close types. - if ($root->{'type'}) { - if ($root->{'type'} eq 'frenchspacing') { + if ($element->{'type'}) { + if ($element->{'type'} eq 'frenchspacing') { pop @{$formatter->{'frenchspacing_stack'}}; my $frenchspacing = 0; $frenchspacing = 1 if ($formatter->{'frenchspacing_stack'}->[-1] eq 'on'); set_space_protection($formatter->{'container'}, undef, undef, undef, $frenchspacing); - } elsif ($root->{'type'} eq '_code') { - $formatter->{'font_type_stack'}->[-1]->{'monospace'}--; - pop @{$formatter->{'font_type_stack'}} - if !$formatter->{'font_type_stack'}->[-1]->{'monospace'}; - pop @{$formatter->{'frenchspacing_stack'}}; - my $frenchspacing = 0; - $frenchspacing = 1 if ($formatter->{'frenchspacing_stack'}->[-1] eq 'on'); - set_space_protection($formatter->{'container'}, undef, - undef, undef, $frenchspacing); - } elsif ($root->{'type'} eq 'bracketed') { + } elsif ($element->{'type'} eq '_code') { + _close_code($formatter); + } elsif ($element->{'type'} eq 'bracketed') { $result .= _count_added($self, $formatter->{'container'}, add_text($formatter->{'container'}, '}')); - } elsif ($root->{'type'} eq 'row') { + } elsif ($element->{'type'} eq 'row') { my @cell_beginnings; my @cell_lines; my $cell_beginning = 0; @@ -3122,11 +3288,13 @@ sub _convert($$) } $cell_idx = 0; - my $cell_updated_locations; + my $cell_updated_locations = []; my @row_locations; foreach my $cell_locations (@{$self->{'format_context'}->[-1]->{'row_counts'}}) { foreach my $location (@{$cell_locations->{'locations'}}) { next unless (defined($location->{'bytes'}) and defined($location->{'lines'})); + $cell_updated_locations->[$cell_idx] = {} + if (!$cell_updated_locations->[$cell_idx]); push @{$cell_updated_locations->[$cell_idx]->{$location->{'lines'}}}, $location; $max_lines = $location->{'lines'}+1 @@ -3148,7 +3316,8 @@ sub _convert($$) my $last_cell = 0; for (my $cell_idx = 0; $cell_idx < $max_cell; $cell_idx++) { $last_cell = $cell_idx+1 if (defined($cell_lines[$cell_idx]->[$line_idx]) - or defined($cell_updated_locations->[$cell_idx]->{$line_idx})); + or ($cell_updated_locations->[$cell_idx] + and defined($cell_updated_locations->[$cell_idx]->{$line_idx}))); } for (my $cell_idx = 0; $cell_idx < $last_cell; $cell_idx++) { @@ -3163,7 +3332,8 @@ sub _convert($$) $bytes_count += count_bytes($self, $cell_text); $line_width += Texinfo::Convert::Unicode::string_width($cell_text); } - if (defined($cell_updated_locations->[$cell_idx]->{$line_idx})) { + if ($cell_updated_locations->[$cell_idx] + and defined($cell_updated_locations->[$cell_idx]->{$line_idx})) { foreach my $location (@{$cell_updated_locations->[$cell_idx]->{$line_idx}}) { $location->{'bytes'} = $bytes_count; } @@ -3209,7 +3379,7 @@ sub _convert($$) $self->{'format_context'}->[-1]->{'row_counts'} = []; $self->{'format_context'}->[-1]->{'row_empty_lines_count'} = $self->{'empty_lines_count'}; - } elsif ($root->{'type'} eq 'text_root') { + } elsif ($element->{'type'} eq 'before_node_section') { $self->{'text_before_first_node'} = $result; } } @@ -3242,19 +3412,21 @@ sub _convert($$) # close commands if ($command) { if ($command eq 'float') { - if ($root->{'extra'} - and ($root->{'extra'}->{'type'}->{'normalized'} ne '' - or defined($root->{'number'}) - or $root->{'extra'}->{'caption'} or $root->{'extra'}->{'shortcaption'})) { + if ($element->{'extra'} + and (($element->{'extra'}->{'type'} + and $element->{'extra'}->{'type'}->{'normalized'} ne '') + or ($element->{'structure'} + and defined($element->{'structure'}->{'float_number'})) + or $element->{'extra'}->{'caption'} or $element->{'extra'}->{'shortcaption'})) { $result .= _add_newline_if_needed($self); - my ($caption, $prepended) = Texinfo::Common::float_name_caption($self, - $root); + my ($caption, $prepended) + = Texinfo::Convert::Converter::float_name_caption($self, $element); if ($prepended) { $prepended->{'type'} = 'frenchspacing'; my $float_number = $self->convert_line ($prepended); $result .= $float_number; - $self->{'text_element_context'}->[-1]->{'counter'} += + $self->{'text_element_context'}->[-1]->{'counter'} += Texinfo::Convert::Unicode::string_width($float_number); $self->{'empty_lines_count'} = 0; } @@ -3266,8 +3438,8 @@ sub _convert($$) } } elsif (($command eq 'quotation' or $command eq 'smallquotation') - and $root->{'extra'} and $root->{'extra'}->{'authors'}) { - foreach my $author (@{$root->{'extra'}->{'authors'}}) { + and $element->{'extra'} and $element->{'extra'}->{'authors'}) { + foreach my $author (@{$element->{'extra'}->{'authors'}}) { $result .= _convert($self, $self->gdt("\@center --- \@emph{{author}}\n", {'author' => $author->{'args'}->[0]->{'contents'}})); @@ -3275,10 +3447,10 @@ sub _convert($$) } elsif (($command eq 'multitable')) { $self->{'document_context'}->[-1]->{'in_multitable'}--; } elsif ($root_commands{$command} - and $sectioning_commands{$command} + and $sectioning_heading_commands{$command} and $command ne 'part') { # add menu if missing - my $node = $self->{'node'}; + my $node = $self->{'current_node'}; my $automatic_directions; if ($node and defined($node->{'extra'}->{'nodes_manuals'})) { $automatic_directions = @@ -3287,7 +3459,7 @@ sub _convert($$) if ($node and $automatic_directions and !$self->{'seenmenus'}->{$node}) { $self->{'seenmenus'}->{$node} = 1; - my $menu_node = Texinfo::Structuring::new_complete_node_menu(undef, $node); + my $menu_node = Texinfo::Structuring::new_complete_node_menu($node); if ($menu_node) { my $menu_text = $self->_convert($menu_node); if ($menu_text) { @@ -3322,7 +3494,7 @@ sub _convert($$) pop @{$self->{'format_context'}}; pop @{$self->{'text_element_context'}}; push @{$self->{'format_context'}->[-1]->{'row'}}, $result; - _update_count_context($self); + update_count_context($self); my $cell_counts = pop @{$self->{'count_context'}}; push @{$self->{'format_context'}->[-1]->{'row_counts'}}, $cell_counts; $result = ''; @@ -3338,7 +3510,6 @@ sub _convert($$) 1; __END__ -# $Id$ # Automatically generated from maintain/template.pod =head1 NAME @@ -3347,13 +3518,18 @@ Texinfo::Convert::Plaintext - Convert Texinfo tree to Plaintext =head1 SYNOPSIS - my $converter + my $converter = Texinfo::Convert::Plaintext->converter({'parser' => $parser}); $converter->output($tree); $converter->convert($tree); $converter->convert_tree($tree); +=head1 NOTES + +The Texinfo Perl module main purpose is to be used in C to convert +Texinfo to other formats. There is no promise of API stability. + =head1 DESCRIPTION Texinfo::Convert::Plaintext converts a Texinfo tree to Plaintext. @@ -3364,15 +3540,18 @@ Texinfo::Convert::Plaintext converts a Texinfo tree to Plaintext. =item $converter = Texinfo::Convert::Plaintext->converter($options) -Initialize converter from Texinfo to Plaintext. +Initialize converter from Texinfo to Plaintext. The I<$options> hash reference holds options for the converter. In -this option hash reference a parser object may be associated with the -I key. The other options should be configuration options -described in the Texinfo manual. Those options, when appropriate, -override the document content. +this option hash reference a L +may be associated with the I key. The other options +are Texinfo customization options and a few other options that can +be passed to the converter. Most of the customization options are described in +the Texinfo manual. Those customization options, when appropriate, override +the document content. The parser should not be available directly anymore +after getting the associated information. -See L for more informations. +See L for more information. =item $converter->output($tree) @@ -3381,12 +3560,11 @@ described in the Texinfo manual. =item $result = $converter->convert($tree) -Convert a Texinfo tree I<$tree> or tree portion and return -the resulting output. +Convert a Texinfo tree I<$tree> and return the resulting output. =item $result = $converter->convert_tree($tree) -Convert a Texinfo tree portion I<$tree> and return the resulting +Convert a Texinfo tree portion I<$tree> and return the resulting output. This function does not try to output a full document but only portions. For a full document use C. @@ -3396,4 +3574,14 @@ portions. For a full document use C. Patrice Dumas, Epertusus@free.frE +=head1 COPYRIGHT AND LICENSE + +Copyright 2010- Free Software Foundation, Inc. See the source file for +all copyright years. + +This library is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3 of the License, or (at +your option) any later version. + =cut -- cgit v1.2.3