| # TexinfoMarkup.pm: output tree keeping Texinfo code information |
| # |
| # Copyright 2011-2026 Free Software Foundation, Inc. |
| # |
| # This program is free software; you can redistribute it and/or modify |
| # it under the terms of the GNU General Public License as published by |
| # the Free Software Foundation; either version 3 of the License, |
| # or (at your option) any later version. |
| # |
| # This program is distributed in the hope that it will be useful, |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| # GNU General Public License for more details. |
| # |
| # You should have received a copy of the GNU General Public License |
| # along with this program. If not, see <https://www.gnu.org/licenses/>. |
| # |
| # Original author: Patrice Dumas <pertusus@free.fr> |
| # |
| # |
| # This is an abstract class, that calls markup language formatting |
| # functions, but does not implement them. The type of markup corresponds |
| # to nested elements with attributes, similar to XML. Initially setup |
| # for XML, but more generic. |
| # |
| # Markup language formatting functions should be implemented in modules that |
| # inherit from this module. The functions to implement are documented in |
| # the POD section. |
| |
| package Texinfo::Example::TexinfoMarkup; |
| |
| use 5.006; |
| use strict; |
| |
| # have :alpha: match only ASCII |
| use if $] >= 5.014, re => '/a'; |
| |
| # To check if there is no erroneous autovivification |
| #no autovivification qw(fetch delete exists store strict); |
| |
| use Texinfo::Commands; |
| |
| use Texinfo::TreeElement; |
| |
| use Texinfo::Common; |
| use Texinfo::Indices; |
| |
| # for section_level_adjusted_command_name |
| use Texinfo::Structuring; |
| use Texinfo::Convert::Converter; |
| use Texinfo::Convert::Unicode; |
| # for debugging and adding the original line for some commands |
| use Texinfo::Convert::Texinfo; |
| #use Data::Dumper; |
| use Carp qw(cluck); |
| |
| our @ISA = qw(Texinfo::Convert::Converter); |
| |
| our $VERSION = '7.3dev'; |
| |
| |
| # our because it is used in the xml to texi translator and subclasses. |
| our %no_arg_commands_formatting = ( |
| '*' => 'linebreak', |
| ' ' => ['spacecmd', ['type', 'spc']], |
| "\t" => ['spacecmd', ['type', 'tab']], |
| "\n" => ['spacecmd', ['type', 'nl']], |
| '-' => 'hyphenbreak', # hyphenation hint |
| '|' => ['divideheading'], # used in formatting commands |
| # @evenfooting and friends |
| '/' => 'slashbreak', |
| ':' => 'noeos', |
| '!' => 'eosexcl', |
| '?' => 'eosquest', |
| '.' => 'eosperiod', |
| '@' => 'arobase', |
| '{' => 'lbrace', |
| '}' => 'rbrace', |
| '&' => 'ampsymbol', # avoid amp as it is already the entity for |
| # & in text in XML |
| '\\' => 'backslash', # should only appear in math |
| |
| 'TeX' => 'tex', |
| 'LaTeX' => 'latex', |
| 'bullet' => 'bullet', |
| 'copyright' => 'copyright', |
| 'registeredsymbol' => 'registered', |
| 'dots' => 'dots', |
| 'enddots' => 'enddots', |
| 'error' => 'errorglyph', |
| 'expansion' => 'expansion', |
| 'arrow' => 'rarr', |
| 'click' => 'click', |
| 'minus' => 'minus', |
| 'point' => 'point', |
| 'print' => 'printglyph', |
| 'result' => 'result', |
| 'l' => 'lslash', |
| 'L' => 'Lslash', |
| 'today' => ['today'], |
| 'comma' => 'comma', |
| 'atchar' => 'atchar', |
| 'ampchar' => 'ampchar', |
| 'lbracechar' => 'lbracechar', |
| 'rbracechar' => 'rbracechar', |
| 'backslashchar' => 'backslashchar', |
| 'hashchar' => 'hashchar', |
| # in Texinfo::Convert::Converter::default_xml_no_arg_commands_formatting, |
| # guillemotleft and guillemotright are mapped to laquo and raquo, |
| # but guillemetleft and guillemetright are also mapped to those |
| # entities. To make sure that it is possible to go back to the |
| # @-command from the entity, we use specific entities here |
| 'guillemotleft' => 'guillemotleft', |
| 'guillemotright' => 'guillemotright', |
| ); |
| |
| my %brace_commands = %Texinfo::Commands::brace_commands; |
| |
| # use default XML formatting to complete the hash, removing XML |
| # specific formatting. This avoids some code duplication. |
| my %default_xml_no_arg_commands_formatting = |
| %Texinfo::Convert::Converter::xml_text_entity_no_arg_commands_formatting; |
| |
| foreach my $command (keys(%default_xml_no_arg_commands_formatting)) { |
| if (!exists($no_arg_commands_formatting{$command})) { |
| if ($default_xml_no_arg_commands_formatting{$command} ne '') { |
| if ($default_xml_no_arg_commands_formatting{$command} =~ /^&(.*);$/) { |
| $no_arg_commands_formatting{$command} = $1; |
| } else { |
| die "BUG: Strange xml_no_arg_commands_formatting: $default_xml_no_arg_commands_formatting{$command}\n"; |
| } |
| } else { |
| $no_arg_commands_formatting{$command} = ''; |
| } |
| } |
| } |
| |
| # following is not format specific. Some infos are taken from generic |
| # XML, but XML specific formatting is stripped. |
| |
| # our because it is used in the xml to texi translator |
| our %accent_types = %Texinfo::Convert::Converter::xml_accent_entities; |
| |
| # do not treat @dotless as an accent command but |
| # together with brace commands. |
| delete $accent_types{'dotless'}; |
| |
| # no entity |
| my @other_accents = ('tieaccent', 'ubaraccent', 'udotaccent'); |
| foreach my $accent (@other_accents) { |
| $accent_types{$accent} = $accent; |
| } |
| |
| my %line_command_line_attributes = ( |
| 'setfilename' => 'file', |
| 'documentencoding' => 'encoding', |
| 'verbatiminclude' => 'file', |
| 'documentlanguage' => 'lang', |
| ); |
| |
| my %line_command_numbered_arguments_attributes = ( |
| 'definfoenclose' => [ 'command', 'open', 'close' ], |
| 'alias' => [ 'new', 'existing' ], |
| 'syncodeindex' => [ 'from', 'to' ], |
| 'synindex' => [ 'from', 'to' ], |
| ); |
| |
| my %nobrace_commands = %Texinfo::Commands::nobrace_commands; |
| my %line_commands = %Texinfo::Commands::line_commands; |
| |
| foreach my $command ('item', 'headitem', 'tab') { |
| delete $nobrace_commands{$command}; |
| } |
| |
| foreach my $command ('item', 'itemx') { |
| delete $line_commands{$command}; |
| } |
| |
| my %default_args_code_style |
| = %Texinfo::Convert::Converter::default_args_code_style; |
| |
| # our because it is used in the xml to texi translator |
| our %commands_args_elements = ( |
| 'namedanchor' => ['anchoridentifier', 'anchorname'], |
| 'email' => ['emailaddress', 'emailname'], |
| 'uref' => ['urefurl', 'urefdesc', 'urefreplacement'], |
| 'url' => ['urefurl', 'urefdesc', 'urefreplacement'], |
| 'link' => ['linknodename', 'linkrefname', 'linkinfofile'], |
| 'inforef' => ['inforefnodename', 'inforefrefname', 'inforefinfoname'], |
| 'image' => ['imagefile', 'imagewidth', 'imageheight', |
| 'alttext', 'imageextension'], |
| # * means that the previous element is variadic, ie can appear indefinitely |
| 'example' => ['examplelanguage', 'examplearg', '*'], |
| 'quotation' => ['quotationtype'], |
| 'cartouche' => ['cartouchetitle'], |
| 'float' => ['floattype', 'floatname'], |
| 'itemize' => ['itemprepend'], |
| 'enumerate' => ['enumeratefirst'], |
| ); |
| |
| foreach my $ref_cmd ('pxref', 'xref', 'ref') { |
| $commands_args_elements{$ref_cmd} |
| = ['xrefnodename', 'xrefinfoname', 'xrefprinteddesc', 'xrefinfofile', |
| 'xrefprintedname']; |
| } |
| |
| foreach my $explained_command (keys(%Texinfo::Commands::explained_commands)) { |
| $commands_args_elements{$explained_command} = ["${explained_command}word", |
| "${explained_command}desc"]; |
| } |
| |
| foreach my $brace_command (keys(%Texinfo::Commands::brace_commands)) { |
| if ($Texinfo::Commands::brace_commands{$brace_command} eq 'inline') { |
| if ($brace_command eq 'inlinefmtifelse') { |
| $commands_args_elements{$brace_command} = ["${brace_command}format", |
| "${brace_command}contentif", "${brace_command}contentelse"]; |
| |
| } else { |
| $commands_args_elements{$brace_command} = ["${brace_command}format", |
| "${brace_command}content"]; |
| } |
| } |
| } |
| |
| my %defcommand_name_type = ( |
| 'defline' => 'symbol', |
| 'deftypeline' => 'symbol', |
| 'deffn' => 'function', |
| 'defvr' => 'variable', |
| 'deftypefn' => 'function', |
| 'deftypeop' => 'operation', |
| 'deftypevr' => 'variable', |
| 'defcv' => 'classvar', |
| 'deftypecv' => 'classvar', |
| 'defop' => 'operation', |
| 'deftp' => 'datatype', |
| ); |
| |
| my %type_elements = ( |
| 'paragraph' => 'para', |
| 'preformatted' => 'pre', |
| 'menu_entry' => 'menuentry', |
| 'menu_entry_leading_text' => 'menuleadingtext', |
| 'menu_entry_node' => 'menunode', |
| 'menu_entry_separator' => 'menuseparator', |
| 'menu_comment' => 'menucomment', |
| 'menu_entry_description' => 'menudescription', |
| 'menu_entry_name' => 'menutitle', |
| 'postamble_after_end' => 'postambleafterend', |
| 'preamble_before_beginning' => 'preamblebeforebeginning', |
| 'table_definition' => 'tableitem', |
| 'table_entry' => 'tableentry', |
| 'table_term' => 'tableterm', |
| 'row' => 'row', |
| 'multitable_head' => 'thead', |
| 'multitable_body' => 'tbody', |
| 'def_item' => 'definitionitem', |
| 'before_item' => 'beforefirstitem', |
| 'before_defline' => 'beforefirstdefline', |
| ); |
| |
| # Empty containers do not happen often, mainly when a source mark |
| # needs to be kept. However, it is more robust to remove explictely |
| # empty containers that we want to remove instead of relying on a |
| # specific tree. |
| my %container_ignored_if_empty = ( |
| 'preformatted' => 1, |
| 'menu_comment' => 1, |
| ); |
| |
| my %default_context_block_commands = ( |
| 'float' => 1, |
| ); |
| |
| # converter_defaults() should be implemented by subclasses. |
| sub converter_defaults($;$) { |
| return undef; |
| } |
| |
| sub converter_initialize($) { |
| my $self = shift; |
| |
| $self->{'context_block_commands'} = {%default_context_block_commands}; |
| foreach my $raw (grep {$Texinfo::Commands::block_commands{$_} eq 'format_raw'} |
| keys(%Texinfo::Commands::block_commands)) { |
| $self->{'context_block_commands'}->{$raw} = 1 |
| if $self->{'expanded_formats'}->{$raw}; |
| } |
| } |
| |
| sub conversion_initialization($;$) { |
| my ($self, $document) = @_; |
| |
| $self->set_document($document); |
| |
| $self->{'document_context'} = [{'monospace' => [0]}]; |
| } |
| |
| sub conversion_output_begin($;$$) { |
| my ($self, $output_file, $output_filename) = @_; |
| |
| my $result = $self->txi_markup_header(); |
| $result .= $self->txi_markup_open_element('texinfo')."\n"; |
| if ($output_file ne '') { |
| my $filename_element = $self->txi_markup_open_element('filename', |
| [['file', $output_filename]]) |
| .$self->txi_markup_close_element('filename')."\n"; |
| $result .= $filename_element; |
| } |
| return $result; |
| } |
| |
| sub conversion_output_end($) { |
| my $self = shift; |
| |
| return $self->txi_markup_close_element('texinfo')."\n"; |
| } |
| |
| # Main output function for the Texinfo language markup output files. |
| sub output($$) { |
| my ($self, $document) = @_; |
| |
| return $self->output_tree($document); |
| } |
| |
| # API for markup formatting subclasses |
| sub in_monospace($) { |
| my $self = shift; |
| |
| return $self->{'document_context'}->[-1]->{'monospace'}->[-1]; |
| } |
| # end of API |
| |
| sub _format_command($$) { |
| my ($self, $command) = @_; |
| |
| if (ref($no_arg_commands_formatting{$command}) eq '') { |
| return $self->txi_markup_atom($command); |
| } else { |
| my @spec = @{$no_arg_commands_formatting{$command}}; |
| my $format_element = shift @spec; |
| return $self->txi_markup_element($format_element, \@spec); |
| } |
| } |
| |
| sub _index_entry($$) { |
| my ($self, $element) = @_; |
| |
| if ($element->{'extra'} and $element->{'extra'}->{'index_entry'}) { |
| my $indices_information; |
| if ($self->{'document'}) { |
| $indices_information = $self->{'document'}->indices_information(); |
| } |
| |
| my ($index_entry, $index_info) |
| = Texinfo::Common::lookup_index_entry($element->{'extra'}->{'index_entry'}, |
| $indices_information); |
| my $attribute = [['index', $index_entry->{'index_name'}]]; |
| push @$attribute, ['number', $index_entry->{'entry_number'}] |
| if (defined($index_entry->{'entry_number'})); |
| # in case the index is not a default index, or the style of the |
| # entry (in code or not) is not the default for this index |
| my $entry_index_name = $index_entry->{'index_name'}; |
| my $in_code = $index_info->{'in_code'}; |
| if (!$Texinfo::Commands::index_names{$entry_index_name} |
| or $in_code != $Texinfo::Commands::index_names{$entry_index_name}->{'in_code'}) { |
| push @$attribute, ['incode', $in_code]; |
| } |
| if ($index_info->{'merged_in'}) { |
| push @$attribute, ['mergedindex', $index_info->{'merged_in'}]; |
| } |
| my $result = $self->txi_markup_open_element('indexterm', $attribute); |
| push @{$self->{'document_context'}}, {'monospace' => [0]}; |
| $self->{'document_context'}->[-1]->{'monospace'}->[-1] = 1 |
| if ($in_code); |
| my $index_element = Texinfo::Common::non_leading_trailing_tree( |
| Texinfo::Indices::converter_index_content_element($element, $self)); |
| $result .= $self->_convert($index_element); |
| pop @{$self->{'document_context'}}; |
| $result .= $self->txi_markup_close_element('indexterm'); |
| return $result; |
| } |
| return ''; |
| } |
| |
| sub _infoenclose_attribute($$) { |
| my ($self, $element) = @_; |
| |
| my @attribute = (); |
| return @attribute if (!$element->{'extra'}); |
| push @attribute, ['begin', $element->{'extra'}->{'begin'}] |
| if (defined($element->{'extra'}->{'begin'})); |
| push @attribute, ['end', $element->{'extra'}->{'end'}] |
| if (defined($element->{'extra'}->{'end'})); |
| return @attribute; |
| } |
| |
| sub _accent($$$;$$$$) { |
| my ($self, $text, $element, $index_in_stack, $accents_stack, |
| $in_upper_case, $attributes) = @_; |
| |
| $attributes = [] if (!defined($attributes)); |
| |
| unshift @$attributes, ['type', $accent_types{$element->{'cmdname'}}]; |
| my $result = $self->txi_markup_open_element('accent', $attributes); |
| $result .= $text; |
| $result .= $self->txi_markup_close_element('accent'); |
| return $result; |
| } |
| |
| sub convert($$) { |
| my ($self, $document) = @_; |
| |
| $self->conversion_initialization($document); |
| |
| my $root = $document->tree(); |
| |
| return $self->convert_tree($root); |
| } |
| |
| sub convert_tree($$) { |
| my ($self, $root) = @_; |
| |
| return $self->_convert($root); |
| } |
| |
| sub _arg_leading_spaces($$) { |
| my ($self, $element) = @_; |
| |
| my $result = ''; |
| if (exists($element->{'contents'})) { |
| my $arg = $element->{'contents'}->[0]; |
| if (exists($arg->{'contents'})) { |
| foreach my $content (@{$arg->{'contents'}}) { |
| if (exists($content->{'type'}) |
| and $content->{'type'} eq 'spaces_before_argument') { |
| $result .= $self->txi_markup_protect_text($content->{'text'}); |
| } else { |
| last; |
| } |
| } |
| } |
| } |
| return $result; |
| } |
| |
| sub _format_comment($$) { |
| my ($self, $element) = @_; |
| |
| my $command_text = ''; |
| if ($element->{'contents'}) { |
| my $line_arg = $element->{'contents'}->[0]; |
| if (exists($line_arg->{'contents'})) { |
| foreach my $content (@{$line_arg->{'contents'}}) { |
| $command_text .= $content->{'text'}; |
| } |
| } |
| } |
| return $self->txi_markup_comment(" $element->{'cmdname'}" .$command_text); |
| } |
| |
| sub _format_comment_or_end_line($$) { |
| my ($self, $element) = @_; |
| |
| my ($comment, $end_line) |
| = $self->comment_or_end_line($element); |
| |
| if ($comment) { |
| return _format_comment($self, $comment); |
| } else { |
| return $end_line; |
| } |
| } |
| |
| sub _end_space_and_format_comment_or_end_line($$) { |
| my ($self, $element) = @_; |
| |
| my ($end_spaces, $end_line, $comment) |
| = $self->comment_end_line_end_space($element); |
| |
| $end_spaces = $self->txi_markup_protect_text($end_spaces); |
| |
| if ($comment) { |
| return $end_spaces, _format_comment($self, $comment); |
| } else { |
| return $end_spaces, $end_line; |
| } |
| } |
| |
| |
| sub _texinfo_line($$) { |
| my ($self, $element) = @_; |
| |
| my $line |
| = Texinfo::Convert::Texinfo::convert_to_texinfo( |
| $element->{'contents'}->[-1]); |
| chomp($line); |
| if ($line ne '') { |
| return ['line', $line]; |
| } |
| return (); |
| } |
| |
| sub _format_columnfractions($$) { |
| my ($self, $element) = @_; |
| |
| my $attribute = [$self->_texinfo_line($element)]; |
| my $result = ''; |
| $result .= $self->txi_markup_open_element('columnfractions', $attribute); |
| if ($element |
| and $element->{'extra'} |
| and $element->{'extra'}->{'misc_args'}) { |
| foreach my $fraction (@{$element->{'extra'}->{'misc_args'}}) { |
| $result .= $self->txi_markup_open_element('columnfraction', |
| [['value', $fraction]]) |
| .$self->txi_markup_close_element('columnfraction'); |
| } |
| } |
| $result .= $self->txi_markup_close_element('columnfractions'); |
| $result .= _format_comment_or_end_line($self, $element); |
| return $result; |
| } |
| |
| sub _convert_def_line($$) { |
| my ($self, $element) = @_; |
| |
| my $result = ''; |
| |
| if ($element->{'cmdname'}) { |
| # @def*x command has the command associated with def_line. |
| $result .= $self->txi_markup_open_element($element->{'cmdname'}); |
| } |
| $result .= $self->txi_markup_open_element('definitionterm'); |
| $result .= $self->_index_entry($element); |
| push @{$self->{'document_context'}->[-1]->{'monospace'}}, 1; |
| my $def_command = $element->{'extra'}->{'def_command'}; |
| if ($element->{'contents'} |
| and $element->{'contents'}->[0]->{'contents'}) { |
| my $main_command; |
| my $alias; |
| if ($Texinfo::Common::def_aliases{$def_command}) { |
| $main_command |
| = $Texinfo::Common::def_aliases{$def_command}; |
| $alias = 1; |
| } else { |
| $main_command = $def_command; |
| $alias = 0; |
| } |
| foreach my $arg (@{$element->{'contents'}->[0]->{'contents'}}) { |
| my $type = $arg->{'type'}; |
| # should only happen for dubious trees in which the def line |
| # was not split in def roles |
| next if (!defined($type)); |
| my $content = $self->_convert($arg); |
| if ($type eq 'spaces' or $type eq 'spaces_after_argument' |
| or $type eq 'spaces_before_argument') { |
| $content =~ s/\n$//; |
| $result .= $content; |
| } else { |
| my $attribute = []; |
| if ($type eq 'def_category' and $alias) { |
| push @$attribute, ['automatic', 'on']; |
| } |
| my $format_element; |
| if ($type eq 'def_name') { |
| $format_element = $defcommand_name_type{$main_command}; |
| } elsif ($type eq 'def_arg') { |
| $format_element = 'param'; |
| } elsif ($type eq 'def_typearg') { |
| $format_element = 'paramtype'; |
| } else { |
| $format_element = $type; |
| $format_element =~ s/^def_//; |
| } |
| if ($arg->{'contents'} and scalar($arg->{'contents'}) |
| and $arg->{'contents'}->[0]->{'type'} |
| and $arg->{'contents'}->[0]->{'type'} eq 'bracketed_arg') { |
| push @$attribute, ['bracketed', 'on']; |
| } |
| $result |
| .= $self->txi_markup_open_element("def$format_element", $attribute) |
| .$content |
| .$self->txi_markup_close_element("def$format_element"); |
| } |
| } |
| } |
| pop @{$self->{'document_context'}->[-1]->{'monospace'}}; |
| $result .= $self->txi_markup_close_element('definitionterm'); |
| if ($element->{'cmdname'}) { |
| $result .= $self->txi_markup_close_element($element->{'cmdname'}); |
| } |
| chomp ($result); |
| $result .= "\n"; |
| |
| return $result; |
| } |
| |
| my @node_directions = ('Next', 'Prev', 'Up'); |
| |
| # not used here, but it is consistent with other %commands_args_elements |
| # entries and may be used by XML to Texinfo converters |
| $commands_args_elements{'node'} = ['nodename']; |
| foreach my $direction (@node_directions) { |
| push @{$commands_args_elements{'node'}}, 'node'.lc($direction); |
| } |
| |
| sub _convert($$;$); |
| |
| sub _convert($$;$) { |
| my ($self, $element, $unused) = @_; |
| |
| if (!defined($element)) {cluck();} |
| |
| if (0) { |
| #if (1) { #} |
| print STDERR "element\n"; |
| print STDERR " Command: $element->{'cmdname'}\n" if ($element->{'cmdname'}); |
| print STDERR " Type: $element->{'type'}\n" if ($element->{'type'}); |
| print STDERR " Text: $element->{'text'}\n" if (exists($element->{'text'})); |
| #print STDERR " Special def_command: $element->{'extra'}->{'def_command'}\n" |
| # if (exists($element->{'extra'}) and $element->{'extra'}->{'def_command'}); |
| } |
| |
| if (exists($element->{'text'})) { |
| if ($self->{'document_context'}->[-1]->{'raw'}) { |
| return $element->{'text'}; |
| } |
| my $result = ''; |
| my $text_element; |
| if (exists($element->{'type'}) |
| and defined($type_elements{$element->{'type'}})) { |
| $text_element = $type_elements{$element->{'type'}}; |
| $result .= $self->txi_markup_open_element($text_element); |
| } |
| $result .= $self->txi_markup_convert_text($element); |
| if ($text_element) { |
| $result .= $self->txi_markup_close_element($text_element); |
| } |
| return $result; |
| } |
| |
| my @close_format_elements; |
| # for @-commands with contents, to accumulate conversion results |
| my $result = ''; |
| if (exists($element->{'cmdname'})) { |
| my $cmdname = $element->{'cmdname'}; |
| if (defined($no_arg_commands_formatting{$cmdname})) { |
| if ($self->{'itemize_command_as_argument'} |
| and $element eq $self->{'itemize_command_as_argument'}) { |
| # the element was determined to be an element without braces |
| # when going through @itemize, reuse this information to output |
| # a specific element. |
| my $arguments = [['command', $cmdname]]; |
| return $self->txi_markup_element('formattingcommand', $arguments); |
| } |
| |
| return $self->_format_command($cmdname); |
| } elsif ($accent_types{$cmdname}) { |
| if ($self->get_conf('OUTPUT_CHARACTERS')) { |
| return $self->convert_accents($element, \&_accent, 1); |
| } else { |
| my $attributes = []; |
| my $arg; |
| if (!exists($element->{'contents'})) { |
| $arg = ''; |
| } else { |
| $arg = $self->_convert($element->{'contents'}->[0]); |
| if (exists($element->{'info'}) |
| and exists($element->{'info'}->{'spaces_after_cmd_before_arg'})) { |
| push @$attributes, ['spacesaftercmd', |
| $element->{'info'}->{'spaces_after_cmd_before_arg'}->{'text'}]; |
| } |
| if ($element->{'contents'}->[0]->{'type'} ne 'brace_container' |
| and $element->{'contents'}->[0]->{'type'} ne 'brace_arg') { |
| push @$attributes, ['bracketed', 'off']; |
| } |
| } |
| return $self->_accent($arg, $element, undef, undef, undef, |
| $attributes); |
| } |
| } elsif ($cmdname eq 'item' |
| or $cmdname eq 'itemx' |
| or $cmdname eq 'headitem' |
| or $cmdname eq 'tab') { |
| if ($cmdname eq 'item' |
| and $element->{'parent'}->{'cmdname'} |
| and ($element->{'parent'}->{'cmdname'} eq 'itemize' |
| or $element->{'parent'}->{'cmdname'} eq 'enumerate')) { |
| $result .= $self->txi_markup_open_element('listitem'); |
| if ($element->{'parent'}->{'cmdname'} eq 'itemize') { |
| my $prepended_element |
| = Texinfo::Common::item_itemize_prepended($element); |
| if (!exists($prepended_element->{'cmdname'})) { |
| $prepended_element |
| = Texinfo::Common::non_leading_trailing_tree($prepended_element); |
| } |
| $result .= $self->txi_markup_open_element('prepend') |
| .$self->_convert($prepended_element) |
| .$self->txi_markup_close_element('prepend'); |
| } |
| unshift @close_format_elements, 'listitem'; |
| } elsif (($cmdname eq 'item' |
| or $cmdname eq 'itemx') |
| and $element->{'contents'} |
| and $element->{'contents'}->[0]->{'type'} |
| and $element->{'contents'}->[0]->{'type'} eq 'line_arg') { |
| my $table_command = $element->{'parent'}->{'parent'}->{'parent'}; |
| my $format_item_command; |
| my $attribute = []; |
| if ($table_command->{'cmdname'} |
| and $Texinfo::Commands::block_commands{$table_command->{'cmdname'}} |
| eq 'item_line') { |
| # arguments_line type element |
| my $arguments_line = $table_command->{'contents'}->[0]; |
| my $block_line_arg = $arguments_line->{'contents'}->[0]; |
| |
| my $prepended_element |
| = Texinfo::Common::block_item_line_command($block_line_arg); |
| |
| if ($prepended_element) { |
| $format_item_command = $prepended_element->{'cmdname'}; |
| $attribute = [$self->_infoenclose_attribute($prepended_element)]; |
| } |
| } |
| my $line_item_result |
| = $self->txi_markup_open_element($cmdname); |
| if ($format_item_command) { |
| $line_item_result .= $self->txi_markup_open_element('itemformat', |
| [['command', $format_item_command], @$attribute]); |
| } |
| $line_item_result .= $self->_index_entry($element); |
| my $in_code; |
| $in_code = 1 |
| if ($format_item_command |
| and defined($default_args_code_style{$format_item_command}) |
| and $default_args_code_style{$format_item_command}->[0]); |
| my $in_monospace_not_normal; |
| if ($format_item_command) { |
| if (defined($default_args_code_style{$format_item_command}) |
| and $default_args_code_style{$format_item_command}->[0]) { |
| $in_monospace_not_normal = 1; |
| } elsif ($brace_commands{$format_item_command} |
| and $brace_commands{$format_item_command} eq 'style_no_code') { |
| $in_monospace_not_normal = 0; |
| } |
| } |
| push @{$self->{'document_context'}->[-1]->{'monospace'}}, |
| $in_monospace_not_normal |
| if (defined($in_monospace_not_normal)); |
| |
| my $converted_tree = Texinfo::Common::non_trailing_tree( |
| $element->{'contents'}->[0]); |
| my $arg; |
| if (defined($converted_tree)) { |
| $arg = $self->convert_tree($converted_tree); |
| } else { |
| $arg = ''; |
| } |
| my ($end_space, $end_line) |
| = _end_space_and_format_comment_or_end_line($self, |
| $element->{'contents'}->[0]); |
| pop @{$self->{'document_context'}->[-1]->{'monospace'}} |
| if (defined($in_monospace_not_normal)); |
| |
| $line_item_result .= $arg . $end_space; |
| if ($format_item_command) { |
| $line_item_result .= $self->txi_markup_close_element('itemformat'); |
| } |
| $line_item_result |
| .= $self->txi_markup_close_element($cmdname).$end_line; |
| return $line_item_result; |
| } elsif (($cmdname eq 'item' |
| or $cmdname eq 'headitem' |
| or $cmdname eq 'tab') |
| and $element->{'parent'}->{'type'} |
| and $element->{'parent'}->{'type'} eq 'row') { |
| $result .= $self->txi_markup_open_element('entry', |
| [['command', $cmdname],]); |
| unshift @close_format_elements, 'entry'; |
| } # otherwise we have an incorrect construct, for instance |
| # out of block commands @item, @itemx in enumerate or multitable... |
| } elsif ($element->{'type'} and $element->{'type'} eq 'index_entry_command' |
| and $element->{'extra'} and $element->{'extra'}->{'index_entry'}) { |
| my $indices_information; |
| if ($self->{'document'}) { |
| $indices_information = $self->{'document'}->indices_information(); |
| } |
| |
| my ($index_entry, $index_info) |
| = Texinfo::Common::lookup_index_entry($element->{'extra'}->{'index_entry'}, |
| $indices_information); |
| my $format_element; |
| my $attribute = []; |
| if (exists $line_commands{$cmdname}) { |
| $format_element = $cmdname; |
| } else { |
| $format_element = 'indexcommand'; |
| $attribute = [['command', $cmdname]]; |
| } |
| push @$attribute, ['index', |
| $index_entry->{'index_name'}]; |
| my $leading_spaces = _arg_leading_spaces($self, $element); |
| |
| # this is important to get the spaces before a @subentry |
| my $end_line = _format_comment_or_end_line($self, $element); |
| |
| return $self->txi_markup_open_element($format_element, $attribute) |
| .$leading_spaces.$self->_index_entry($element) |
| .$self->txi_markup_close_element($format_element) |
| .${end_line}; |
| } elsif (exists($line_commands{$cmdname})) { |
| my $type = $line_commands{$cmdname}; |
| if ($type eq 'text') { |
| return '' if ($cmdname eq 'end'); |
| my $attribute; |
| if ($line_command_line_attributes{$cmdname}) { |
| if ($element->{'extra'} |
| and defined($element->{'extra'}->{'text_arg'})) { |
| push @$attribute, [$line_command_line_attributes{$cmdname}, |
| $element->{'extra'}->{'text_arg'}]; |
| } |
| } |
| my $converted_tree = Texinfo::Common::non_trailing_tree( $element->{'contents'}->[0]); |
| my $arg; |
| if (defined($converted_tree)) { |
| $arg = $self->convert_tree($converted_tree); |
| } else { |
| $arg = ''; |
| } |
| my ($end_space, $end_line) |
| = _end_space_and_format_comment_or_end_line($self, |
| $element->{'contents'}->[0]); |
| |
| return $self->txi_markup_open_element($cmdname, $attribute) |
| .$arg.$end_space |
| .$self->txi_markup_close_element($cmdname).${end_line}; |
| } elsif ($type eq 'line') { |
| if ($cmdname eq 'node') { |
| my $nodename; |
| my $node_relations; |
| if ($element->{'extra'} and $element->{'extra'}->{'is_target'}) { |
| $nodename = $element->{'extra'}->{'identifier'}; |
| my $nodes_list = $self->{'document'}->nodes_list(); |
| $node_relations |
| = $nodes_list->[$element->{'extra'}->{'node_number'} -1]; |
| } else { |
| $nodename = ''; |
| } |
| $result .= $self->txi_markup_open_element('node', |
| [['identifier', $nodename],]); |
| push @{$self->{'document_context'}->[-1]->{'monospace'}}, 1; |
| # arguments_line type element |
| my $arguments_line = $element->{'contents'}->[0]; |
| my $line_arg = $arguments_line->{'contents'}->[0]; |
| $result .= $self->txi_markup_open_element('nodename'); |
| if ($nodename ne '') { |
| my $converted_tree = Texinfo::Common::non_trailing_tree( |
| $line_arg); |
| # should always be true |
| if (defined($converted_tree)) { |
| $result .= $self->_convert($converted_tree); |
| } |
| } |
| $result .= $self->txi_markup_close_element('nodename'); |
| # first arg is the node name, directions start at 1. |
| my $direction_index = 1; |
| my $pending_empty_directions = ''; |
| foreach my $direction (@node_directions) { |
| my $format_element = 'node'.lc($direction); |
| if ($node_relations and $node_relations->{'node_directions'} |
| and $node_relations->{'node_directions'}->{lc($direction)}) { |
| my $node_direction |
| = $node_relations->{'node_directions'}->{lc($direction)}; |
| my $node_name = ''; |
| my $attributes = []; |
| if (scalar(@{$arguments_line->{'contents'}}) < $direction_index +1 |
| or !defined($arguments_line->{'contents'}->[$direction_index]->{'extra'}) |
| or !(defined($arguments_line->{'contents'}->[$direction_index] |
| ->{'extra'}->{'manual_node'} |
| or defined($arguments_line->{'contents'}->[$direction_index] |
| ->{'extra'}->{'normalized'})))) { |
| push @$attributes, ['automatic', 'on']; |
| |
| # TODO since we call get_label_element, we only use internal |
| # nodes for directions, external nodes could be used too. |
| if (defined($node_direction->{'extra'}->{'identifier'})) { |
| my $label_element |
| = Texinfo::Common::get_label_element($node_direction); |
| if (defined($label_element)) { |
| my $converted_tree = Texinfo::Common::non_trailing_tree( |
| $label_element); |
| $node_name .= Texinfo::Common::normalize_top_node_name( |
| $self->_convert($converted_tree)); |
| } |
| } |
| } else { |
| my $converted_tree = Texinfo::Common::non_trailing_tree( |
| $arguments_line->{'contents'}->[$direction_index]); |
| $node_name = $self->_convert($converted_tree); |
| } |
| $result .= "$pending_empty_directions". |
| $self->txi_markup_open_element($format_element, $attributes) |
| .$node_name. |
| $self->txi_markup_close_element($format_element); |
| $pending_empty_directions = ''; |
| } else { |
| if ($arguments_line->{'contents'}->[$direction_index]) { |
| $pending_empty_directions .= |
| $self->txi_markup_open_element($format_element) |
| ._arg_leading_spaces($self, |
| $arguments_line->{'contents'}->[$direction_index]) |
| .$self->txi_markup_close_element($format_element); |
| } |
| } |
| $direction_index++; |
| } |
| $result .= $self->txi_markup_close_element('node'); |
| $result .= _format_comment_or_end_line($self, $arguments_line); |
| |
| pop @{$self->{'document_context'}->[-1]->{'monospace'}}; |
| } elsif ($Texinfo::Commands::root_commands{$cmdname}) { |
| my $attribute = []; |
| my $level_adjusted_cmdname |
| = Texinfo::Structuring::section_level_adjusted_command_name($element); |
| if ($level_adjusted_cmdname ne $cmdname) { |
| push @$attribute, ['originalcommand', $cmdname]; |
| } |
| $result .= $self->txi_markup_open_element($level_adjusted_cmdname, |
| $attribute); |
| my $closed_section_element = ''; |
| |
| # arguments_line type |
| my $arguments_line = $element->{'contents'}->[0]; |
| my $converted_tree = Texinfo::Common::non_trailing_tree( |
| $arguments_line->{'contents'}->[0]); |
| my $arg; |
| if (defined($converted_tree)) { |
| $arg = $self->convert_tree($converted_tree); |
| } else { |
| $arg = ''; |
| } |
| my ($end_space, $end_line) |
| = _end_space_and_format_comment_or_end_line($self, |
| $arguments_line->{'contents'}->[-1]); |
| |
| $result .= $self->txi_markup_open_element('sectiontitle') |
| .$arg.$end_space |
| .$self->txi_markup_close_element('sectiontitle') |
| .$closed_section_element.$end_line; |
| } elsif ($Texinfo::Commands::def_commands{$cmdname}) { |
| $result .= _convert_def_line($self, $element); |
| return $result; |
| } else { |
| my $attribute = []; |
| if ($cmdname eq 'listoffloats') { |
| push @$attribute, ['type', |
| $element->{'extra'}->{'float_type'}]; |
| } |
| my $converted_tree = Texinfo::Common::non_trailing_tree( |
| $element->{'contents'}->[0]); |
| my $arg; |
| if (defined($converted_tree)) { |
| $arg = $self->convert_tree($converted_tree); |
| } else { |
| $arg = ''; |
| } |
| my ($end_space, $end_line) |
| = _end_space_and_format_comment_or_end_line($self, |
| $element->{'contents'}->[0]); |
| return $self->txi_markup_open_element($cmdname, $attribute) |
| .$arg.$end_space |
| .$self->txi_markup_close_element($cmdname).$end_line; |
| } |
| } elsif ($type eq 'lineraw') { |
| if ($cmdname eq 'c' or $cmdname eq 'comment') { |
| return _format_comment($self, $element); |
| } elsif ($cmdname eq 'clear' or $cmdname eq 'set' |
| or $cmdname eq 'unmacro') { |
| my $attribute = []; |
| my $misc_args; |
| if (defined($element->{'extra'}) |
| and defined($element->{'extra'}->{'misc_args'})) { |
| $misc_args = $element->{'extra'}->{'misc_args'}; |
| } |
| if (defined($misc_args) and scalar(@$misc_args)) { |
| if ($cmdname eq 'clear' or $cmdname eq 'set' |
| or $cmdname eq 'unmacro') { |
| unshift @$attribute, ['name', $misc_args->[0]]; |
| } |
| } |
| my $arg_element = $element->{'contents'}->[0]; |
| my $converted_tree = Texinfo::Common::non_trailing_tree( |
| $arg_element); |
| my $arg; |
| if (defined($converted_tree)) { |
| $arg = $self->convert_tree($converted_tree); |
| } else { |
| $arg = ''; |
| } |
| my ($end_space, $end_line) |
| = _end_space_and_format_comment_or_end_line($self, |
| $arg_element); |
| |
| return $self->txi_markup_open_element($cmdname, $attribute) |
| .$arg.$end_space |
| .$self->txi_markup_close_element($cmdname).$end_line; |
| } else { |
| my $arg_element = $element->{'contents'}->[0]; |
| my $converted_tree = Texinfo::Common::non_trailing_tree( |
| $arg_element); |
| my $arg = ''; |
| if (defined($converted_tree)) { |
| foreach my $content (@{$converted_tree->{'contents'}}) { |
| $arg .= $self->txi_markup_protect_text($content->{'text'}); |
| } |
| } |
| my ($end_space, $end_line) |
| = _end_space_and_format_comment_or_end_line($self, |
| $arg_element); |
| return $self->txi_markup_open_element($cmdname) |
| .$arg.$end_space |
| .$self->txi_markup_close_element($cmdname).$end_line; |
| } |
| } else { |
| # commands with somewhat ad-hoc formatting, put the actual |
| # argument in line attribute, add attributes with the |
| # collected misc_args values, and have no content inside. |
| print STDERR "BUG: unknown line_command style $type\n" |
| if ($type ne 'specific'); |
| my $args_attributes; |
| if (exists($line_command_numbered_arguments_attributes{$cmdname})) { |
| $args_attributes |
| = $line_command_numbered_arguments_attributes{$cmdname}; |
| } elsif ($cmdname eq 'columnfractions') { |
| return _format_columnfractions($self, $element); |
| } else { |
| $args_attributes = ['value']; |
| } |
| my $attribute = []; |
| my $arg_index = 0; |
| if (exists($element->{'extra'}) |
| and exists($element->{'extra'}->{'misc_args'})) { |
| foreach my $arg_attribute (@{$args_attributes}) { |
| if (exists($element->{'extra'}->{'misc_args'}->[$arg_index])) { |
| push @$attribute, [$arg_attribute, |
| $element->{'extra'}->{'misc_args'}->[$arg_index]]; |
| } |
| $arg_index++; |
| } |
| } |
| my $end_line = _format_comment_or_end_line($self, $element); |
| # not sure if it may happen |
| $end_line = "\n" if ($end_line eq ''); |
| push @$attribute, $self->_texinfo_line($element); |
| return $self->txi_markup_open_element($cmdname, $attribute) |
| .$self->txi_markup_close_element($cmdname).$end_line; |
| } |
| } elsif (exists($nobrace_commands{$cmdname})) { |
| # other or skipspace, symbol commands should be selected above |
| # as being in no_arg_commands_formatting |
| my $spaces = ''; |
| $spaces = $element->{'extra'}->{'spaces_after_command'} |
| if (exists($element->{'extra'}) |
| and exists($element->{'extra'}->{'spaces_after_command'}) |
| and $element->{'extra'}->{'spaces_after_command'} ne ''); |
| return $self->txi_markup_open_element($cmdname) |
| .$self->txi_markup_close_element($cmdname).$spaces; |
| } elsif (exists($element->{'type'}) |
| and $element->{'type'} eq 'definfoenclose_command') { |
| my $in_monospace_not_normal; |
| if (defined($default_args_code_style{$cmdname}) |
| and $default_args_code_style{$cmdname}->[0]) { |
| $in_monospace_not_normal = 1; |
| } elsif ($brace_commands{$cmdname} |
| and $brace_commands{$cmdname} eq 'style_no_code') { |
| $in_monospace_not_normal = 0; |
| } |
| push @{$self->{'document_context'}->[-1]->{'monospace'}}, |
| $in_monospace_not_normal |
| if (defined($in_monospace_not_normal)); |
| my $arg = $self->_convert($element->{'contents'}->[0]); |
| pop @{$self->{'document_context'}->[-1]->{'monospace'}} |
| if (defined($in_monospace_not_normal)); |
| my $command_result = $self->txi_markup_open_element('infoenclose', |
| [['command', $cmdname], |
| $self->_infoenclose_attribute($element)]) |
| .$arg. $self->txi_markup_close_element('infoenclose'); |
| return $command_result; |
| } elsif (exists($element->{'contents'}) |
| and exists($brace_commands{$cmdname})) { |
| |
| if ($Texinfo::Commands::inline_format_commands{$cmdname} |
| and $element->{'extra'} and $element->{'extra'}->{'format'} |
| and $self->{'expanded_formats'}->{$element->{'extra'}->{'format'}}) { |
| if ($cmdname eq 'inlineraw') { |
| push @{$self->{'document_context'}}, {'monospace' => [0]}; |
| $self->{'document_context'}->[-1]->{'raw'} = 1; |
| } |
| my $command_result = ''; |
| if (scalar(@{$element->{'contents'}}) >= 2 |
| and defined($element->{'contents'}->[1]) |
| and $element->{'contents'}->[1]->{'contents'} |
| and scalar(@{$element->{'contents'}->[1]->{'contents'}})) { |
| $command_result = $self->_convert({'contents' |
| => [$element->{'contents'}->[1]]}); |
| } |
| if ($cmdname eq 'inlineraw') { |
| pop @{$self->{'document_context'}}; |
| } |
| return $command_result; |
| } elsif ($cmdname eq 'value') { |
| # value in tree corresponds to an unknown flag, ignored here |
| return ''; |
| } |
| |
| |
| # this is used for commands without args, or associated to the |
| # first argument |
| my $attribute = []; |
| if ($cmdname eq 'verb') { |
| push @$attribute, ['delimiter', $element->{'info'}->{'delimiter'}]; |
| } elsif ($cmdname eq 'anchor' |
| or $cmdname eq 'namedanchor') { |
| my $anchor_name; |
| if ($element->{'extra'} and $element->{'extra'}->{'is_target'}) { |
| $anchor_name = $element->{'extra'}->{'identifier'}; |
| } else { |
| $anchor_name = ''; |
| } |
| push @$attribute, ['identifier', $anchor_name]; |
| } |
| |
| my $space_after_command_attribute; |
| if ($element->{'info'} |
| and $element->{'info'}->{'spaces_after_cmd_before_arg'}) { |
| $space_after_command_attribute = ['spacesaftercmd', |
| $element->{'info'}->{'spaces_after_cmd_before_arg'}->{'text'}]; |
| } |
| |
| my @format_elements; |
| my $main_cmdname; |
| if ($commands_args_elements{$cmdname}) { |
| # command with arguments, format the arguments using their specific |
| # elements |
| @format_elements = @{$commands_args_elements{$cmdname}}; |
| $main_cmdname = $cmdname; |
| } else { |
| # command with only one argument, the command itself is used as |
| # element for the first argument |
| @format_elements = ($cmdname); |
| # leading spaces are directly associated to the @-command for @-command |
| # in context brace_commands |
| push @$attribute, $space_after_command_attribute |
| if (defined($space_after_command_attribute)); |
| } |
| |
| if ($Texinfo::Commands::brace_commands{$cmdname} eq 'context') { |
| push @{$self->{'document_context'}}, {'monospace' => [0]}; |
| } |
| my $last_empty_element; |
| my $args_or_one_arg_cmd = ''; |
| my $arg_index = 0; |
| foreach my $format_element (@format_elements) { |
| if (defined($element->{'contents'}->[$arg_index])) { |
| # Leading spaces are gathered here except for context brace_commands |
| # (gathered just above). |
| my $in_monospace_not_normal; |
| if (defined($default_args_code_style{$cmdname}) |
| and $default_args_code_style{$cmdname}->[$arg_index]) { |
| $in_monospace_not_normal = 1; |
| } elsif ($brace_commands{$cmdname} |
| and $brace_commands{$cmdname} eq 'style_no_code') { |
| $in_monospace_not_normal = 0; |
| } |
| push @{$self->{'document_context'}->[-1]->{'monospace'}}, |
| $in_monospace_not_normal |
| if (defined($in_monospace_not_normal)); |
| my $arg = $self->_convert($element->{'contents'}->[$arg_index]); |
| pop @{$self->{'document_context'}->[-1]->{'monospace'}} |
| if (defined($in_monospace_not_normal)); |
| |
| if (!defined($main_cmdname) or $arg ne '' |
| or scalar(@$attribute) > 0) { |
| $args_or_one_arg_cmd .= |
| $self->txi_markup_open_element($format_element, $attribute) |
| .$arg |
| .$self->txi_markup_close_element($format_element); |
| $last_empty_element = undef; |
| # we keep the last empty argument to be able to prepend it to be able |
| # to reconstitute trailing empty arguments in the original Texinfo |
| # code. |
| # For example, for @bracecmd{a,b,,c,,} we keep the last (6th argument) |
| # empty element. |
| } elsif (defined($main_cmdname)) { |
| $last_empty_element |
| = $self->txi_markup_open_element($format_element) |
| .$self->txi_markup_close_element($format_element); |
| } |
| $attribute = []; |
| } else { |
| last; |
| } |
| $arg_index++; |
| } |
| if ($last_empty_element) { |
| $args_or_one_arg_cmd .= $last_empty_element; |
| } |
| if ($Texinfo::Commands::brace_commands{$cmdname} eq 'context') { |
| pop @{$self->{'document_context'}}; |
| } |
| if (not defined($main_cmdname)) { |
| # one argument @-command |
| return $args_or_one_arg_cmd; |
| } |
| # This is for the main command |
| $attribute = []; |
| push @$attribute, $space_after_command_attribute |
| if (defined($space_after_command_attribute)); |
| if ($cmdname eq 'image') { |
| if (Texinfo::Common::element_is_inline($element)) { |
| push @$attribute, ['where', 'inline']; |
| } |
| } elsif ($Texinfo::Commands::ref_commands{$cmdname}) { |
| if ($element->{'contents'}) { |
| my $normalized; |
| my $node_arg = $element->{'contents'}->[0]; |
| if ($node_arg and $node_arg->{'extra'} |
| and $node_arg->{'extra'}->{'node_content'}) { |
| my $normalized; |
| if (defined($node_arg->{'extra'}->{'normalized'})) { |
| $normalized = $node_arg->{'extra'}->{'normalized'}; |
| } else { |
| $normalized |
| = Texinfo::Convert::NodeNameNormalization::convert_to_node_identifier( |
| Texinfo::TreeElement::new( |
| $node_arg->{'extra'}->{'node_content'})); |
| } |
| if ($normalized) { |
| push @$attribute, ['label', $normalized]; |
| } |
| } |
| my $manual; |
| my $manual_arg_index = 3; |
| if ($cmdname eq 'link' or $cmdname eq 'inforef') { |
| $manual_arg_index = 2; |
| } |
| if (defined($element->{'contents'}->[$manual_arg_index]) |
| and $element->{'contents'}->[$manual_arg_index]->{'contents'} |
| and @{$element->{'contents'}->[$manual_arg_index]->{'contents'}}) { |
| |
| Texinfo::Convert::Text::set_options_code( |
| $self->{'convert_text_options'}); |
| Texinfo::Convert::Text::set_options_encoding_if_not_ascii($self, |
| $self->{'convert_text_options'}); |
| $manual = Texinfo::Convert::Text::convert_to_text( |
| $element->{'contents'}->[$manual_arg_index], |
| $self->{'convert_text_options'}); |
| Texinfo::Convert::Text::reset_options_encoding( |
| $self->{'convert_text_options'}); |
| Texinfo::Convert::Text::reset_options_code( |
| $self->{'convert_text_options'}); |
| } |
| if (!defined($manual) and $node_arg |
| and $node_arg->{'extra'} |
| and $node_arg->{'extra'}->{'manual_content'}) { |
| Texinfo::Convert::Text::set_options_code( |
| $self->{'convert_text_options'}); |
| Texinfo::Convert::Text::set_options_encoding_if_not_ascii($self, |
| $self->{'convert_text_options'}); |
| $manual = Texinfo::Convert::Text::convert_to_text( |
| $node_arg->{'extra'}->{'manual_content'}, |
| $self->{'convert_text_options'}); |
| Texinfo::Convert::Text::reset_options_encoding( |
| $self->{'convert_text_options'}); |
| Texinfo::Convert::Text::reset_options_code( |
| $self->{'convert_text_options'}); |
| } |
| if (defined($manual)) { |
| my $manual_base = $manual; |
| $manual_base =~ s/\.[^\.]*$//; |
| $manual_base =~ s/^.*\///; |
| |
| push @$attribute, ['manual', $manual_base] |
| if ($manual_base ne ''); |
| } |
| } |
| } |
| # this never happens as there is no @-commands with more than one |
| # argument that is also a context brace_commands. Leading spaces |
| # after the command brace opening are only associated with command |
| # if a context brace_commands, therefore they are with the first argument. |
| return $self->txi_markup_open_element($main_cmdname, $attribute) |
| .$args_or_one_arg_cmd |
| .$self->txi_markup_close_element($main_cmdname); |
| } elsif (exists($Texinfo::Commands::block_commands{$cmdname})) { |
| if ($self->{'context_block_commands'}->{$cmdname}) { |
| push @{$self->{'document_context'}}, {'monospace' => [0]}; |
| } |
| my $prepended_elements = ''; |
| my $attribute = []; |
| if ($cmdname eq 'itemize' |
| or $Texinfo::Commands::block_commands{$cmdname} |
| eq 'item_line') { |
| # arguments_line type element |
| my $arguments_line = $element->{'contents'}->[0]; |
| my $block_line_arg = $arguments_line->{'contents'}->[0]; |
| |
| my $command_argument; |
| if ($cmdname eq 'itemize') { |
| $command_argument |
| = Texinfo::Common::block_line_argument_command($block_line_arg); |
| } else { |
| $command_argument |
| = Texinfo::Common::item_line_block_line_argument_command( |
| $block_line_arg); |
| } |
| |
| if ($command_argument) { |
| if ($element->{'cmdname'} eq 'itemize' |
| and !$command_argument->{'contents'}) { |
| # this is used to determine that a command appears on the @itemize |
| # line without braces, for the command line arguments |
| # conversion just below. |
| # It could have been possible to use the tree in the command as |
| # argument formatting, but this is simpler, and $command_argument |
| # was already obtained by going through the tree. |
| $self->{'itemize_command_as_argument'} = $command_argument; |
| } |
| push @$attribute, |
| (['commandarg', $command_argument->{'cmdname'}], |
| $self->_infoenclose_attribute($command_argument)); |
| } |
| } elsif ($cmdname eq 'enumerate') { |
| my $arguments_line = $element->{'contents'}->[0]; |
| my $block_line_arg = $arguments_line->{'contents'}->[0]; |
| my $specification = '1'; |
| my ($spec_text, $surplus) |
| = Texinfo::Common::simple_arg_text($block_line_arg); |
| if (defined($spec_text)) { |
| $specification = $spec_text if ($spec_text =~ /^(\d+|[[:alpha:]])$/); |
| } |
| push @$attribute, ['first', $specification]; |
| } elsif ($cmdname eq 'float' and $element->{'extra'}) { |
| if ($element->{'extra'}->{'is_target'}) { |
| push @$attribute, ['identifier', $element->{'extra'}->{'identifier'}]; |
| } |
| push @$attribute, ['type', |
| $element->{'extra'}->{'float_type'}]; |
| if (defined($element->{'extra'}->{'float_number'})) { |
| push @$attribute, ['number', |
| $element->{'extra'}->{'float_number'}]; |
| } |
| } elsif ($cmdname eq 'verbatim') { |
| push @$attribute, ['space', 'preserve']; |
| } elsif ($cmdname eq 'macro' |
| or $cmdname eq 'rmacro' |
| or $cmdname eq 'linemacro') { |
| if (defined($element->{'extra'}) |
| and defined($element->{'extra'}->{'macro_name'})) { |
| my $macro_name = $element->{'extra'}->{'macro_name'}; |
| push @$attribute, ['name', $macro_name]; |
| my $macro_args_nr = scalar(@{$element->{'extra'}->{'formal_args'}}); |
| if ($macro_args_nr) { |
| for (my $i = 0; $i < $macro_args_nr; $i++) { |
| my $formal_arg = $element->{'extra'}->{'formal_args'}->[$i]; |
| $prepended_elements .= $self->txi_markup_open_element('formalarg') |
| .$self->txi_markup_protect_text($formal_arg) |
| .$self->txi_markup_close_element('formalarg'); |
| } |
| } |
| } |
| # through arguments_line type and macro_line type |
| my $line = $element->{'contents'}->[0]->{'contents'}->[0]->{'text'}; |
| chomp($line); |
| if ($line ne '') { |
| push @$attribute, ['line', $line]; |
| } |
| } |
| if ($self->{'expanded_formats'}->{$cmdname}) { |
| $self->{'document_context'}->[-1]->{'raw'} = 1; |
| } else { |
| my $end_command; |
| if (exists($element->{'contents'}) |
| and $element->{'contents'}->[-1]->{'cmdname'} |
| and $element->{'contents'}->[-1]->{'cmdname'} eq 'end') { |
| $end_command = $element->{'contents'}->[-1]; |
| my $leading_spaces = _arg_leading_spaces($self, $end_command); |
| if ($leading_spaces ne '') { |
| push @$attribute, ['endspaces', $leading_spaces]; |
| } |
| } |
| $result .= $self->txi_markup_open_element($cmdname, |
| $attribute) |
| .${prepended_elements}; |
| |
| my $arguments_line; |
| if (exists($element->{'contents'}) |
| and exists($element->{'contents'}->[0]->{'type'}) |
| and $element->{'contents'}->[0]->{'type'} eq 'arguments_line') { |
| $arguments_line = $element->{'contents'}->[0]; |
| } |
| if (defined($arguments_line)) { |
| my $arguments_list = $arguments_line->{'contents'}; |
| my $variadic_element = undef; |
| my $last_empty_element; |
| my $end_line = ''; |
| if ($commands_args_elements{$cmdname}) { |
| my $arg_index = 0; |
| foreach my $arg_element (@{$arguments_list}) { |
| my $format_element; |
| if (defined($variadic_element)) { |
| $format_element = $variadic_element; |
| } else { |
| if ($commands_args_elements{$cmdname}->[$arg_index] eq '*') { |
| $variadic_element |
| = $commands_args_elements{$cmdname}->[$arg_index-1]; |
| $format_element = $variadic_element; |
| # the last argument was empty, it is the same argument |
| # as the variadic argument, it needs to be output to have |
| # it count as the last non variadic argument. |
| if ($last_empty_element) { |
| $result .= $last_empty_element; |
| $last_empty_element = undef; |
| } |
| } else { |
| $format_element |
| = $commands_args_elements{$cmdname}->[$arg_index]; |
| } |
| } |
| my $arg = ''; |
| my $end_space = ''; |
| # FIXME how can this not be true? |
| if (defined($arg_element)) { |
| my $in_code; |
| $in_code = 1 |
| if (defined($default_args_code_style{$cmdname}) |
| and $default_args_code_style{$cmdname}->[$arg_index]); |
| push @{$self->{'document_context'}->[-1]->{'monospace'}}, 1 |
| if ($in_code); |
| if ($arg_index+1 eq scalar(@{$arguments_list})) { |
| # last argument |
| my $converted_tree = Texinfo::Common::non_trailing_tree( |
| $arg_element); |
| if (defined($converted_tree)) { |
| $arg = $self->convert_tree($converted_tree); |
| } else { |
| $arg = ''; |
| } |
| ($end_space, $end_line) |
| = _end_space_and_format_comment_or_end_line($self, |
| $arg_element); |
| |
| # happens for @-commands interrupted by other commands |
| # incorrectly present on the line |
| $end_line = "\n" if ($end_line eq ''); |
| } else { |
| $arg = $self->_convert($arg_element); |
| } |
| pop @{$self->{'document_context'}->[-1]->{'monospace'}} |
| if ($in_code); |
| } |
| # must add every variadic argument even if empty to get the |
| # correct count |
| if ($arg ne '' or $variadic_element) { |
| $result .= $self->txi_markup_open_element($format_element) |
| .$arg.$end_space |
| .$self->txi_markup_close_element($format_element); |
| $last_empty_element = undef; |
| } else { |
| if ($arg_index > 0) { |
| my $spaces = []; |
| if ($end_space ne '') { |
| push @$spaces, ['spaces', $end_space]; |
| } |
| # we keep the last empty argument to be able to prepend |
| # it to be able to reconstitute trailing empty arguments |
| # in the original Texinfo code. |
| $last_empty_element |
| = $self->txi_markup_open_element($format_element, $spaces) |
| .$self->txi_markup_close_element($format_element); |
| } else { |
| $result .= $end_space; |
| } |
| } |
| $arg_index++; |
| } |
| if ($last_empty_element) { |
| $result .= $last_empty_element; |
| } |
| $result .= $end_line; |
| } else { |
| # in that case the end of line is in the columnfractions line |
| # or in the columnprototypes. |
| if ($cmdname eq 'multitable') { |
| my $columnfractions_element |
| = Texinfo::Common::multitable_columnfractions($element); |
| |
| if ($columnfractions_element) { |
| $result |
| .= _format_columnfractions($self, $columnfractions_element); |
| } else { |
| # arguments_line type element |
| my $arguments_line = $element->{'contents'}->[0]; |
| my $block_line_arg = $arguments_line->{'contents'}->[0]; |
| if ($block_line_arg->{'contents'}) { |
| $result .= $self->txi_markup_open_element('columnprototypes'); |
| foreach my $arg (@{$block_line_arg->{'contents'}}) { |
| if ($arg->{'type'} |
| and $arg->{'type'} eq 'bracketed_arg') { |
| my $attributes = []; |
| push @$attributes, ['bracketed', 'on']; |
| $result .= $self->txi_markup_open_element('columnprototype', |
| $attributes) |
| .$self->_convert($arg) |
| .$self->txi_markup_close_element('columnprototype'); |
| } else { |
| $result .= $self->_convert($arg); |
| } |
| } |
| $result .= $self->txi_markup_close_element('columnprototypes'); |
| } |
| $result .= _format_comment_or_end_line($self, $arguments_line); |
| # happens for multitable line with prototypes interrupted |
| # by another @-command |
| $result .= "\n" unless ($result =~ /\n/); |
| } |
| } else { |
| # get end of lines from @*table and block @-commands that |
| # usually have arguments but with missing or bogus arguments, |
| # and from block @-commands without argument. |
| my ($end_space, $end_line) |
| = _end_space_and_format_comment_or_end_line($self, |
| $arguments_line->{'contents'}->[0]); |
| $result .= $end_space . $end_line; |
| # happens for commands interrupted on the line |
| $result .= "\n" unless ($result =~ /\n/); |
| } |
| } |
| # @def* line args are in the first def contents, the def_line type |
| # and processed below. |
| } elsif (not |
| exists($Texinfo::Commands::def_commands{$cmdname})) { |
| # NOTE probably never happens right now. |
| # happens for bogus empty @macro immediately followed by |
| # newline. |
| #print STDERR "no args: $cmdname\n"; |
| $result .= "\n"; |
| } |
| unshift @close_format_elements, $cmdname; |
| } |
| delete $self->{'itemize_command_as_argument'} |
| if ($self->{'itemize_command_as_argument'}); |
| } |
| } |
| if (exists($element->{'type'})) { |
| |
| if ($element->{'type'} eq 'arguments_line' |
| or ($container_ignored_if_empty{$element->{'type'}} |
| and !exists($element->{'contents'}))) { |
| return $result; |
| } |
| |
| if (defined($type_elements{$element->{'type'}})) { |
| my $attribute = []; |
| if ($element->{'type'} eq 'preformatted') { |
| push @$attribute, ['space', 'preserve']; |
| } |
| $result |
| .= $self->txi_markup_open_element($type_elements{$element->{'type'}}, |
| $attribute); |
| } elsif ($element->{'type'} eq 'def_line') { |
| $result .= _convert_def_line($self, $element); |
| return $result; |
| # case of bracketed in def line not corresponding to a def* argument |
| # by itself, for example, in the following '{a b}{c d}' is the |
| # argument, it is not a bracketed by itself, but contains two |
| # bracketed_arg. Similarly '{e f}' is a bracketed_arg but the arg is |
| # 'h{e f}'. |
| # @deffn {a b}{c d} h{e f} g h |
| } elsif ($element->{'type'} eq 'bracketed_arg' |
| and not ($element->{'parent'}->{'parent'} |
| and $element->{'parent'}->{'parent'}->{'parent'} |
| and $element->{'parent'}->{'parent'}->{'parent'}->{'cmdname'} |
| and $element->{'parent'}->{'parent'}->{'parent'}->{'cmdname'} |
| eq 'multitable') |
| and (!$element->{'parent'}->{'type'} |
| or ($element->{'parent'}->{'type'} ne 'def_category' |
| and $element->{'parent'}->{'type'} ne 'def_type' |
| and $element->{'parent'}->{'type'} ne 'def_name' |
| and $element->{'parent'}->{'type'} ne 'def_typearg' |
| and $element->{'parent'}->{'type'} ne 'def_arg' |
| and $element->{'parent'}->{'type'} ne 'def_class'))) { |
| my $attribute = []; |
| push @$attribute, ['bracketed', 'on']; |
| $result .= $self->txi_markup_open_element("defbracketed", $attribute); |
| push @close_format_elements, 'defbracketed'; |
| } |
| } |
| if (exists($element->{'contents'})) { |
| my $in_code; |
| if (($element->{'cmdname'} |
| and ($Texinfo::Commands::preformatted_code_commands{$element->{'cmdname'}} |
| or $Texinfo::Commands::math_commands{$element->{'cmdname'}})) |
| or $element->{'type'} and $element->{'type'} eq 'menu_entry_node') { |
| $in_code = 1; |
| } |
| push @{$self->{'document_context'}->[-1]->{'monospace'}}, 1 |
| if ($in_code); |
| if (ref($element->{'contents'}) ne 'ARRAY') { |
| cluck "contents not an array($element->{'contents'})."; |
| } |
| foreach my $content (@{$element->{'contents'}}) { |
| $result .= $self->_convert($content); |
| } |
| pop @{$self->{'document_context'}->[-1]->{'monospace'}} |
| if ($in_code); |
| } |
| if (exists($element->{'type'})) { |
| if (defined($type_elements{$element->{'type'}})) { |
| $result |
| .= $self->txi_markup_close_element($type_elements{$element->{'type'}}); |
| } |
| } |
| |
| foreach my $format_element (@close_format_elements) { |
| $result .= $self->txi_markup_close_element($format_element); |
| } |
| if (exists($element->{'cmdname'}) |
| and exists($Texinfo::Commands::block_commands{$element->{'cmdname'}})) { |
| if ($self->{'expanded_formats'}->{$element->{'cmdname'}}) { |
| } else { |
| if (exists($element->{'contents'}) |
| and $element->{'contents'}->[-1]->{'cmdname'} |
| and $element->{'contents'}->[-1]->{'cmdname'} eq 'end') { |
| my $end_command = $element->{'contents'}->[-1]; |
| my ($end_space, $end_line) |
| = _end_space_and_format_comment_or_end_line($self, |
| $end_command->{'contents'}->[0]); |
| $result .= $end_space . $end_line; |
| } else { |
| # missing @end, add an end of line after the closing markup element. |
| $result .= "\n"; |
| } |
| } |
| if ($self->{'context_block_commands'}->{$element->{'cmdname'}}) { |
| pop @{$self->{'document_context'}}; |
| } |
| } elsif (exists($element->{'cmdname'}) |
| and $Texinfo::Commands::root_commands{$element->{'cmdname'}} |
| and $element->{'cmdname'} ne 'node') { |
| my $level_adjusted_cmdname |
| = Texinfo::Structuring::section_level_adjusted_command_name($element); |
| my $sections_list = $self->{'document'}->sections_list(); |
| my $section_relations |
| = $sections_list->[$element->{'extra'}->{'section_number'} -1]; |
| if (!($section_relations |
| and $section_relations->{'section_children'} |
| and scalar(@{$section_relations->{'section_children'}})) |
| or $level_adjusted_cmdname eq 'top') { |
| $result .= $self->txi_markup_close_element($level_adjusted_cmdname)."\n"; |
| my $current = $element; |
| my $current_relations |
| = $sections_list->[$current->{'extra'}->{'section_number'} -1]; |
| while ($current_relations->{'section_directions'} |
| and $current_relations->{'section_directions'}->{'up'} |
| and !$current_relations->{'section_directions'}->{'next'} |
| and Texinfo::Structuring::section_level_adjusted_command_name( |
| $current_relations->{'section_directions'}->{'up'}->{'element'}) ne 'top') { |
| $current_relations |
| = $current_relations->{'section_directions'}->{'up'}; |
| $current = $current_relations->{'element'}; |
| my $level_adjusted_current_cmdname |
| = Texinfo::Structuring::section_level_adjusted_command_name($current); |
| $result |
| .= $self->txi_markup_close_element($level_adjusted_current_cmdname) |
| ."\n"; |
| } |
| } |
| } |
| return $result; |
| } |
| |
| 1; |
| |
| __END__ |
| |
| =head1 NAME |
| |
| Texinfo::Example::TexinfoMarkup - Convert Texinfo tree to element and attribute markup |
| |
| =head1 SYNOPSIS |
| |
| package Texinfo::Convert::TexinfoMyMarkup; |
| |
| use Texinfo::Example::TexinfoMarkup; |
| |
| @ISA = qw(Texinfo::Example::TexinfoMarkup); |
| |
| sub converter_defaults ($$) { |
| return %myconverter_defaults; |
| } |
| |
| sub txi_markup_protect_text($$) |
| { |
| my $self = shift; |
| .... |
| } |
| |
| =head1 NOTES |
| |
| The Texinfo Perl module main purpose is to be used in C<texi2any> to convert |
| Texinfo to other formats. There is no promise of API stability. |
| |
| =head1 DESCRIPTION |
| |
| C<Texinfo::Example::TexinfoMarkup> converts a Texinfo tree to the Texinfo |
| Markup Language which is based on nested elements with attributes, similar |
| to XML. All the information present in the Texinfo tree, after |
| expansion of C<@macro>, C<@value> and inclusion of include files is |
| kept. C<Texinfo::Example::TexinfoMarkup> is an abstract class, to be used as a |
| super class for modules implementing specific markup formatting functions |
| called by C<Texinfo::Example::TexinfoMarkup>. |
| |
| The Texinfo Markup Language elements and attributes are not documented, |
| but the Texinfo XML output by the C<Texinfo::Example::TexinfoXML> subclass |
| (L<Texinfo::Example::TexinfoXML>) is a straightforward formatting as XML, |
| and is described by the Texinfo DTD. Therefore the Texinfo DTD can be |
| used as a description of the structure of both Texinfo XML and of the |
| more abstract Texinfo Markup Language. |
| |
| =head1 METHODS |
| |
| =head2 Markup formatting methods defined by subclasses |
| |
| The following methods should be implemented by the modules inheriting |
| from C<Texinfo::Example::TexinfoMarkup>: |
| |
| =over |
| |
| =item $result = $converter->txi_markup_atom($atom) |
| |
| Format the I<$atom> symbol string in a simpler way than with an element. For |
| example in XML the formatting of the symbol is achieved with an entity. |
| |
| =item $result = $converter->txi_markup_comment($comment_string) |
| |
| Format I<$comment_string> as a comment. |
| |
| =item $result = $converter->txi_markup_convert_text($element) |
| |
| Called to format the Texinfo tree I<$element> text, which is a |
| reference on a hash. The I<$element> text is in the C<text> key. |
| The C<type> key value may also be set to distinguish the type of text |
| (L<Texinfo::Parser/Types for text elements>). |
| Texinfo tree elements are described in details in L<Texinfo::Parser/TEXINFO TREE>. |
| |
| =item $result = $converter->txi_markup_element($format_element, $attributes) |
| |
| =item $result = $converter->txi_markup_open_element($format_element, $attributes) |
| |
| =item $result = $converter->txi_markup_close_element($format_element, $attributes) |
| |
| C<txi_markup_element> is called for the formatting of empty elements. Otherwise, |
| C<txi_markup_open_element> is called when an element is opened, and |
| C<txi_markup_close_element> is called when an element is closed. |
| I<$format_element> is the element name, I<$attributes> is a reference on an |
| array containing references on arrays of pairs, one pair for each attribute, with |
| the attribute name as the first item of the pair and the attribute text as the |
| second item of the pair. |
| |
| =item $result = $converter->txi_markup_header() |
| |
| Called to format a header at the beginning of output files. |
| |
| =item $result = $converter->txi_markup_protect_text($string) |
| |
| Protect special character in text for text fragments out of text |
| Texinfo tree elements. For example, for spaces at end of line that |
| are ignorable in most output formats, for C<@set> or C<@macro> |
| arguments. |
| |
| =back |
| |
| =head2 Formatting state information |
| |
| A method is available for subclasses to gather information on the |
| formatting state: |
| |
| =over |
| |
| =item $converter->in_monospace() |
| |
| Return 1 if in a context where spacing should be kept |
| and C<---> or C<''> left as is, for example in C<@code>, C<@example>. |
| |
| =back |
| |
| =head1 AUTHOR |
| |
| Patrice Dumas, E<lt>bug-texinfo@gnu.orgE<gt> |
| |
| =head1 SEE ALSO |
| |
| L<Texinfo::Convert::Converter>. L<Texinfo::Example::TexinfoXML>. The |
| C<Texinfo::Example::TexinfoSXML> is another subclass, which outputs |
| SXML. It is not much documented. |
| |
| =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 |