# ReadDocBook.pm: output tree as DocBook using Texinfo::Reader. # # 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 . # # Original author: Patrice Dumas # This file is an example of a converter using the Reader interface and # the TreeElement interface. It is not used in texi2any. package Texinfo::Example::TreeElementReadDocBook; use 5.006; use strict; # To check if there is no erroneous autovivification #no autovivification qw(fetch delete exists store strict); #use Data::Dumper; use Carp qw(cluck confess); # for XS_structuring_enabled use Texinfo::XSLoader; use Texinfo::Commands; use Texinfo::TreeElement; use Texinfo::Reader; use Texinfo::Common; # for debugging use Texinfo::Convert::Texinfo; use Texinfo::Document; use Texinfo::Convert::Unicode; use Texinfo::Convert::Utils; use Texinfo::Convert::Text; use Texinfo::Convert::Converter; use Texinfo::Example::TreeElementConverter; # inherits from Texinfo::Convert::Converter through # Texinfo::Example::TreeElementConverter our @ISA = qw(Texinfo::Example::TreeElementConverter); our $VERSION = '7.3'; my %brace_commands = %Texinfo::Commands::brace_commands; my $nbsp = '&#'.hex('00A0').';'; my %defaults = ( # Customization option variables 'FORMAT_MENU' => 'nomenu', 'EXTENSION' => 'xml', # dbk? 'OUTPUT_ENCODING_NAME' => 'utf-8', 'SPLIT' => '', 'OPEN_QUOTE_SYMBOL' => '&#'.hex('2018').';', 'CLOSE_QUOTE_SYMBOL' => '&#'.hex('2019').';', 'USE_NUMERIC_ENTITY' => 1, 'NO_TOP_NODE_OUTPUT' => 1, ); my @docbook_image_extensions = ('eps', 'gif', 'jpg', 'jpeg', 'pdf', 'png', 'svg'); my %docbook_special_quotations; foreach my $special_quotation ( 'caution', 'important', 'note', 'tip', 'warning') { $docbook_special_quotations{$special_quotation} = 1; } # For '*', there is no line break in DocBook, except in cmdsynopsis (in this # case it is ). But currently we don't use cmdsynopsis, and it is # unlikely we ever will. my %docbook_specific_formatting = ( 'TeX' => '&tex;', 'LaTeX' => '&latex;', "\t" => $nbsp, "\n" => $nbsp, " " => $nbsp, 'tie' => $nbsp, ); my %docbook_no_arg_commands_formatting = %Texinfo::Convert::Converter::xml_text_entity_no_arg_commands_formatting; foreach my $command (keys(%Texinfo::Convert::Unicode::unicode_entities)) { $docbook_no_arg_commands_formatting{$command} = $Texinfo::Convert::Unicode::unicode_entities{$command}; } foreach my $command (keys(%docbook_specific_formatting)) { $docbook_no_arg_commands_formatting{$command} = $docbook_specific_formatting{$command}; } my %quoted_style_commands = ( 'samp' => 1, ); my %upper_case_style_commands = ( 'sc' => 1, ); my @inline_elements = ('emphasis', 'abbrev', 'acronym', 'link', 'inlinemediaobject', 'firstterm', 'footnote', 'replaceable', 'subscript', 'superscript', 'wordasword'); my %inline_elements; foreach my $inline_element (@inline_elements) { $inline_elements{$inline_element} = 1; }; my %style_attribute_commands; %style_attribute_commands = ( 'b' => 'emphasis role="bold"', 'cite' => 'citetitle', 'code' => 'literal', 'command' => 'command', 'dfn' => 'firstterm', 'emph' => 'emphasis', 'env' => 'envar', 'file' => 'filename', 'footnote' => 'footnote', # brace context command 'headitemfont' => 'emphasis role="bold"', # actually instead of 'i' => 'emphasis', 'indicateurl' => 'literal', 'kbd' => 'userinput', 'key' => 'keycap', 'math' => 'mathphrase', # brace context command 'option' => 'option', 'r' => '', 'samp' => 'literal', 'sansserif' => '', 'strong' => 'emphasis role="bold"', 'sub' => 'subscript', 'sup' => 'superscript', 't' => 'literal', 'var' => 'replaceable', 'verb' => 'literal', # brace other command ); my %style_brace_types = map {$_ => 1} ('style_other', 'style_code', 'style_no_code'); # @all_style_commands is the union of style brace commands, commands # in %style_attribute_commands and a few other, some not style brace commands. # Using keys of a map generated hash does like uniq, it avoids duplicates. # The first grep selects style brace commands, ie commands with %brace_commands # type in %style_brace_types. my @all_style_commands = keys %{{ map { $_ => 1 } ((grep {$style_brace_types{$brace_commands{$_}}} keys(%brace_commands)), keys(%style_attribute_commands), 'w', 'dmn', 'titlefont') }}; # special string for 'w'. my $w_command_mark = ''; my %style_commands_formatting; foreach my $command(@all_style_commands) { $style_commands_formatting{$command} = {}; if ($style_attribute_commands{$command}) { $style_commands_formatting{$command}->{'attribute'} = $style_attribute_commands{$command}; } if ($quoted_style_commands{$command}) { $style_commands_formatting{$command}->{'quote'} = 1; } if ($upper_case_style_commands{$command}) { $style_commands_formatting{$command}->{'upper_case'} = 1; } } my %docbook_line_elements_with_arg_map = ( 'exdent' => 'simpara role="exdent"', 'center' => 'simpara role="center"', ); my %docbook_ignored_nobrace_commands = %Texinfo::Commands::nobrace_commands; foreach my $command ('item', 'headitem', 'tab', keys(%docbook_no_arg_commands_formatting)) { delete $docbook_ignored_nobrace_commands{$command}; } my %docbook_no_warn_empty_commands = %docbook_ignored_nobrace_commands; #foreach my $command () { # $docbook_no_warn_empty_commands{$command} = 1; #} my %docbook_no_warn_empty_types; # do not warn for empty brace commands and bracketed arguments and # for a few containers. foreach my $type ('brace_container', 'bracketed_arg', 'brace_command_context', 'preformatted', 'before_item', 'line_arg', 'block_line_arg', 'preamble_before_content', 'before_node_section') { $docbook_no_warn_empty_types{$type} = 1; } my %docbook_line_commands = %Texinfo::Commands::line_commands; foreach my $command ('itemx') { delete $docbook_line_commands{$command}; } my %docbook_global_commands = ( 'documentlanguage' => 1, ); my %default_args_code_style = %Texinfo::Convert::Converter::default_args_code_style; my %defcommand_name_type = ( 'defline' => 'varname', 'deftypeline' => 'varname', 'defcv' => 'property', 'deffn' => 'function', 'defop' => 'methodname', 'deftp' => 'structname', 'deftypecv' => 'property', 'deftypefn' => 'function', 'deftypeop' => 'methodname', 'deftypevr' => 'varname', 'defvr' => 'varname', ); foreach my $def_alias (keys(%Texinfo::Common::def_aliases)) { my $main_command = $Texinfo::Common::def_aliases{$def_alias}; $defcommand_name_type{$def_alias} = $defcommand_name_type{$main_command}; } my %def_argument_types_docbook = ( 'def_type' => ['returnvalue'], 'def_class' => ['ooclass', 'classname'], # TODO or a simple emphasis? # replaceable is not used here, such that replaceable is only # used if there is an explicit @var{} 'def_arg' => ['emphasis role="arg"'], 'def_typearg' => ['type'], ); my %ignored_block_commands; foreach my $block_command ('copying', 'titlepage', 'documentdescription', 'nodedescriptionblock', 'publication', 'documentinfo') { $ignored_block_commands{$block_command} = 1; } my %ignored_text_types; foreach my $text_type ( 'ignorable_spaces_after_command', 'ignorable_spaces_before_command', 'spaces_after_close_brace', 'spaces_before_paragraph', 'spaces_at_end', ) { $ignored_text_types{$text_type} = 1; } my %ignored_types; foreach my $type ( 'menu_entry_leading_text', 'menu_entry_separator', 'postamble_after_end', 'preamble_before_beginning', 'preamble_before_setfilename', #'arguments_line', ) { $ignored_types{$type} = 1; } my %type_elements = ( 'paragraph' => 'para', 'table_definition' => 'listitem', 'table_entry' => 'varlistentry', 'row' => 'row', 'multitable_head' => 'thead', 'multitable_body' => 'tbody', # Unfortunatly there does not seem to be anything better in DocBook. 'def_item' => 'blockquote', ); # 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, ); my %docbook_preformatted_formats = ( # command 'example' => 'screen', 'smallexample' => 'screen', 'display' => 'literallayout', 'smalldisplay' => 'literallayout', 'lisp' => 'programlisting', 'smalllisp' => 'programlisting', 'format' => 'literallayout', 'smallformat' => 'literallayout', ); my %docbook_preformatted_types = ( # type # Note that menus are ignored, so these are unlikely to happen 'menu_comment' => 'literallayout', 'menu_description' => 'literallayout', ); my %sectioning_commands_done; sub converter_defaults($;$) { return \%defaults; } my $build_tree_for_text_converter; sub converter_initialize($) { my $self = shift; # need to build the Perl tree for the Texinfo::Convert::Tree converter # if the converter does not use C data as XS is not enabled for # conversion, and the tree is not already built to Perl, which # happens if XS is enabled for structuring. $build_tree_for_text_converter = (not Texinfo::XSLoader::XS_convert_enabled() and Texinfo::XSLoader::XS_structuring_enabled()); $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}; } foreach my $conf ('OPEN_QUOTE_SYMBOL', 'CLOSE_QUOTE_SYMBOL') { if (not defined($self->get_conf($conf))) { # override undef set in init file/command line $self->force_conf($conf, ''); } } } sub conversion_initialization($;$) { my $self = shift; my $document = shift; if ($document) { $self->set_document($document); } $self->{'document_context'} = []; _new_document_context($self); $self->{'lang_stack'} = []; $self->{'in_skipped_node_top'} = 0; %sectioning_commands_done = (); } sub conversion_finalization($) { my $self = shift; pop @{$self->{'document_context'}}; } sub convert($$) { my $self = shift; my $document = shift; $self->conversion_initialization($document); my $root = $document->tree(Texinfo::XSLoader::XS_structuring_enabled()); $self->register_document_relations_lists_elements($document); push @{$self->{'lang_stack'}}, ''; my $result = $self->convert_tree($root); $self->conversion_finalization(); return $result; } sub convert_tree($$) { my $self = shift; my $root = shift; if (!defined($root)) { confess("TreeElementReadDocbook::convert_tree: undef root\n"); } if (scalar(@{$self->{'lang_stack'}}) == 0) { push @{$self->{'lang_stack'}}, ''; } my $reader = Texinfo::Reader::new($root); if (!defined($reader)) { confess("TreeElementReadDocbook::convert_tree: undef reader\n"); } return _convert($self, $reader); } sub conversion_output_begin($;$$) { my $self = shift; my $output_file = shift; my $output_filename = shift; if ($self->{'document'}) { $self->register_document_relations_lists_elements($self->{'document'}); } my $encoding = ''; if ($self->get_conf('OUTPUT_ENCODING_NAME') and $self->get_conf('OUTPUT_ENCODING_NAME') ne 'utf-8') { $encoding = " encoding=\"".$self->get_conf('OUTPUT_ENCODING_NAME')."\" "; } my $id; if ($output_file ne '') { # FIXME DocBook 5 id -> xml:id $id = " id=\"".$self->xml_protect_text($output_filename)."\""; } else { $id = ''; } my $lang_attribute; $self->tree_element_set_global_document_commands('preamble', ['documentlanguage']); my $documentlanguage = $self->get_conf('documentlanguage'); if (defined($documentlanguage)) { $lang_attribute = " lang=\"$documentlanguage\""; Texinfo::Convert::Utils::switch_lang_translations($self, $documentlanguage, $self->get_conf('COMMAND_LINE_ENCODING')); push @{$self->{'lang_stack'}}, $documentlanguage; } else { $lang_attribute = ''; # start with an empty string if there is no documentlanguage push @{$self->{'lang_stack'}}, ''; } my $result = "".' ]> '. "\n"; my $legalnotice; my $copying_element = $self->get_global_unique_tree_element('copying'); if ($copying_element) { my $children = $copying_element->get_children(); my $copying_result = $self->convert_tree($self->new_tree_element( {'contents' => $copying_element->get_children()})); if ($copying_result ne '') { $legalnotice = "$copying_result"; } } my $fulltitle_command; foreach my $title_cmdname ('title', 'shorttitlepage') { my $command = $self->get_global_unique_tree_element($title_cmdname); if ($command) { next if (!$command->get_child(0)->children_number()); $fulltitle_command = $command; last; } } if (!defined($fulltitle_command)) { my $command_list = Texinfo::Example::TreeElementConverter::global_commands_information_command_list( $self->{'document'}, 'titlefont'); if ($command_list) { my $command = $command_list->[0]; if ($command->children_number() and $command->get_child(0)->children_number()) { $fulltitle_command = $command; } } } # get informations from the @titlepage. Since the fulltitle is gathered # independently, only author and subtitle are gathered here. my $subtitle_info = ''; my $authors_info = ''; foreach my $information_block_cmdname ('documentinfo', 'titlepage') { my $information_block = $self->get_global_unique_tree_element($information_block_cmdname); if (defined($information_block)) { my $collected_commands = Texinfo::Reader::reader_collect_commands_list( $information_block, ['author', 'subtitle']); my @authors_elements; my $subtitle_text = ''; if (scalar(@{$collected_commands})) { foreach my $element (@{$collected_commands}) { my $cmdname = $element->{'cmdname'}; if ($cmdname eq 'author') { push @authors_elements, $element; } elsif ($cmdname eq 'subtitle') { # concatenate the text of @subtitle as DocBook only allows one. my ($arg, $end_line) = _convert_argument_and_end_line($self, $element); $subtitle_text .= $arg . $end_line } } } if ($subtitle_text ne '') { chomp ($subtitle_text); $subtitle_info = "$subtitle_text\n"; } if (scalar(@authors_elements)) { # using authorgroup and collab is the best, because it doesn't require # knowing people name decomposition. Also it should work for group names. # FIXME dblatex ignores collab/collabname. $authors_info .= "\n"; foreach my $element (@authors_elements) { my ($arg, $end_line) = _convert_argument_and_end_line($self, $element); # FIXME DocBook 5 no more collabname, merged with other elements in # orgname, which is much more specific than collabname, it is for an # organisation and therefore not suitable here. # https://tdg.docbook.org/tdg/5.0/ch01#introduction-whats-new # person/personname is not suitable either, because in Texinfo @author # may correspond to more than one author, and also because we do not # have the information in Texinfo needed for , which requires # a split of the name in honorific, firstname, surname... # https://tdg.docbook.org/tdg/5.0/personname my $result = "$arg$end_line"; chomp ($result); $result .= "\n"; $authors_info .= $result; } $authors_info .= "\n"; } } } my $settitle_command; my $settitle = $self->get_global_unique_tree_element('settitle'); if ($settitle and $settitle->children_number() and $settitle->get_child(0)->children_number()) { $settitle_command = $settitle; } my $top_command = $self->get_global_unique_tree_element('top'); my $titleabbrev_command; if ($fulltitle_command) { $titleabbrev_command = $settitle_command; } elsif ($settitle_command) { $fulltitle_command = $settitle_command; } elsif (defined($legalnotice) and $top_command) { # if there is a legalnotice, we really want to have a title # preceding it, so we also use @top # arguments_line type element my $arguments_line = $top_command->get_child(0); my $line_arg = $arguments_line->get_child(0); if ($line_arg->children_number()) { $fulltitle_command = $top_command; } } my $title_info = ''; if ($fulltitle_command) { foreach my $element_command ([$fulltitle_command, 'title'], [$titleabbrev_command, 'titleabbrev']) { my ($element, $docbook_element) = @$element_command; if (defined($element)) { my ($arg, $end_line) = _convert_argument_and_end_line($self, $element); my $result = "<$docbook_element>$arg$end_line"; chomp ($result); $result .= "\n"; $title_info .= $result; if ($docbook_element eq 'title') { $title_info .= $subtitle_info; } } } } $self->tree_element_set_global_document_commands('before', ['documentlanguage']); Texinfo::Convert::Utils::switch_lang_translations($self, $self->get_conf('documentlanguage'), $self->get_conf('COMMAND_LINE_ENCODING')); my $document_info = ''; $document_info .= $title_info . $authors_info; $document_info .= $legalnotice if (defined($legalnotice)); # we duplicate title info, as it is explicitly said in the DocBook manual # that it can be duplicated if exactly the same $result .= $title_info; if ($document_info ne '') { # FIXME DocBook 5 bookinfo->info $result .= "$document_info\n"; } return $result; } sub conversion_output_end($) { my $self = shift; return "\n"; } sub output($$) { my $self = shift; my $document = shift; return $self->output_tree($document, Texinfo::XSLoader::XS_structuring_enabled()); } my %docbook_sections = ( 'top' => 'chapter', 'part' => 'part', 'chapter' => 'chapter', 'unnumbered' => 'chapter', 'centerchap' => 'chapter', 'appendix' => 'appendix', # chapter would have been the best choice here, but it is not a possibility # for renderas in bridgehead (possibilities are sect1 to 5 and other). # other is rendered small, smaller than sect1. So use sect1, even though # it matches better with the @heading level. 'majorheading' => 'sect1', 'chapheading' => 'sect1', 'heading' => 'sect1', 'subheading' => 'sect2', 'subsubheading' => 'sect3', 2 => 'sect1', 3 => 'sect2', 4 => 'sect3' ); my %docbook_special_unnumbered; foreach my $special_unnumbered ('acknowledgements', 'colophon', 'dedication', 'preface') { $docbook_special_unnumbered{$special_unnumbered} = 1; } # element is not texinfo tree element here, but xml element sub _docbook_section_element($$) { my $self = shift; my $element = shift; my $section_level = $element->get_attribute('section_level'); if (defined($section_level)) { my $heading_level = $section_level; if (exists $docbook_sections{$heading_level}) { return $docbook_sections{$heading_level}; } } my $level_adjusted_cmdname = Texinfo::Example::TreeElementConverter::tree_element_section_level_adjusted_command_name( $element); if ($level_adjusted_cmdname eq 'unnumbered') { my $sections_list = $self->tree_element_sections_list(); if ($sections_list) { my $section_relations = $sections_list->[$element->get_attribute('section_number') -1]; if ($section_relations->{'associated_node'}) { my $associated_node = $section_relations->{'associated_node'}->{'element'}; my $normalized = lc($associated_node->get_attribute('normalized')); if ($docbook_special_unnumbered{$normalized}) { return $normalized; } } } } if (defined($docbook_sections{$level_adjusted_cmdname})) { return $docbook_sections{$level_adjusted_cmdname}; } else { # special case of no structuring information available for a regular # sectioning command, like @section, @appendix, if Structuring # sectioning_structure was not called. my $heading_level = Texinfo::Example::TreeElementConverter::tree_element_section_level($element); return $docbook_sections{$heading_level}; } } sub _begin_index_entry($$) { my $self = shift; my $element = shift; my ($index_entry, $index_info) = $self->get_tree_element_index_entry($element); if ($index_entry) { # FIXME DocBook 5 role->type my $result = "{'index_name'}\">"; _new_document_context($self); $self->{'document_context'}->[-1]->{'monospace'}->[-1] = 1 if ($index_info->{'in_code'}); $result .= ""; return ($result, $index_entry); } return (undef, undef); } sub _end_index_entry($$) { my $self = shift; my $element = shift; pop @{$self->{'document_context'}}; my $result = ""; $result .= ""; return $result; } sub _parse_attribute($) { my $element = shift; return ('', '') if (!defined($element)); my $attributes = ''; if ($element =~ /^(\w+)(\s+.*)/) { $element = $1; $attributes = $2; } return ($element, $attributes); } sub _protect_text($$) { my $self = shift; my $text = shift; my $result = $self->xml_protect_text($text); # form feed not allowed in XML $result =~ s/\f/ /g; return $result; } sub _format_comment($$) { my ($self, $element) = @_; # TODO simplify, use only the text, not the spaces my $command_text = ''; my $spaces_before_argument = $element->get_attribute('spaces_before_argument'); if (defined($spaces_before_argument)) { $command_text .= $spaces_before_argument->{'text'}; } my $line_arg = $element->get_child(0); if (defined($line_arg)) { my $text_element = $line_arg->get_child(0); if (defined($text_element)) { $command_text .= $text_element->{'text'}; } my $spaces_after_argument = $line_arg->get_attribute('spaces_after_argument'); if (defined($spaces_after_argument)) { $command_text .= $spaces_after_argument->{'text'}; } } return $self->xml_comment($command_text); } # NOTE may be called on brace commands such as @titlefont # this is similar to functions used in other converters, but not exactly # the same as we want to start with an element that is already registered # to C, and so we go through the arguments_line in the function # tree_element_argument_comment_end_line overriden for XS. sub _convert_argument_and_end_line($$) { my $self = shift; my $element = shift; my ($line_arg, $comment, $end_line) = $self->tree_element_argument_comment_end_line($element); my $converted = $self->convert_tree($line_arg); if ($comment) { $end_line = _format_comment($self, $comment); } return ($converted, $end_line); } sub _output_anchor($) { my $element = shift; if ($element->get_attribute('is_target')) { # FIXME DocBook 5 id -> xml:id return ''; } else { return ''; } } sub _new_document_context($) { my $self = shift; push (@{$self->{'document_context'}}, { 'monospace' => [0], 'upper_case' => [0], 'no_break' => [0], }); } sub _begin_def_line($$) { my $self = shift; my $element = shift; my $result = ''; my ($index_entry_text, $index_entry) = _begin_index_entry($self, $element); if ($index_entry) { $result .= $index_entry_text; $result .= $self->convert_tree( Texinfo::Example::TreeElementConverter::tree_element_index_content_element($element)); $result .= _end_index_entry($self, $element); } _new_document_context($self); $self->{'document_context'}->[-1]->{'monospace'}->[0] = 1; $self->{'document_context'}->[-1]->{'inline'}++; return $result; } my $debug_global_element_nr = 0; my $TXI_READ_TEXT = Texinfo::Reader::TXI_READ_TEXT; my $TXI_READ_IGNORABLE_TEXT = Texinfo::Reader::TXI_READ_IGNORABLE_TEXT; my $TXI_READ_ELEMENT_START = Texinfo::Reader::TXI_READ_ELEMENT_START; my $TXI_READ_ELEMENT_END = Texinfo::Reader::TXI_READ_ELEMENT_END; sub _convert($$) { my $self = shift; my $reader = shift; my $result = ''; my $void = ''; my $output_ref = \$result; my @format_elements_stack = (); #print STDERR " ---- C\n"; while (1) { my $next = $reader->read(); last if (!defined($next)); my $category = $next->{'category'}; my $element = $next->{'element'}; my $registerd_element = $reader->register_token_element(); if (!defined($element)) { $element = $registerd_element; } my $e_type = $element->{'type'}; #if (1) { #} if (0) { #} $debug_global_element_nr++; my $category_name = $Texinfo::Reader::token_category_name{$category}; print STDERR "element $category_name $debug_global_element_nr"; my $cmdname = $element->{'cmdname'}; print STDERR " cmd: $cmdname," if (defined($cmdname)); print STDERR " type: $e_type" if (defined($e_type)); my $text = $element->{'text'}; if (defined($text)) { my $protected_text = $text; $protected_text =~ s/\n/\\n/; print STDERR " text: $protected_text"; } if (scalar(@format_elements_stack)) { my @strings; foreach my $close_format_elements (@format_elements_stack) { if (scalar(@$close_format_elements)) { push @strings, '['.join(';', @$close_format_elements).']'; } else { push @strings, ''; } } print STDERR ' s: '.join('|', @strings); } if (defined($self->{'pending_prepend'})) { print STDERR ' pp: '.$self->{'pending_prepend'}; } print STDERR "\n"; } if ($category == $TXI_READ_TEXT) { next if (defined($e_type) and $ignored_text_types{$e_type}); my $text = $element->{'text'}; if (defined($e_type) and $e_type eq '_converted') { $$output_ref .= $text; } elsif ($self->{'document_context'}->[-1]->{'raw'}) { $$output_ref .= $text; } else { my $result_text = $text; if ($self->{'document_context'}->[-1]->{'upper_case'}->[-1]) { $result_text = uc($result_text); } if ($self->{'document_context'}->[-1]->{'no_break'}->[-1]) { $result_text =~ s/\n/ /g; $result_text =~ s/ +/$nbsp/g; } $result_text = _protect_text($self, $result_text); if (!(defined($e_type) and $e_type eq 'raw') and !$self->{'document_context'}->[-1]->{'monospace'}->[-1]) { $result_text = $self->xml_format_text_with_numeric_entities($result_text); } #warn "had text `$text', returning $result_text\n"; $$output_ref .= $result_text; } } elsif ($category == $TXI_READ_IGNORABLE_TEXT) { # nothing to do } elsif ($category == $TXI_READ_ELEMENT_START) { #warn " START element\n"; my $cmdname = $element->{'cmdname'}; if (defined($cmdname)) { #warn " START got cmdname $cmdname\n"; my $result_text; if (defined($docbook_no_arg_commands_formatting{$cmdname})) { #warn " has no_arg_commands_formatting \n"; my $command_name; if ($self->{'document_context'}->[-1]->{'upper_case'}->[-1] and $Texinfo::Commands::letter_no_arg_commands{$cmdname} and $Texinfo::Commands::letter_no_arg_commands{uc($cmdname)}) { $command_name = uc($cmdname) } else { $command_name = $cmdname; } my $translated_tree = $self->tree_element_translated_command_tree($command_name); if ($translated_tree) { $result_text = $self->convert_tree($translated_tree); } else { $result_text = $docbook_no_arg_commands_formatting{$command_name}; } } elsif ($cmdname eq 'today') { $result_text = $self->convert_tree( $self->tree_element_expand_today()); } elsif ($Texinfo::Commands::accent_commands{$cmdname}) { $result_text = $self->tree_element_xml_accents($element, $self->{'document_context'}->[-1]->{'upper_case'}->[-1]); } if (defined($result_text)) { $$output_ref .= $result_text; $reader->skip_children($element); #warn " did inline cmdname $cmdname ($result_text)\n"; next; } if ($cmdname eq 'item' or $cmdname eq 'itemx' or $cmdname eq 'headitem' or $cmdname eq 'tab') { push @format_elements_stack, []; my $parent = $element->parent(); my $parent_cmdname = $parent->{'cmdname'}; if ($cmdname eq 'item' and $parent_cmdname and ($parent_cmdname eq 'itemize' or $parent_cmdname eq 'enumerate')) { $$output_ref .= ""; if ($parent_cmdname eq 'itemize') { my $command_as_argument_name; my $prepended_element = Texinfo::Example::TreeElementConverter::tree_element_item_itemize_prepended( $element); if ($prepended_element) { $command_as_argument_name = $prepended_element->{'cmdname'}; } if (!($command_as_argument_name and $command_as_argument_name eq 'bullet')) { $self->{'pending_prepend'} = $self->convert_tree($prepended_element); $self->{'pending_prepend'} .= " "; } } push @{$format_elements_stack[-1]}, 'listitem'; } elsif (($cmdname eq 'item' or $cmdname eq 'itemx') and $element->children_number() and exists($element->get_child(0)->{'type'}) and $element->get_child(0)->{'type'} eq 'line_arg') { my $result_text = ''; $result_text .= "" if ($cmdname eq 'itemx'); my ($index_entry_text, $index_entry) = _begin_index_entry($self, $element); if ($index_entry) { $result_text .= $index_entry_text; # FIXME the $index_element tree refers to part of the # tree. If $index_element and the main tree are built to Perl, # there will be two references to this part of the tree, # and an error because there are too many refcounts. # The two trees may be built to Perl, for example, if # TEXINFO_XS_CONVERT=0 and there is an @image whose argument is # converted to text and needs to be built to Perl, with # build_tree_for_text_converter set. # In 2025, this situation happened in the t/*.t test suite # with TEXINFO_XS_CONVERT=0. my $index_element = Texinfo::Example::TreeElementConverter::tree_element_index_content_element( $element); $result_text .= $self->convert_tree($index_element); $result_text .= _end_index_entry($self, $element); } if ($element->get_child(0)->children_number()) { my $table_item_tree = $self->tree_element_table_item_content_tree($element); $table_item_tree = $element->get_child(0) if (!defined($table_item_tree)); $result_text .= $self->convert_tree($table_item_tree); } chomp ($result_text); $result_text .= "\n"; $result_text .= ""; $$output_ref .= $result_text; $reader->skip_children($element); pop @format_elements_stack; } else { unless (($cmdname eq 'item' or $cmdname eq 'headitem' or $cmdname eq 'tab') and $element->parent()->{'type'} and $element->parent()->{'type'} eq 'row') { warn "BUG: multitable cell command not in a row " .Texinfo::Common::debug_print_element($element, 1); } $$output_ref .= ""; push @{$format_elements_stack[-1]}, 'entry'; } # end *item* tab } elsif ($e_type and $e_type eq 'index_entry_command') { my ($result, $index_entry) = _begin_index_entry($self, $element); if ($index_entry) { $$output_ref .= $result; } else { $reader->skip_children($element); } } elsif ($cmdname eq 'subentry') { # keep the initial output_ref if (!$self->{'document_context'}->[-1]->{'subentry_level'}) { push @{$self->{'document_context'}->[-1]->{'subentry_output'}}, $output_ref; } my $subentry_output = ''; my $level; if (scalar(@{$self->{'document_context'}->[-1] ->{'subentry_output'}}) == 1) { $level = 'secondary'; } else { $level = 'tertiary'; } $subentry_output .= "<$level>"; $self->{'document_context'}->[-1]->{'subentry_level'}++; # redirect output_ref to be able to output subentries without # nesting them. $output_ref = \$subentry_output; push @{$self->{'document_context'}->[-1]->{'subentry_output'}}, $output_ref; } elsif (exists($docbook_line_commands{$cmdname})) { #warn " is dbk line command\n"; if ($docbook_global_commands{$cmdname}) { Texinfo::Example::TreeElementConverter::tree_element_set_informative_command_value( $self, $element); if ($cmdname eq 'documentlanguage') { Texinfo::Convert::Utils::switch_lang_translations($self, $self->get_conf('documentlanguage'), $self->get_conf('COMMAND_LINE_ENCODING')); } } elsif ($Texinfo::Commands::root_commands{$cmdname}) { my $section_relations; if ($cmdname ne 'node') { my $sections_list = $self->tree_element_sections_list(); if ($sections_list) { $section_relations = $sections_list->[$element->get_attribute('section_number') -1]; } } if ($self->get_conf('NO_TOP_NODE_OUTPUT')) { my $node_element; if ($cmdname eq 'node') { $node_element = $element; } elsif ($section_relations and $section_relations->{'part_following_node'}) { # the section is necessarily a part $node_element = $section_relations->{'part_following_node'}->{'element'}; } if ($node_element or $cmdname eq 'part') { # normalized not defined happens for empty nodes if ($node_element and $node_element->get_attribute('normalized') and $node_element->get_attribute('normalized') eq 'Top') { $self->{'in_skipped_node_top'} = 1; $output_ref = \$void; } elsif (defined($self->{'in_skipped_node_top'}) and $self->{'in_skipped_node_top'} == 1) { $self->{'in_skipped_node_top'} = -1; $output_ref = \$result; } } } my $anchor; my $node_relations; if ($cmdname eq 'node') { my $node_number = $element->get_attribute('node_number'); if ($node_number) { my $nodes_list = $self->tree_element_nodes_list(); $node_relations = $nodes_list->[$node_number -1]; if (not $node_relations->{'associated_section'}) { $anchor = _output_anchor($element); $$output_ref .= $anchor . "\n" if ($anchor ne ''); } } } if (!defined($anchor)) { # start the section at the associated node or part, or at the # sectioning command if there is no associated node nor part my $opening_section_relations; my $part; if ($cmdname eq 'node') { if ($node_relations) { $opening_section_relations = $node_relations->{'associated_section'}; } } elsif ($cmdname eq 'part') { $part = $element; if ($section_relations and $section_relations->{'part_associated_section'}) { $opening_section_relations = $section_relations->{'part_associated_section'}; } } else { $opening_section_relations = $section_relations; } # FIXME add !$part in condition? my $section_element; if ($opening_section_relations) { if ($opening_section_relations->{'associated_part'}) { $part = $opening_section_relations->{'associated_part'}->{'element'}; } $section_element = $opening_section_relations->{'element'}; } my @opened_elements; # we need to check if the section was already done in case there is # both a node and a part, we do not know in which order they appear. if (not ($section_element and $sectioning_commands_done{$section_element})) { push @opened_elements, $part if $part; if ($section_element) { push @opened_elements, $section_element; } } foreach my $opened_element (@opened_elements) { if ($section_element and $opened_element eq $section_element) { $sectioning_commands_done{$section_element} = 1; } my $section_attribute = ''; # It is not clear if a label should be set for @appendix* and # @chapter/@*section or not, as the formatter should be # able to figure it out. For @unnumbered or if ! NUMBER_SECTIONS # having a label (empty) is important. my $label = ''; my $section_heading_number = $opened_element->get_attribute('section_heading_number'); if (defined($section_heading_number) and ($self->get_conf('NUMBER_SECTIONS') or !defined($self->get_conf('NUMBER_SECTIONS')))) { # Looking at docbook2html output, Appendix is appended in the # section title, so only the letter is used. $label = $section_heading_number; } my $docbook_sectioning_element = _docbook_section_element($self, $opened_element); if (! $docbook_special_unnumbered{$docbook_sectioning_element}) { $section_attribute .= " label=\"$label\""; } my $section_relations; my $sections_list = $self->tree_element_sections_list(); if ($sections_list) { $section_relations = $sections_list->[$opened_element->get_attribute('section_number') -1]; if ($section_relations->{'associated_node'}) { # FIXME DocBook 5 id -> xml:id $section_attribute .= " id=\"".$section_relations->{'associated_node'}->{'element'}->get_attribute('normalized')."\""; } } my $language = ''; my $documentlanguage = $self->get_conf('documentlanguage'); if (defined($documentlanguage)) { $language = $documentlanguage; if ($self->{'lang_stack'}->[-1] ne $language) { $section_attribute .= ' lang="'.$language.'"'; } } push @{$self->{'lang_stack'}}, $language; my $result_text = ''; $result_text .= "<$docbook_sectioning_element${section_attribute}>\n"; my ($arg, $end_line) = _convert_argument_and_end_line($self, $opened_element); $result_text .= "$arg$end_line"; chomp ($result_text); $result_text .= "\n"; $$output_ref .= $result_text; # if associated with a sectioning element, the part is opened # before the sectioning element, such that the part content # appears after the sectioning command opening, no need for # partintro. if ($docbook_sectioning_element eq 'part' and not ($section_relations and $section_relations->{'part_associated_section'}) and !Texinfo::Example::TreeElementConverter::tree_element_is_content_empty( $opened_element)) { $$output_ref .= "\n"; } } } } elsif ($cmdname eq 'c' or $cmdname eq 'comment') { $$output_ref .= _format_comment($self, $element); } elsif ($Texinfo::Commands::sectioning_heading_commands{$cmdname}) { if (!$Texinfo::Commands::root_commands{$cmdname}) { my ($arg, $end_line) = _convert_argument_and_end_line($self, $element); $result_text .= "" ."$arg$end_line"; chomp ($result_text); $result_text .= "\n"; $$output_ref .= $result_text; } } elsif ($Texinfo::Commands::def_commands{$cmdname}) { my $def_line_result = _begin_def_line($self, $element); $$output_ref .= $def_line_result if (defined($def_line_result)); } elsif (exists($docbook_line_elements_with_arg_map{$cmdname})) { my ($docbook_element, $attribute_text) = _parse_attribute($docbook_line_elements_with_arg_map{$cmdname}); my ($arg, $end_line) = _convert_argument_and_end_line($self, $element); if ($docbook_element eq '') { $result_text .= "$arg$end_line"; } else { $result_text .= "<$docbook_element${attribute_text}>" ."$arg$end_line"; } chomp ($result_text); $result_text .= "\n"; $$output_ref .= $result_text; } elsif ($cmdname eq 'insertcopying') { my $copying = $self->get_global_unique_tree_element('copying'); if ($copying) { $$output_ref .= $self->convert_tree($self->new_tree_element( {'contents' => $copying->get_children()})); } } elsif ($cmdname eq 'verbatiminclude') { my $expansion = $self->tree_element_expand_verbatiminclude($element); if (defined($expansion)) { $$output_ref .= $self->convert_tree($expansion); } } elsif ($cmdname eq 'printindex') { my $misc_args = $element->get_attribute('misc_args'); if ($misc_args) { # FIXME DocBook 5 #return "[0]\">\n"; $$output_ref .= "[0]\">" ."\n"; } else { $$output_ref .= "\n"; } } if (!$Texinfo::Commands::root_commands{$cmdname} and !$Texinfo::Commands::def_commands{$cmdname}) { $reader->skip_children($element); } # ignore all the other line commands } elsif ($e_type and $e_type eq 'definfoenclose_command') { } elsif (exists($Texinfo::Commands::brace_commands{$cmdname})) { #Texinfo::Common::debug_list(" brace command with args", $element->{'contents'}); if ($style_commands_formatting{$cmdname}) { my $formatting = $style_commands_formatting{$cmdname}; my $in_monospace_not_normal; if ((defined($default_args_code_style{$cmdname}) and $default_args_code_style{$cmdname}->[0]) or $Texinfo::Commands::math_commands{$cmdname}) { $in_monospace_not_normal = 1; } elsif ($brace_commands{$cmdname} and $brace_commands{$cmdname} eq 'style_no_code') { $in_monospace_not_normal = 0; } my ($style, $attribute_text) = _parse_attribute($formatting->{'attribute'}); if ($Texinfo::Commands::brace_commands{$cmdname} eq 'context') { _new_document_context($self); } if ($formatting->{'upper_case'}) { push @{$self->{'document_context'}->[-1]->{'upper_case'}}, 1; } if ($cmdname eq 'w') { push @{$self->{'document_context'}->[-1]->{'no_break'}}, 1; } push @{$self->{'document_context'}->[-1]->{'monospace'}}, $in_monospace_not_normal if (defined($in_monospace_not_normal)); if (defined($formatting->{'quote'})) { $$output_ref .= $self->get_conf('OPEN_QUOTE_SYMBOL'); } if ($cmdname eq 'math') { $$output_ref .= ''; } if ($style ne '' and (!$self->{'document_context'}->[-1]->{'inline'} or $inline_elements{$style})) { $$output_ref .= "<$style${attribute_text}>"; } } elsif ($cmdname eq 'anchor' or $cmdname eq 'namedanchor') { $$output_ref .= _output_anchor($element); $reader->skip_children($element); } elsif ($Texinfo::Commands::ref_commands{$cmdname}) { my $args_nr = $element->children_number(); my $command_name; my $book_element; my ($section_name, $node_name); my $manual_file_index = 3; if ($cmdname eq 'inforef') { $manual_file_index = 2; $command_name = 'xref'; } elsif ($cmdname eq 'link') { $manual_file_index = 2; $command_name = 'ref'; } else { if ($args_nr >= 5) { my $book_arg = $element->get_child(4); if ($book_arg->children_number()) { $book_element = $book_arg; } } if ($args_nr >= 3) { my $section_arg = $element->get_child(2); if ($section_arg->children_number()) { $section_name = $self->convert_tree($section_arg); } } $command_name = $cmdname; } my $manual_file_element; if ($args_nr >= $manual_file_index+1) { my $manual_file_arg = $element->get_child($manual_file_index); if ($manual_file_arg->children_number()) { $manual_file_element = $manual_file_arg; } } if (! defined($section_name) and $args_nr >= 2 and $element->get_child(1)->children_number()) { my $section_arg = $element->get_child(1); $section_name = $self->convert_tree($section_arg); } elsif ($element->get_child(0)->children_number()) { my $node_arg = $element->get_child(0); push @{$self->{'document_context'}->[-1]->{'upper_case'}}, 0; $node_name = $self->convert_tree($node_arg); pop @{$self->{'document_context'}->[-1]->{'upper_case'}}; if (($book_element or $manual_file_element) and $node_name eq 'Top') { $node_name = undef; } } push @{$self->{'document_context'}->[-1]->{'upper_case'}}, 0; # external book ref if ($book_element) { if ($section_name) { my $substituted_strings = { 'section_name' => $self->new_tree_element({'type' => '_converted', 'text' => $section_name}), 'book' => $book_element }; if ($command_name eq 'ref') { $$output_ref .= $self->convert_tree( $self->tree_element_cdt( 'section ``{section_name}\'\' in @cite{{book}}', $substituted_strings)); } elsif ($command_name eq 'xref') { $$output_ref .= $self->convert_tree( $self->tree_element_cdt( 'See section ``{section_name}\'\' in @cite{{book}}', $substituted_strings)); } elsif ($command_name eq 'pxref') { $$output_ref .= $self->convert_tree( $self->tree_element_cdt( 'see section ``{section_name}\'\' in @cite{{book}}', $substituted_strings)); } } elsif ($node_name) { my $substituted_strings = { 'node_name' => $self->new_tree_element({'type' => '_converted', 'text' => $node_name}), 'book' => $book_element }; if ($command_name eq 'ref') { $$output_ref .= $self->convert_tree( $self->tree_element_cdt('``{node_name}\'\' in @cite{{book}}', $substituted_strings)); } elsif ($command_name eq 'xref') { $$output_ref .= $self->convert_tree( $self->tree_element_cdt('See ``{node_name}\'\' in @cite{{book}}', $substituted_strings)); } elsif ($command_name eq 'pxref') { $$output_ref .= $self->convert_tree( $self->tree_element_cdt('see ``{node_name}\'\' in @cite{{book}}', $substituted_strings)); } } else { if ($command_name eq 'ref') { $$output_ref .= $self->convert_tree( $self->tree_element_cdt('@cite{{book}}', {'book' => $book_element })); } elsif ($command_name eq 'xref') { $$output_ref .= $self->convert_tree( $self->tree_element_cdt('See @cite{{book}}', {'book' => $book_element })); } elsif ($command_name eq 'pxref') { $$output_ref .= $self->convert_tree( $self->tree_element_cdt('see @cite{{book}}', {'book' => $book_element })); } } } elsif ($manual_file_element) { if ($section_name) { my $substituted_strings = { 'section_name' => $self->new_tree_element({'type' => '_converted', 'text' => $section_name}), 'manual' => $manual_file_element }; if ($command_name eq 'ref') { $$output_ref .= $self->convert_tree( $self->tree_element_cdt( 'section ``{section_name}\'\' in @file{{manual}}', $substituted_strings)); } elsif ($command_name eq 'xref') { $$output_ref .= $self->convert_tree( $self->tree_element_cdt( 'See section ``{section_name}\'\' in @file{{manual}}', $substituted_strings)); } elsif ($command_name eq 'pxref') { $$output_ref .= $self->convert_tree( $self->tree_element_cdt( 'see section ``{section_name}\'\' in @file{{manual}}', $substituted_strings)); } } elsif ($node_name) { my $substituted_strings = { 'node_name' => $self->new_tree_element({'type' => '_converted', 'text' => $node_name}), 'manual' => $manual_file_element }; if ($command_name eq 'ref') { $$output_ref .= $self->convert_tree( $self->tree_element_cdt('``{node_name}\'\' in @file{{manual}}', $substituted_strings)); } elsif ($command_name eq 'xref') { $$output_ref .= $self->convert_tree( $self->tree_element_cdt( 'See ``{node_name}\'\' in @file{{manual}}', $substituted_strings)); } elsif ($command_name eq 'pxref') { $$output_ref .= $self->convert_tree( $self->tree_element_cdt( 'see ``{node_name}\'\' in @file{{manual}}', $substituted_strings)); } } else { if ($command_name eq 'ref') { $$output_ref .= $self->convert_tree( $self->tree_element_cdt('@file{{manual}}', {'manual' => $manual_file_element })); } elsif ($command_name eq 'xref') { $$output_ref .= $self->convert_tree( $self->tree_element_cdt('See @file{{manual}}', {'manual' => $manual_file_element })); } elsif ($command_name eq 'pxref') { $$output_ref .= $self->convert_tree( $self->tree_element_cdt('see @file{{manual}}', {'manual' => $manual_file_element })); } } } elsif ($cmdname eq 'inforef') { } else { my $linkend = ''; my $node_arg = $element->get_child(0); if ($node_arg and defined($node_arg->get_attribute('normalized')) and !$node_arg->get_attribute('manual_content')) { $linkend = ' linkend="'. $node_arg->get_attribute('normalized').'"'; } my $link_text = $section_name; $link_text = $node_name if (! defined($link_text)); my $argument = "".$link_text.""; if ($cmdname eq 'ref' or $cmdname eq 'link') { $$output_ref .= $self->convert_tree( $self->tree_element_cdt('{title_ref}', {'title_ref' => $self->new_tree_element({'type' => '_converted', 'text' => $argument})})); } elsif ($cmdname eq 'xref') { $$output_ref .= $self->convert_tree( $self->tree_element_cdt('See {title_ref}', {'title_ref' => $self->new_tree_element({'type' => '_converted', 'text' => $argument})})); } elsif ($cmdname eq 'pxref') { $$output_ref .= $self->convert_tree( $self->tree_element_cdt('see {title_ref}', {'title_ref' => $self->new_tree_element({'type' => '_converted', 'text' => $argument})})); } } pop @{$self->{'document_context'}->[-1]->{'upper_case'}}; $reader->skip_children($element); } elsif ($cmdname eq 'image') { my $argument = $element->get_child(0); if ($argument and $argument->children_number()) { Texinfo::Convert::Text::set_options_code( $self->{'convert_text_options'}); Texinfo::Convert::Text::set_options_encoding_if_not_ascii($self, $self->{'convert_text_options'}); Texinfo::Example::TreeElementConverter::build_element_tree( $argument) if ($build_tree_for_text_converter); my $basefile = Texinfo::Convert::Text::convert_to_text( $argument, $self->{'convert_text_options'}); Texinfo::Convert::Text::reset_options_code( $self->{'convert_text_options'}); Texinfo::Convert::Text::reset_options_encoding( $self->{'convert_text_options'}); my $is_inline = Texinfo::Example::TreeElementConverter::tree_element_element_is_inline( $element); if ($is_inline) { $$output_ref .= ""; } else { $$output_ref .= ""; } my @files; foreach my $extension (@docbook_image_extensions) { my ($file_name, $file_name_encoding) = $self->encoded_input_file_name("$basefile.$extension"); if (Texinfo::Common::locate_include_file($file_name, $self->get_conf('INCLUDE_DIRECTORIES'))) { push @files, ["$basefile.$extension", uc($extension)]; } } my $image_file_found = scalar(@files);; if (!$image_file_found) { push @files, ["$basefile.jpg", 'JPG']; } foreach my $file (@files) { $$output_ref .= "xml_protect_text($file->[0]) ."\" format=\"$file->[1]\">"; } my ($image_text, $image_width) = $self->tree_element_txt_image_text($element, $basefile); if (defined($image_text)) { # remove last end of line chomp($image_text); $$output_ref .= "" ._protect_text($self, $image_text) .''; } if (!defined($image_text) and !$image_file_found) { $self->converter_line_warn(sprintf( __("\@image file `%s' not found, using `%s'"), $basefile, "$basefile.jpg"), $element->source_info()); } if ($is_inline) { $$output_ref .= ""; } else { $$output_ref .= ""; } } $reader->skip_children($element); } elsif ($cmdname eq 'email') { my $name; my $email; my $args_nr = $element->children_number(); if ($args_nr) { my $email_arg = $element->get_child(0); if ($email_arg->children_number()) { $email = $email_arg; } } my $email_text; if ($args_nr >= 2) { my $name_arg = $element->get_child(1); if ($name_arg->children_number()) { $name = $name_arg; } } if ($email) { Texinfo::Convert::Text::set_options_code( $self->{'convert_text_options'}); Texinfo::Convert::Text::set_options_encoding_if_not_ascii($self, $self->{'convert_text_options'}); Texinfo::Example::TreeElementConverter::build_element_tree( $email) if ($build_tree_for_text_converter); $email_text = _protect_text($self, Texinfo::Convert::Text::convert_to_text( $email, $self->{'convert_text_options'})); Texinfo::Convert::Text::reset_options_code( $self->{'convert_text_options'}); Texinfo::Convert::Text::reset_options_encoding( $self->{'convert_text_options'}); } if ($name and $email) { # FIXME DocBook 5 ulink -> link # There is no possibility to do something similar in DocBook 5. # The best is probably either to forget about the name, or # follow by the name in parentheses my $link_name = $self->convert_tree($name); $$output_ref .= "$link_name"; } elsif ($email) { $$output_ref .= "$email_text"; } elsif ($name) { $$output_ref .= $self->convert_tree($name); } $reader->skip_children($element); } elsif ($cmdname eq 'uref' or $cmdname eq 'url') { my $args_nr = $element->children_number(); my $url_text; my $url_arg = $element->get_child(0); if ($url_arg->children_number()) { Texinfo::Convert::Text::set_options_code( $self->{'convert_text_options'}); Texinfo::Convert::Text::set_options_encoding_if_not_ascii($self, $self->{'convert_text_options'}); Texinfo::Example::TreeElementConverter::build_element_tree( $url_arg) if ($build_tree_for_text_converter); $url_text = _protect_text($self, Texinfo::Convert::Text::convert_to_text($url_arg, $self->{'convert_text_options'})); Texinfo::Convert::Text::reset_options_code( $self->{'convert_text_options'}); Texinfo::Convert::Text::reset_options_encoding( $self->{'convert_text_options'}); } else { $url_text = ''; } my $replacement; if ($args_nr >= 2) { my $replacement_arg = $element->get_child(1); if ($replacement_arg->children_number()) { $replacement = $self->convert_tree($replacement_arg); } } if (!defined($replacement) or $replacement eq '') { if ($args_nr >= 3) { my $replacement_arg = $element->get_child(2); if ($replacement_arg->children_number()) { $replacement = $self->convert_tree($replacement_arg); } } } if (!defined($replacement) or $replacement eq '') { $replacement = $url_text; } $$output_ref .= "$replacement"; $reader->skip_children($element); # FIXME DocBook 5 # need to add in the preamble # xmlns:xlink="http://www.w3.org/1999/xlink" # return "$replacement"; } elsif ($cmdname eq 'abbr' or $cmdname eq 'acronym') { my $args_nr = $element->children_number(); if ($args_nr) { my $argument; my $arg_element = $element->get_child(0); if ($arg_element->children_number()) { my $arg_text = $self->convert_tree($arg_element); if ($arg_text ne '') { my $format_element; if ($cmdname eq 'abbr') { $format_element = 'abbrev'; } else { $format_element = $cmdname; } $argument = "<$format_element>$arg_text"; } } my $explanation_e; if ($args_nr >= 2) { $explanation_e = $element->get_child(1); } if ($explanation_e and $explanation_e->children_number()) { if (defined($argument)) { my $tree = $self->tree_element_cdt('{abbr_or_acronym} ({explanation})', {'abbr_or_acronym' => $self->new_tree_element({'type' => '_converted', 'text' => $argument}), 'explanation' => $explanation_e}); $$output_ref .= $self->convert_tree($tree); } else { $$output_ref .= $self->convert_tree($explanation_e); } } elsif (defined($argument)) { $$output_ref .= $argument; } } $reader->skip_children($element); } elsif ($cmdname eq 'U') { if ($element->children_number()) { my $arg = $element->get_child(0); if ($arg->children_number()) { my $arg_text = $arg->get_child(0)->{'text'}; if (defined($arg_text)) { $$output_ref .= "&#x$arg_text;"; } } } $reader->skip_children($element); } elsif ($Texinfo::Commands::brace_commands{$cmdname} eq 'inline') { my $expand = 0; if ($Texinfo::Commands::inline_format_commands{$cmdname}) { if ($cmdname eq 'inlinefmtifelse' or ($element->get_attribute('format') and $self->{'expanded_formats'} ->{$element->get_attribute('format')})) { $expand = 1; } } elsif (defined($element->get_attribute('expand_index'))) { $expand = 1; } if ($expand) { my $arg_index = 1; if ($cmdname eq 'inlineraw') { _new_document_context($self); $self->{'document_context'}->[-1]->{'raw'} = 1; } elsif ($cmdname eq 'inlinefmtifelse' and ! $self->{'expanded_formats'} ->{$element->get_attribute('format')}) { $arg_index = 2; } if ($element->children_number() > $arg_index) { my $converted_arg = $element->get_child($arg_index); if ($converted_arg->children_number()) { $$output_ref .= $self->convert_tree($converted_arg); } } if ($cmdname eq 'inlineraw') { pop @{$self->{'document_context'}}; } } #warn " returning after braced cmd result\n"; $reader->skip_children($element); } elsif ($cmdname eq 'seeentry' or $cmdname eq 'seealso') { # gather the text to output it when the index entry closes # and not where the command appears if (!$self->{'document_context'}->[-1]->{$cmdname.'_info'}) { my $command_text = ''; $self->{'document_context'}->[-1]->{$cmdname.'_info'} = [\$command_text, $output_ref]; $output_ref = \$command_text; } else { $reader->skip_children($element); } } else { # ignored brace command #warn " returning empty string for ignored braced cmd\n"; $reader->skip_children($element); } } elsif (exists($Texinfo::Commands::block_commands{$cmdname})) { if ($ignored_block_commands{$cmdname} or ($Texinfo::Commands::block_commands{$cmdname} eq 'raw' and $cmdname ne 'verbatim') or $Texinfo::Commands::block_commands{$cmdname} eq 'menu' or ($Texinfo::Commands::block_commands{$cmdname} eq 'format_raw' and !$self->{'expanded_formats'}->{$cmdname})) { $reader->skip_children($element); next; } push @format_elements_stack, []; if ($self->{'context_block_commands'}->{$cmdname}) { _new_document_context($self); } my @attributes; my $appended = ''; my @format_elements; if (exists($docbook_preformatted_formats{$cmdname})) { push @{$self->{'document_context'}->[-1]->{'preformatted_stack'}}, $docbook_preformatted_formats{$cmdname}; } elsif ($cmdname eq 'enumerate') { push @format_elements, 'orderedlist'; my $numeration; my $enumerate_specification; my $arguments_line = $element->get_child(0); my $block_line_arg = $arguments_line->get_child(0); my $text_element = $block_line_arg->get_child(0); if (defined($text_element)) { $enumerate_specification = $text_element->{'text'}; } if (defined($enumerate_specification)) { if ($enumerate_specification =~ /^[A-Z]/) { $numeration = 'upperalpha'; } elsif ($enumerate_specification =~ /^[a-z]/) { $numeration = 'loweralpha'; } else { $numeration = 'arabic'; } } else { $numeration = 'arabic'; } push @attributes, " numeration=\"$numeration\""; } elsif ($Texinfo::Commands::block_commands{$cmdname} eq 'item_line') { push @format_elements, 'variablelist'; } elsif ($cmdname eq 'itemize') { push @format_elements, 'itemizedlist'; #push @attributes, " mark=\"\""; } elsif ($cmdname eq 'multitable') { push @format_elements, "informaltable"; push @attributes, ''; my $columns_count = $element->get_attribute('max_columns'); $columns_count = 0 if (!defined($columns_count)); push @format_elements, 'tgroup'; push @attributes, " cols=\"$columns_count\""; my @fractions; my $multiply; my $columnfractions = Texinfo::Example::TreeElementConverter::tree_element_multitable_columnfractions( $element); if ($columnfractions) { my $misc_args = $columnfractions->get_attribute('misc_args'); if ($misc_args) { @fractions = @{$misc_args}; $multiply = 100; } } else { # @multitable line arguments_line type element my $arguments_line = $element->get_child(0); my $block_line_arg = $arguments_line->get_child(0); my $contents = $block_line_arg->get_children(); if ($contents) { $multiply = 1; foreach my $content (@$contents) { if ($content->{'type'} and $content->{'type'} eq 'bracketed_arg') { my $prototype_text = ''; if ($content->children_number()) { Texinfo::Convert::Text::set_options_encoding_if_not_ascii( $self, $self->{'convert_text_options'}); Texinfo::Example::TreeElementConverter::build_element_tree( $content) if ($build_tree_for_text_converter); $prototype_text = Texinfo::Convert::Text::convert_to_text( $content, $self->{'convert_text_options'}); Texinfo::Convert::Text::reset_options_encoding( $self->{'convert_text_options'}); } push @fractions, Texinfo::Convert::Unicode::string_width($prototype_text); } } } } foreach my $fraction (@fractions) { $appended .= ''; } } elsif ($cmdname eq 'float') { my $anchor = _output_anchor($element); $$output_ref .= $anchor . "\n" if ($anchor ne ''); } elsif ($cmdname eq 'verbatim') { push @format_elements, 'screen'; } elsif ($cmdname eq 'displaymath') { push @format_elements, 'informalequation'; push @format_elements, 'mathphrase'; } elsif ($cmdname eq 'quotation' or $cmdname eq 'smallquotation') { my $quotation_authors = []; Texinfo::Example::TreeElementConverter::tree_element_find_element_authors( $element, $quotation_authors); foreach my $author (@$quotation_authors) { my $arg = $author->get_child(0); if ($arg->children_number()) { $appended .= ''. $self->convert_tree($arg) ."\n"; } } my $format_element; # arguments_line type element my $arguments_line = $element->get_child(0); my $block_line_arg = $arguments_line->get_child(0); if ($block_line_arg->children_number()) { Texinfo::Example::TreeElementConverter::build_element_tree( $block_line_arg) if ($build_tree_for_text_converter); my $quotation_arg_text = Texinfo::Convert::Text::convert_to_text($block_line_arg, $self->{'convert_text_options'}); if ($docbook_special_quotations{lc($quotation_arg_text)}) { $format_element = lc($quotation_arg_text); } else { # FIXME this is needed because build_tree removes the # C data handler $block_line_arg = $arguments_line->get_child(0); $self->{'pending_prepend'} = $self->convert_tree( $self->tree_element_cdt('@b{{quotation_arg}:} ', {'quotation_arg' => $block_line_arg})); } } $format_element = 'blockquote' if (!defined($format_element)); push @format_elements, $format_element; } elsif ($cmdname eq 'cartouche') { push @format_elements, 'sidebar'; } elsif ($Texinfo::Commands::block_commands{$cmdname} eq 'format_raw') { # the document_context was opened for the command, so this is # forgotten once all the raw internal text has been formatted # when the document_context is closed $self->{'document_context'}->[-1]->{'raw'} = 1; } foreach my $format_element (@format_elements) { my $attribute = shift @attributes; $attribute = '' if (!defined($attribute)); $$output_ref .= "<$format_element${attribute}>"; unshift @{$format_elements_stack[-1]}, $format_element; } $$output_ref .= $appended if (defined($appended)); if ($cmdname eq 'cartouche') { # arguments_line type element my $arguments_line = $element->get_child(0); my $block_line_arg = $arguments_line->get_child(0); if ($block_line_arg->children_number()) { # Keep the previous output reference and # accumulate in a separate text to be able to add the opening # element only if not empty my $title_text = ''; $self->{'document_context'}->[-1]->{'cartouche_title'} = [$output_ref, \$title_text]; $output_ref = \$title_text; } } elsif ($Texinfo::Commands::preformatted_code_commands{$cmdname} or $Texinfo::Commands::math_commands{$cmdname}) { push @{$self->{'document_context'}->[-1]->{'monospace'}}, 1; } } #warn " end of cmdname\n"; } elsif ($e_type) { #warn " START have type $e_type\n"; if (defined($type_elements{$e_type})) { $$output_ref .= "<$type_elements{$e_type}>"; } elsif ($e_type eq 'preformatted') { $$output_ref .= "<$self->{'document_context'}->[-1]->{'preformatted_stack'}->[-1]>"; $self->{'document_context'}->[-1]->{'in_preformatted'} = 1; } elsif ($e_type eq 'def_line') { my $def_line_result = _begin_def_line($self, $element); $$output_ref .= $def_line_result if (defined($def_line_result)); } elsif ($e_type eq 'table_term') { # should be closed by the @item. Allows to have the index entries in # term, which is better than out. $$output_ref .= ""; } elsif ($e_type eq 'before_node_section') { if (!$self->get_conf('_DOCBOOK_PIECE')) { # ignore text before the first @node or sectioning command # as DocBook does not allow content not within some semantic # markup, unless _DOCBOOK_PIECE is set to mean that a the output is not # a full book. $output_ref = \$void; } } elsif ($e_type eq 'def_category') { $$output_ref .= ""; } elsif ($e_type eq 'def_name') { my $def_command = $element->parent()->parent()->get_attribute('def_command'); $$output_ref .= "<$defcommand_name_type{$def_command}>"; } elsif ($def_argument_types_docbook{$e_type}) { foreach my $element_attribute ( @{$def_argument_types_docbook{$e_type}}) { my ($element, $attribute_text) = _parse_attribute($element_attribute); $$output_ref .= "<$element${attribute_text}>"; } } elsif (exists($docbook_preformatted_types{$e_type})) { push @{$self->{'document_context'}->[-1]->{'preformatted_stack'}}, $docbook_preformatted_types{$e_type}; } elsif ($ignored_types{$e_type} or ($e_type eq 'arguments_line' and not ( $self->{'document_context'}->[-1]->{'cartouche_title'}))) { $reader->skip_children($element); } # not restricted enough, includes line_args, for instance #and tree_element_element_is_inline($element, 1)) if (defined($self->{'pending_prepend'}) and ($e_type eq 'paragraph' or $e_type eq 'preformatted')) { $$output_ref .= $self->{'pending_prepend'}; delete $self->{'pending_prepend'}; } } #warn " end of START\n"; } elsif ($category == $TXI_READ_ELEMENT_END) { my $cmdname = $element->{'cmdname'}; if (defined($cmdname)) { if ($style_commands_formatting{$cmdname} and !($e_type and $e_type eq 'definfoenclose_command')) { my $formatting = $style_commands_formatting{$cmdname}; 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; } my ($style, $attribute_text) = _parse_attribute($formatting->{'attribute'}); if ($style ne '' and (!$self->{'document_context'}->[-1]->{'inline'} or $inline_elements{$style})) { $$output_ref .= ""; } if ($cmdname eq 'math') { $$output_ref .= ''; } if (defined($formatting->{'quote'})) { $$output_ref .= $self->get_conf('CLOSE_QUOTE_SYMBOL'); } if (defined($formatting->{'upper_case'})) { pop @{$self->{'document_context'}->[-1]->{'upper_case'}}; } if ($cmdname eq 'w') { pop @{$self->{'document_context'}->[-1]->{'no_break'}}; } pop @{$self->{'document_context'}->[-1]->{'monospace'}} if (defined($in_monospace_not_normal) or $Texinfo::Commands::math_commands{$cmdname}); if ($Texinfo::Commands::brace_commands{$cmdname} eq 'context') { pop @{$self->{'document_context'}}; } if ($cmdname eq 'w') { $$output_ref .= $w_command_mark; } } elsif ($cmdname eq 'item' or $cmdname eq 'itemx' or $cmdname eq 'headitem' or $cmdname eq 'tab') { my $close_format_elements = pop @format_elements_stack; foreach my $format_element (@$close_format_elements) { $$output_ref .= ""; } } elsif (exists($Texinfo::Commands::block_commands{$cmdname})) { my $close_format_elements = pop @format_elements_stack; foreach my $format_element (@$close_format_elements) { $$output_ref .= ""; } if ($Texinfo::Commands::preformatted_code_commands{$cmdname} or $Texinfo::Commands::math_commands{$cmdname}) { pop @{$self->{'document_context'}->[-1]->{'monospace'}}; } # a pending_prepend still there may happen if a quotation is empty. delete $self->{'pending_prepend'}; if (!$self->{'document_context'}->[-1]->{'raw'} and exists($docbook_preformatted_formats{$cmdname})) { my $format = pop @{$self->{'document_context'}->[-1]->{'preformatted_stack'}}; die "BUG $format ne $docbook_preformatted_formats{$cmdname}" if ($format ne $docbook_preformatted_formats{$cmdname}); } if ($self->{'context_block_commands'}->{$cmdname}) { pop @{$self->{'document_context'}}; } # close sectioning command } elsif ($cmdname ne 'node' and $Texinfo::Commands::root_commands{$cmdname}) { my $sections_list = $self->tree_element_sections_list(); my $section_relations = $sections_list->[$element->get_attribute('section_number') -1]; my $docbook_sectioning_element = _docbook_section_element($self, $element); if ($docbook_sectioning_element eq 'part' and not ($section_relations and $section_relations->{'part_associated_section'}) and !Texinfo::Example::TreeElementConverter::tree_element_is_content_empty( $element)) { $$output_ref .= "\n"; } my $level_adjusted_cmdname = Texinfo::Example::TreeElementConverter::tree_element_section_level_adjusted_command_name( $element); if (!($section_relations and $section_relations->{'section_children'} and scalar(@{$section_relations->{'section_children'}})) or $level_adjusted_cmdname eq 'top') { $$output_ref .= "\n"; pop @{$self->{'lang_stack'}}; my $current = $element; my $current_relations = $sections_list->[$current->get_attribute('section_number') -1]; while ($current_relations->{'section_directions'} and $current_relations->{'section_directions'}->{'up'} and !$current_relations->{'section_directions'}->{'next'} and Texinfo::Example::TreeElementConverter::tree_element_section_level_adjusted_command_name( $current_relations->{'section_directions'}->{'up'}->{'element'}) ne 'top') { $current_relations = $current_relations->{'section_directions'}->{'up'}; $current = $current_relations->{'element'}; $$output_ref .= '\n"; pop @{$self->{'lang_stack'}}; } } } elsif ($cmdname eq 'seeentry' or $cmdname eq 'seealso') { $output_ref = $self->{'document_context'}->[-1]->{$cmdname.'_info'}->[1]; } elsif ($e_type and $e_type eq 'index_entry_command') { my $result = ''; my ($index_entry, $index_info) = $self->get_tree_element_index_entry($element); my $entry_element = $index_entry->{'entry_element'}; if ($self->{'document_context'}->[-1]->{'subentry_output'}) { $output_ref = shift(@{$self->{'document_context'} ->[-1]->{'subentry_output'}}); foreach my $subentry_output (@{$self->{'document_context'}->[-1]->{'subentry_output'}}) { $result .= $$subentry_output; } } if (defined($self->{'document_context'}->[-1]->{'seeentry_info'})) { my $seeentry_ref = $self->{'document_context'}->[-1]->{'seeentry_info'}->[0]; $result .= ""; $result .= $$seeentry_ref; $result .= ""; delete $self->{'document_context'}->[-1]->{'seeentry_output'}; } if (defined($self->{'document_context'}->[-1]->{'seealso_info'})) { my $seealso_ref = $self->{'document_context'}->[-1]->{'seealso_info'}->[0]; $result .= ""; $result .= $$seealso_ref; $result .= ""; delete $self->{'document_context'}->[-1]->{'seealso_output'}; } $$output_ref .= ""; $$output_ref .= $result; $$output_ref .= ""; pop @{$self->{'document_context'}}; my ($comment, $end_line) = $self->tree_element_comment_or_end_line($element); if ($comment) { $end_line = _format_comment($self, $comment); } if ($self->{'document_context'}->[-1]->{'in_preformatted'}) { chomp($end_line); } $$output_ref .= $end_line; } elsif (exists($docbook_line_commands{$cmdname})) { if ($Texinfo::Commands::def_commands{$cmdname}) { pop @{$self->{'document_context'}}; $$output_ref .= "\n"; } elsif ($cmdname eq 'subentry') { my $subentry_level = $self->{'document_context'}->[-1]->{'subentry_level'}; my $level; if ($subentry_level == 1) { $level = 'secondary'; } else { $level = 'tertiary'; } $self->{'document_context'}->[-1]->{'subentry_level'}--; $output_ref = $self->{'document_context'}->[-1] ->{'subentry_output'}->[$subentry_level]; $$output_ref .= ""; } } } elsif ($e_type) { if (defined($type_elements{$e_type})) { $$output_ref .= ""; } elsif ($e_type eq 'preformatted') { $$output_ref .= "{'document_context'}->[-1]->{'preformatted_stack'}->[-1]>"; delete $self->{'document_context'}->[-1]->{'in_preformatted'}; } elsif ($e_type eq 'def_line') { pop @{$self->{'document_context'}}; $$output_ref .= "\n"; } elsif ($e_type eq 'def_category') { $$output_ref .= ":"; } elsif ($e_type eq 'def_name') { my $def_command = $element->parent()->parent()->get_attribute('def_command'); $$output_ref .= ""; } elsif ($def_argument_types_docbook{$e_type}) { foreach my $element_attribute (reverse ( @{$def_argument_types_docbook{$e_type}})) { my ($element, $attribute_text) = _parse_attribute($element_attribute); $$output_ref .= ""; } } elsif ($e_type eq 'block_line_arg') { my $top_cartouche_title = $self->{'document_context'}->[-1]->{'cartouche_title'}; if ($top_cartouche_title) { my $title_text_ref; ($output_ref, $title_text_ref) = @$top_cartouche_title; if ($$title_text_ref ne '') { $$output_ref .= ''.$$title_text_ref.''."\n"; } delete $self->{'document_context'}->[-1]->{'cartouche_title'}; } } elsif (exists($docbook_preformatted_types{$e_type})) { my $format = pop @{$self->{'document_context'}->[-1]->{'preformatted_stack'}}; die "BUG $format ne $docbook_preformatted_types{$e_type}" if ($format ne $docbook_preformatted_types{$e_type}); } elsif ($e_type eq 'before_node_section' and !$self->get_conf('_DOCBOOK_PIECE')) { $output_ref = \$result; } } } else {# EMPTY my $cmdname = $element->{'cmdname'}; if ($cmdname) { if (defined($docbook_no_arg_commands_formatting{$cmdname})) { $$output_ref .= $docbook_no_arg_commands_formatting{$cmdname}; } elsif ($cmdname eq 'w') { # special case to ensure that @w leads to something even if empty $$output_ref .= $w_command_mark; } elsif ($Texinfo::Commands::accent_commands{$cmdname}) { $result .= $self->tree_element_xml_accents($element, $self->{'document_context'}->[-1]->{'upper_case'}->[-1]); } elsif (!exists($docbook_no_warn_empty_commands{$cmdname}) # brace commands without braces lead to EMPTY element tokens and !exists($Texinfo::Commands::brace_commands{$cmdname}) and !($e_type and $e_type eq 'definfoenclose_command')) { print STDERR "UNEXPECTED EMPTY C $cmdname\n"; } } elsif ($e_type) { if (!exists($docbook_no_warn_empty_types{$e_type})) { print STDERR "UNEXPECTED EMPTY T $e_type\n"; } } else { print STDERR "UNEXPECTED no type\n"; cluck(); } } } #print STDERR " ---- E\n"; return $result; } # figure: mandatory title->use it with shortcaption?. Has a caption. 1; __END__