\n" if $vertical;
$nr_of_buttons_shown++;
} elsif (defined($active)) {
# only active buttons are print out when not in table
if ($need_delimiter and $nr_of_buttons_shown > 0) {
$result_buttons .= ', ';
}
my $open = $self->html_attribute_class('span', ['nav-button']);
if ($open ne '') {
$result_buttons .= $open.'>';
}
$result_buttons .= $active;
if ($open ne '') {
$result_buttons .= '';
}
$nr_of_buttons_shown++;
}
}
if ($result_buttons eq '') {
return '';
}
my $result = '';
# if $vertical/VERTICAL_HEAD_NAVIGATION, the buttons are in a vertical
# table which is itself in the first column of a table opened in
# header_navigation
if ($self->get_conf('HEADER_IN_TABLE')) {
$result .= $self->html_attribute_class('table', ['nav-panel']).'>'."\n";
$result .= "
';
} elsif ($self->get_conf('SPLIT')
and $self->get_conf('SPLIT') eq 'node' and $result ne ''
and defined($self->get_conf('DEFAULT_RULE'))) {
$result .= $self->get_conf('DEFAULT_RULE')."\n";
}
return $result;
}
# this can only be called on root commands and associated tree units
sub _default_format_element_header($$$$) {
my ($self, $cmdname, $command, $output_unit) = @_;
my $result = '';
print STDERR "FORMAT elt header "
# uncomment to get perl object names
#."$output_unit (@{$output_unit->{'unit_contents'}}) ".
. "(".join('|', map{Texinfo::Common::debug_print_element($_)}
@{$output_unit->{'unit_contents'}}) . ") ".
Texinfo::OutputUnits::output_unit_texi($output_unit) ."\n"
if ($self->get_conf('DEBUG'));
# Do the heading if the command is the first command in the element
if (($output_unit->{'unit_contents'}->[0] eq $command
or (!exists($output_unit->{'unit_contents'}->[0]->{'cmdname'})
and $output_unit->{'unit_contents'}->[1] eq $command))
# and there is more than one element
and (exists($output_unit->{'tree_unit_directions'}))) {
my $is_top = $self->unit_is_top_output_unit($output_unit);
my $first_in_page = 0;
if (exists($output_unit->{'unit_filename'})
and $self->count_elements_in_filename('current',
$output_unit->{'unit_filename'}) == 1) {
$first_in_page = 1;
}
my $previous_is_top = 0;
$previous_is_top = 1
if (exists($output_unit->{'tree_unit_directions'}->{'prev'})
and $self->unit_is_top_output_unit($output_unit->{'tree_unit_directions'}
->{'prev'}));
print STDERR "Header ($previous_is_top, $is_top, $first_in_page): "
.Texinfo::Convert::Texinfo::root_heading_command_to_texinfo($command)."\n"
if ($self->get_conf('DEBUG'));
if ($is_top) {
# use TOP_BUTTONS for top.
$result .=
&{$self->formatting_function('format_navigation_header')}($self,
$self->get_conf('TOP_BUTTONS'), $cmdname, $command)
if ($self->get_conf('SPLIT') or $self->get_conf('HEADERS'));
} else {
my $split = $self->get_conf('SPLIT');
if ($first_in_page and !$self->get_conf('HEADERS')) {
if (defined($split) and $split eq 'chapter') {
$result
.= &{$self->formatting_function('format_navigation_header')}($self,
$self->get_conf('CHAPTER_BUTTONS'), $cmdname, $command);
$result .= $self->get_conf('DEFAULT_RULE') ."\n"
if (defined($self->get_conf('DEFAULT_RULE'))
and !$self->get_conf('VERTICAL_HEAD_NAVIGATION'));
} elsif (defined($split) and $split eq 'section') {
$result
.= &{$self->formatting_function('format_navigation_header')}($self,
$self->get_conf('SECTION_BUTTONS'), $cmdname, $command);
}
}
if (($first_in_page or $previous_is_top)
and $self->get_conf('HEADERS')) {
$result
.= &{$self->formatting_function('format_navigation_header')}($self,
$self->get_conf('SECTION_BUTTONS'), $cmdname, $command);
} elsif ($self->get_conf('HEADERS')
or (defined($split) and $split eq 'node')) {
# got to do this here, as it isn't done otherwise since
# navigation_header is not called
$result
.= &{$self->formatting_function('format_navigation_panel')}($self,
$self->get_conf('SECTION_BUTTONS'), $cmdname,
$command, undef, 1);
}
}
}
return $result;
}
sub register_opened_section_level($$$$) {
my ($self, $filename, $level, $close_string) = @_;
if (!exists($self->{'pending_closes'}->{$filename})) {
$self->{'pending_closes'}->{$filename} = [];
}
my $pending_closes = $self->{'pending_closes'}->{$filename};
while (@$pending_closes < $level) {
push(@$pending_closes, "");
}
push(@$pending_closes, $close_string);
}
sub close_registered_sections_level($$$) {
my ($self, $filename, $level) = @_;
if (not defined($level)) {
cluck 'close_registered_sections_level $level not defined';
}
my @closed_elements;
if (!exists($self->{'pending_closes'}->{$filename})) {
return \@closed_elements;
}
my $pending_closes = $self->{'pending_closes'}->{$filename};
while (@$pending_closes > $level) {
my $close_string = pop @$pending_closes;
push(@closed_elements, $close_string)
if ($close_string ne "");
}
return \@closed_elements;
}
sub _contents_inline_element($$$) {
my ($self, $cmdname,
# undef unless called from @-command formatting function
$element) = @_;
print STDERR "CONTENTS_INLINE $cmdname\n" if ($self->get_conf('DEBUG'));
my $table_of_contents
= &{$self->formatting_function('format_contents')}($self,
$cmdname, $element);
if (defined($table_of_contents) and $table_of_contents ne '') {
my ($special_unit_variety, $special_unit, $class_base,
$special_unit_direction)
= $self->command_name_special_unit_information($cmdname);
my $result = $self->html_attribute_class('div', ["region-${class_base}"]);
my $unit_command = $special_unit->{'unit_command'};
my $id = $self->command_id($unit_command);
if (defined($id) and $id ne '') {
$result .= " id=\"$id\"";
}
$result .= ">\n";
my $heading = $self->command_text($unit_command);
$heading = '' if (!defined($heading));
$result .= &{$self->formatting_function('format_heading_text')}($self,
$cmdname, [$class_base.'-heading'], $heading,
$self->get_conf('CHAPTER_HEADER_LEVEL'))."\n";
$result .= $table_of_contents . "\n";
return $result;
}
return '';
}
sub _convert_heading_command($$$$$) {
my ($self, $cmdname, $element, $args, $content) = @_;
my $result = '';
# No situation where this could happen
if (in_string($self)) {
$result .= $self->command_text($element, 'string') ."\n"
if ($cmdname ne 'node');
$result .= $content if (defined($content));
return $result;
}
my $element_id = $self->command_id($element);
print STDERR "CONVERT elt heading "
# uncomment next line for the perl object name
#."$element "
.Texinfo::Convert::Texinfo::root_heading_command_to_texinfo($element)."\n"
if ($self->get_conf('DEBUG'));
my $document = $self->get_info('document');
my $sections_list;
my $nodes_list;
if (defined($document)) {
$sections_list = $document->sections_list();
$nodes_list = $document->nodes_list();
}
my $output_unit;
my $section_relations;
my $node_relations;
if (exists($Texinfo::Commands::root_commands{$cmdname})) {
if ($cmdname eq 'node') {
if (defined($nodes_list) and exists($element->{'extra'})
and $element->{'extra'}->{'node_number'}) {
$node_relations
= $nodes_list->[$element->{'extra'}->{'node_number'} -1];
}
} elsif (defined($sections_list)) {
$section_relations
= $sections_list->[$element->{'extra'}->{'section_number'} -1];
}
# All the root commands are associated to an output unit, the condition
# on associated_unit is always true.
if (exists($element->{'associated_unit'})) {
$output_unit = $element->{'associated_unit'};
}
}
my $element_header = '';
if ($output_unit) {
$element_header = &{$self->formatting_function('format_element_header')}(
$self, $cmdname, $element, $output_unit);
}
my $toc_or_mini_toc_or_auto_menu = '';
if ($self->get_conf('CONTENTS_OUTPUT_LOCATION') eq 'after_top'
and $cmdname eq 'top'
and defined($sections_list)
and scalar(@{$sections_list}) > 1) {
foreach my $content_command_name ('shortcontents', 'contents') {
if ($self->get_conf($content_command_name)) {
my $contents_text
= _contents_inline_element($self, $content_command_name, undef);
if ($contents_text ne '') {
$toc_or_mini_toc_or_auto_menu .= $contents_text;
}
}
}
}
my $format_menu = $self->get_conf('FORMAT_MENU');
if ($toc_or_mini_toc_or_auto_menu eq '' and defined($section_relations)) {
if ($format_menu eq 'sectiontoc') {
$toc_or_mini_toc_or_auto_menu = _mini_toc($self, $section_relations);
} elsif (($format_menu eq 'menu' or $format_menu eq 'menu_no_detailmenu')
and exists($section_relations->{'associated_node'})) {
my $associated_node_relations = $section_relations->{'associated_node'};
# arguments_line type element
my $arguments_line
= $associated_node_relations->{'element'}->{'contents'}->[0];
my $automatic_directions = 1;
if (scalar(@{$arguments_line->{'contents'}}) > 1) {
$automatic_directions = 0;
}
if ($automatic_directions
and !exists($associated_node_relations->{'menus'})) {
my $identifiers_target = $document->labels_information();
my $menu_node;
if ($format_menu eq 'menu') {
$menu_node
= Texinfo::Structuring::new_complete_menu_master_menu($self,
$identifiers_target, $nodes_list,
$associated_node_relations);
} else { # $format_menu eq 'menu_no_detailmenu'
$menu_node
= Texinfo::Structuring::new_complete_node_menu(
$associated_node_relations,
$self->{'current_lang_translations'},
$self->get_conf('DEBUG'));
}
if (defined($menu_node)) {
$toc_or_mini_toc_or_auto_menu
= $self->convert_tree($menu_node, 'master menu');
}
}
}
}
if ($self->get_conf('NO_TOP_NODE_OUTPUT')
and exists($Texinfo::Commands::root_commands{$cmdname})) {
my $in_skipped_node_top
= $self->get_shared_conversion_state('top', 'in_skipped_node_top');
$in_skipped_node_top = 0 if (!defined($in_skipped_node_top));
if ($in_skipped_node_top == 1) {
my $id_class = $cmdname;
$result .= &{$self->formatting_function('format_separate_anchor')}($self,
$element_id, $id_class);
$result .= $element_header;
$result .= $toc_or_mini_toc_or_auto_menu;
return $result;
}
}
my $level_corrected_cmdname = $cmdname;
my $level_set_class;
if (exists($element->{'extra'})
and exists($element->{'extra'}->{'section_level'})) {
# if the level was changed, use a consistent command name
$level_corrected_cmdname
= Texinfo::Structuring::section_level_adjusted_command_name($element);
if ($level_corrected_cmdname ne $cmdname) {
$level_set_class = "${cmdname}-level-set-${level_corrected_cmdname}";
}
}
# find the section starting here, can be through the associated node
# preceding the section, or the section itself
my $opening_section;
my $level_corrected_opening_section_cmdname;
if (defined($node_relations)
and exists($node_relations->{'associated_section'})) {
$opening_section = $node_relations->{'associated_section'}->{'element'};
$level_corrected_opening_section_cmdname
= Texinfo::Structuring::section_level_adjusted_command_name(
$opening_section);
# if there is an associated node, it is not a section opening
# the section was opened before when the node was encountered
} elsif (defined($section_relations)
and !exists($section_relations->{'associated_node'})) {
$opening_section = $element;
$level_corrected_opening_section_cmdname = $level_corrected_cmdname;
}
# could use empty args information also, to avoid calling command_text
#my $empty_heading = (!scalar(@$args) or !defined($args->[0]));
# $heading not defined may happen if the command is a @node, for example
# if there is an error in the node.
my $heading = $self->command_text($element);
my $heading_level;
# node is used as heading if there is nothing else.
if (defined($node_relations)) {
if (defined($output_unit) and exists($output_unit->{'unit_node'})
and $output_unit->{'unit_node'} eq $node_relations
and !exists($node_relations->{'associated_title_command'})) {
if ($element->{'extra'}->{'normalized'} eq 'Top') {
$heading_level = 0;
} else {
# use node
$heading_level = 3;
}
}
} elsif (exists($element->{'extra'})
and exists($element->{'extra'}->{'section_level'})) {
$heading_level = $element->{'extra'}->{'section_level'};
} else {
# for *heading* @-commands which do not have a level
# in the document as they are not associated with the
# sectioning tree, but still have a $heading_level
$heading_level = Texinfo::Common::section_level($element);
}
my $do_heading = (defined($heading) and $heading ne ''
and defined($heading_level));
# if set, the id is associated to the heading text
my $heading_id;
if ($opening_section) {
my $level;
if (exists($opening_section->{'extra'})
and exists($opening_section->{'extra'}->{'section_level'})) {
$level = $opening_section->{'extra'}->{'section_level'};
} else {
# if Structuring sectioning_structure was not called on the
# document (cannot happen in main program or test_utils.pl tests)
$level = Texinfo::Common::section_level($opening_section);
}
my $closed_strings = $self->close_registered_sections_level(
$self->current_filename(), $level);
$result .= join('', @{$closed_strings});
$self->register_opened_section_level($self->current_filename(), $level,
"\n");
# use a specific class name to mark that this is the start of
# the section extent. It is not necessary where the section is.
$result .= $self->html_attribute_class('div',
["${level_corrected_opening_section_cmdname}-level-extent"]);
$result .= " id=\"$element_id\""
if (defined($element_id) and $element_id ne '');
$result .= ">\n";
} elsif (defined($element_id) and $element_id ne '') {
if ($element_header ne '') {
# case of a @node without sectioning command and with a header.
# put the node element anchor before the header.
# Set the class name to the command name if there is no heading,
# else the class will be with the heading element.
my $id_class = $cmdname;
if ($do_heading) {
$id_class = "${cmdname}-id";
}
$result .= &{$self->formatting_function('format_separate_anchor')}($self,
$element_id, $id_class);
} else {
$heading_id = $element_id;
}
}
$result .= $element_header;
if ($do_heading) {
if ($self->get_conf('TOC_LINKS')
and exists($Texinfo::Commands::root_commands{$cmdname})
and exists($sectioning_heading_commands{$cmdname})) {
my $content_href = $self->command_contents_href($element, 'contents');
if (defined($content_href)) {
$heading = "$heading";
}
}
my @heading_classes;
push @heading_classes, $level_corrected_cmdname;
if (defined($level_set_class)) {
push @heading_classes, $level_set_class;
}
if (in_preformatted_context($self)) {
my $id_str = '';
if (defined($heading_id)) {
$id_str = " id=\"$heading_id\"";
}
$result .= $self->html_attribute_class('strong', \@heading_classes)
."${id_str}>".$heading.''."\n";
} else {
$result .= &{$self->formatting_function('format_heading_text')}($self,
$level_corrected_cmdname, \@heading_classes, $heading,
$heading_level +$self->get_conf('CHAPTER_HEADER_LEVEL') -1,
$heading_id, $element, $element_id);
}
} elsif (defined($heading_id)) {
# case of a lone node and no header, and case of an empty @top
$result .= &{$self->formatting_function('format_separate_anchor')}($self,
$heading_id, $cmdname);
}
$result .= $content if (defined($content));
$result .= $toc_or_mini_toc_or_auto_menu;
return $result;
}
foreach my $cmdname (keys(%sectioning_heading_commands), 'node') {
$default_commands_conversion{$cmdname} = \&_convert_heading_command;
}
sub _convert_raw_command($$$$$) {
my ($self, $cmdname, $command, $args, $content) = @_;
$content = '' if (!defined($content));
if ($cmdname eq 'html') {
return $content;
}
# In multiple conversions should only happen rarely, as in general, format
# commands do not happen in inline context where most of the multiple
# conversions are. A possibility is in float caption.
if (!$self->in_multiple_conversions()) {
$self->converter_line_warn(sprintf(__("raw format %s is not converted"),
$cmdname), $command->{'source_info'});
}
return &{$self->formatting_function('format_protect_text')}($self, $content);
}
foreach my $cmdname (keys(%format_raw_commands)) {
$default_commands_conversion{$cmdname} = \&_convert_raw_command;
}
sub _convert_inline_command($$$$) {
my ($self, $cmdname, $command, $args) = @_;
my $format;
if (defined($args) and defined($args->[0])
and defined($args->[0]->{'monospacetext'})
and $args->[0]->{'monospacetext'} ne '') {
$format = $args->[0]->{'monospacetext'};
} else {
return '';
}
my $arg_index = undef;
if (exists($inline_format_commands{$cmdname})) {
if ($cmdname eq 'inlinefmtifelse' and !$self->is_format_expanded($format)) {
$arg_index = 2;
} elsif ($self->is_format_expanded($format)) {
$arg_index = 1;
}
} elsif (exists($command->{'extra'})
and $command->{'extra'}->{'expand_index'}) {
$arg_index = 1;
}
if (defined($arg_index) and $arg_index < scalar(@$args)) {
my $text_arg = $args->[$arg_index];
if (defined($text_arg)) {
if (defined($text_arg->{'normal'})) {
return $text_arg->{'normal'};
} elsif (defined($text_arg->{'raw'})) {
return $text_arg->{'raw'};
}
}
}
return '';
}
foreach my $cmdname (grep {$brace_commands{$_} eq 'inline'}
keys(%brace_commands)) {
$default_commands_conversion{$cmdname} = \&_convert_inline_command;
}
sub _indent_with_table($$$;$) {
my ($self, $cmdname, $content, $extra_classes) = @_;
my @classes = ($cmdname);
push (@classes, @$extra_classes) if (defined($extra_classes));
return $self->html_attribute_class('table', \@classes)
.'>
'.$self->get_info('non_breaking_space').'
'.$content
."
\n";
}
sub _convert_preformatted_command($$$$$) {
my ($self, $cmdname, $command, $args, $content) = @_;
if (!defined($content) or $content eq '') {
return '';
}
if (in_string($self)) {
return $content;
}
my @classes;
# this is mainly for classes as there are purprosely no classes
# for small*
my $main_cmdname;
if (exists($small_block_associated_command{$cmdname})) {
$main_cmdname = $small_block_associated_command{$cmdname};
push @classes, $cmdname;
} else {
$main_cmdname = $cmdname;
}
if ($cmdname eq 'example') {
# arguments_line type element
my $arguments_line = $command->{'contents'}->[0];
foreach my $example_arg (@{$arguments_line->{'contents'}}) {
# convert or remove all @-commands, using simple ascii and unicode
# characters
my $converted_arg
= Texinfo::Convert::NodeNameNormalization::convert_to_normalized(
$example_arg);
if ($converted_arg ne '') {
push @classes, 'user-' . $converted_arg;
}
}
} elsif ($main_cmdname eq 'lisp') {
push @classes, $main_cmdname;
$main_cmdname = 'example';
}
if ($self->get_conf('INDENTED_BLOCK_COMMANDS_IN_TABLE')
and exists($indented_preformatted_commands{$cmdname})) {
return _indent_with_table($self, $cmdname, $content, \@classes);
} else {
unshift @classes, $main_cmdname;
return $self->html_attribute_class('div', \@classes)
.">\n".$content.''."\n";
}
}
foreach my $preformatted_command (keys(%preformatted_commands)) {
$default_commands_conversion{$preformatted_command}
= \&_convert_preformatted_command;
}
sub _convert_indented_command($$$$$) {
my ($self, $cmdname, $command, $args, $content) = @_;
if (!defined($content) or $content eq '') {
return '';
}
if (in_string($self)) {
return $content;
}
my @classes;
my $main_cmdname;
if (exists($small_block_associated_command{$cmdname})) {
push @classes, $cmdname;
$main_cmdname = $small_block_associated_command{$cmdname};
} else {
$main_cmdname = $cmdname;
}
if ($self->get_conf('INDENTED_BLOCK_COMMANDS_IN_TABLE')) {
return _indent_with_table($self, $main_cmdname, $content, \@classes);
} else {
unshift @classes, $main_cmdname;
return $self->html_attribute_class('blockquote', \@classes).">\n"
. $content . ''."\n";
}
}
$default_commands_conversion{'indentedblock'} = \&_convert_indented_command;
sub _convert_verbatim_command($$$$$) {
my ($self, $cmdname, $command, $args, $content) = @_;
$content = '' if (!defined($content));
if (!in_string($self)) {
return $self->html_attribute_class('pre', [$cmdname]).'>'
.$content . '';
} else {
return $content;
}
}
$default_commands_conversion{'verbatim'} = \&_convert_verbatim_command;
sub _convert_displaymath_command($$$$$) {
my ($self, $cmdname, $command, $args, $content) = @_;
$content = '' if (!defined($content));
if (in_string($self)) {
return $content;
}
my $result = '';
my $pre_classes = [$cmdname];
my $use_mathjax = ($self->get_conf('HTML_MATH')
and $self->get_conf('HTML_MATH') eq 'mathjax');
if ($use_mathjax) {
$self->register_file_information('mathjax', 1);
push @$pre_classes, 'tex2jax_process';
}
$result .= $self->html_attribute_class('pre', $pre_classes).'>';
if ($self->get_conf('HTML_MATH')
and $self->get_conf('HTML_MATH') eq 'mathjax') {
$result .= "\\[$content\\]";
} else {
$result .= $content;
}
$result .= '';
return $result;
}
$default_commands_conversion{'displaymath'} = \&_convert_displaymath_command;
sub _convert_verbatiminclude_command($$$$) {
my ($self, $cmdname, $command, $args) = @_;
my $verbatim_include_verbatim
= $self->expand_verbatiminclude($command);
if (defined($verbatim_include_verbatim)) {
return $self->convert_tree($verbatim_include_verbatim,
'convert verbatiminclude');
} else {
return '';
}
}
$default_commands_conversion{'verbatiminclude'}
= \&_convert_verbatiminclude_command;
sub _convert_command_simple_block($$$$$) {
my ($self, $cmdname, $command, $args, $content) = @_;
$content = '' if (!defined($content));
return $self->html_attribute_class('div', [$cmdname]).'>'
.$content.'';
}
$default_commands_conversion{'raggedright'} = \&_convert_command_simple_block;
$default_commands_conversion{'flushleft'} = \&_convert_command_simple_block;
$default_commands_conversion{'flushright'} = \&_convert_command_simple_block;
$default_commands_conversion{'group'} = \&_convert_command_simple_block;
sub _convert_sp_command($$$$) {
my ($self, $cmdname, $command, $args) = @_;
my $sp_nr = 1;
if (exists($command->{'extra'})
and exists($command->{'extra'}->{'misc_args'})) {
$sp_nr = $command->{'extra'}->{'misc_args'}->[0];
}
if ($sp_nr > 0) {
if (in_preformatted_context($self) or in_string($self)) {
return "\n" x $sp_nr;
} else {
return ($self->get_info('line_break_element')."\n") x $sp_nr;
}
} else {
return '';
}
}
$default_commands_conversion{'sp'} = \&_convert_sp_command;
sub _convert_exdent_command($$$$) {
my ($self, $cmdname, $command, $args) = @_;
my $arg = $self->get_pending_formatted_inline_content();
if (defined($args) and defined($args->[0])) {
$arg .= $args->[0]->{'normal'};
}
if (in_string($self)) {
return $arg ."\n";
}
# FIXME do something with CSS? Currently nothing is defined for exdent
if (in_preformatted_context($self)) {
return $self->html_attribute_class('pre', [$cmdname]).'>'.$arg ."\n";
} else {
return $self->html_attribute_class('p', [$cmdname]).'>'.$arg ."\n";
}
}
$default_commands_conversion{'exdent'} = \&_convert_exdent_command;
sub _convert_center_command($$$$) {
my ($self, $cmdname, $command, $args) = @_;
if (!defined($args) or !defined($args->[0])) {
return '';
}
if (in_string($self)) {
return $args->[0]->{'normal'}."\n";
} else {
return $self->html_attribute_class('div', [$cmdname]).">"
.$args->[0]->{'normal'}."\n";
}
}
$default_commands_conversion{'center'} = \&_convert_center_command;
sub _convert_author_command($$$$) {
my ($self, $cmdname, $command, $args) = @_;
my $quotation_titlepage_nr = $self->get_shared_conversion_state('quotation',
'quotation_titlepage_stack');
if (defined($quotation_titlepage_nr) and $quotation_titlepage_nr > 0) {
my $authors_nr
= $self->get_shared_conversion_state('quotation', 'element_authors_number',
$quotation_titlepage_nr);
if ($authors_nr < 0) {
# in titlepage
if (!in_string($self)) {
return $self->html_attribute_class('strong', [$cmdname])
.">$args->[0]->{'normal'}"
.$self->get_info('line_break_element')."\n";
} else {
return $args->[0]->{'normal'} . "\n";
}
} else {
# in quotation
$self->set_shared_conversion_state('quotation', 'elements_authors',
$quotation_titlepage_nr, $authors_nr,
$command);
$authors_nr++;
$self->set_shared_conversion_state('quotation', 'element_authors_number',
$quotation_titlepage_nr, $authors_nr);
}
}
return '';
}
$default_commands_conversion{'author'} = \&_convert_author_command;
sub _convert_title_command($$$$) {
my ($self, $cmdname, $command, $args) = @_;
return '' if (!defined($args) or !defined($args->[0]));
if (!in_string($self)) {
return $self->html_attribute_class('h1', [$cmdname])
.">$args->[0]->{'normal'}\n";
} else {
return $args->[0]->{'normal'};
}
}
$default_commands_conversion{'title'} = \&_convert_title_command;
sub _convert_subtitle_command($$$$) {
my ($self, $cmdname, $command, $args) = @_;
return '' if (!defined($args) or !defined($args->[0]));
if (!in_string($self)) {
return $self->html_attribute_class('h3', [$cmdname])
.">$args->[0]->{'normal'}\n";
} else {
return $args->[0]->{'normal'};
}
}
$default_commands_conversion{'subtitle'} = \&_convert_subtitle_command;
sub _convert_insertcopying_command($$$) {
my ($self, $cmdname, $command) = @_;
my $global_commands;
my $document = $self->get_info('document');
if (defined($document)) {
$global_commands = $document->global_commands_information();
}
if (defined($global_commands) and exists($global_commands->{'copying'})) {
return $self->convert_tree(
Texinfo::TreeElement::new(
{'contents' => $global_commands->{'copying'}->{'contents'}}),
'convert insertcopying');
}
return '';
}
$default_commands_conversion{'insertcopying'}
= \&_convert_insertcopying_command;
sub _convert_maketitle_command($$$) {
my ($self, $cmdname, $command) = @_;
return $self->get_info('title_titlepage');
}
$default_commands_conversion{'maketitle'}
= \&_convert_maketitle_command;
sub _convert_listoffloats_command($$$$) {
my ($self, $cmdname, $command, $args) = @_;
# should probably never happen
return '' if (in_string($self));
my $floats;
my $document = $self->get_info('document');
if (defined($document)) {
$floats = $document->floats_information();
}
my $listoffloats_name = $command->{'extra'}->{'float_type'};
my $formatted_listoffloats_nr
= $self->get_shared_conversion_state('listoffloats',
'formatted_listoffloats',
$listoffloats_name);
$formatted_listoffloats_nr = 0 if (!defined($formatted_listoffloats_nr));
$formatted_listoffloats_nr++;
$self->set_shared_conversion_state('listoffloats', 'formatted_listoffloats',
$listoffloats_name, $formatted_listoffloats_nr);
if (defined($floats) and exists($floats->{$listoffloats_name})
and scalar(@{$floats->{$listoffloats_name}})) {
my $result = $self->html_attribute_class('dl', [$cmdname]).">\n" ;
foreach my $float_and_section (@{$floats->{$listoffloats_name}}) {
my ($float, $float_section) = @$float_and_section;
my $float_href = $self->command_href($float);
next if (!defined($float_href));
$result .= '
';
my $float_text = $self->command_text($float);
if (defined($float_text) and $float_text ne '') {
if ($float_href ne '') {
$result .= "$float_text";
} else {
$result .= $float_text;
}
}
$result .= '
';
my $caption_element;
my $caption_cmdname;
my ($caption, $shortcaption)
= Texinfo::Common::find_float_caption_shortcaption($float);
if (defined($shortcaption)) {
$caption_element = $shortcaption;
$caption_cmdname = 'shortcaption';
} elsif (defined($caption)) {
$caption_element = $caption;
$caption_cmdname = 'caption';
}
my $caption_text;
my @caption_classes;
if (defined($caption_element)) {
my $multiple_formatted = 'listoffloats';
if ($formatted_listoffloats_nr > 1) {
$multiple_formatted .= '-'.($formatted_listoffloats_nr - 1);
}
$caption_text = $self->convert_tree_new_formatting_context(
$caption_element->{'contents'}->[0], $cmdname, undef,
$multiple_formatted);
push @caption_classes, "${caption_cmdname}-in-${cmdname}";
} else {
$caption_text = '';
}
$result .= $self->html_attribute_class('dd', \@caption_classes).'>'
.$caption_text.''."\n";
}
return $result . "\n";
} else {
return '';
}
}
$default_commands_conversion{'listoffloats'} = \&_convert_listoffloats_command;
sub _convert_menu_command($$$$$) {
my ($self, $cmdname, $command, $args, $content) = @_;
$content = '' if (!defined($content));
return $content if ($cmdname eq 'detailmenu');
$self->set_shared_conversion_state('menu', 'html_menu_entry_index', 0);
if ($content !~ /\S/) {
return '';
}
# This can probably only happen with incorrect input. It happens with
# menu in documentdescription. It does not seem that it could happen
# in other situation with a Texinfo tree parsed from Texinfo code.
if (in_string($self)) {
return $content;
}
my $begin_row = '';
my $end_row = '';
if (inside_preformatted($self)) {
$begin_row = '
';
$end_row = '
';
}
return $self->html_attribute_class('table', [$cmdname])
.">${begin_row}\n" . $content . "${end_row}\n";
}
$default_commands_conversion{'menu'} = \&_convert_menu_command;
$default_commands_conversion{'detailmenu'} = \&_convert_menu_command;
sub _convert_float_command($$$$$) {
my ($self, $cmdname, $command, $args, $content) = @_;
$content = '' if (!defined($content));
my ($caption_element, $prepended)
= Texinfo::Convert::Converter::float_name_caption($self, $command);
if (in_string($self)) {
my $prepended_text;
if (defined($prepended)) {
$prepended_text
= $self->convert_tree_new_formatting_context($prepended,
'float prepended');
} else {
$prepended_text = '';
}
my $caption_text = '';
if (defined($caption_element) and exists($caption_element->{'contents'})
and exists($caption_element->{'contents'}->[0]->{'contents'})) {
$caption_text = $self->convert_tree_new_formatting_context(
$caption_element->{'contents'}->[0], 'float caption');
}
return $prepended_text.$content.$caption_text;
}
my $caption_command_name;
if (defined($caption_element)) {
$caption_command_name = $caption_element->{'cmdname'};
}
my $result = $self->html_attribute_class('div', [$cmdname]);
my $id = $self->command_id($command);
if (defined($id) and $id ne '') {
$result .= " id=\"$id\"";
}
$result .= ">\n" . $content;
my $prepended_text;
my $caption_text;
if (defined($prepended)) {
# TODO add a span with a class name for the prependend information
# if not empty?
$prepended_text = $self->convert_tree_new_formatting_context(
Texinfo::TreeElement::new({'cmdname' => 'strong',
'contents' => [
Texinfo::TreeElement::new({'type' => 'brace_container',
'contents' => [$prepended]})]}),
'float number type');
if (defined($caption_element)) {
# register the converted prepended tree to be prepended to
# the first paragraph in caption formatting
$self->register_pending_formatted_inline_content($caption_command_name,
$prepended_text);
$caption_text = $self->convert_tree_new_formatting_context(
$caption_element->{'contents'}->[0], 'float caption');
my $cancelled_prepended
= $self->cancel_pending_formatted_inline_content($caption_command_name);
# unset if prepended text is in caption, i.e. is not cancelled
$prepended_text = '' if (not defined($cancelled_prepended));
}
if ($prepended_text ne '') {
# prepended text is not empty and did not find its way in caption
$prepended_text = '
'.$prepended_text.'
';
}
} elsif (defined($caption_element)) {
$caption_text = $self->convert_tree_new_formatting_context(
$caption_element->{'contents'}->[0], 'float caption');
}
if (defined($caption_text) and $caption_text ne '') {
$result .= $self->html_attribute_class('div', [$caption_command_name]). '>'
.$caption_text.'';
} elsif (defined($prepended) and $prepended_text ne '') {
$result .= $self->html_attribute_class('div', ['type-number-float']). '>'
. $prepended_text .'';
}
return $result . '';
}
$default_commands_conversion{'float'} = \&_convert_float_command;
sub _convert_quotation_command($$$$$) {
my ($self, $cmdname, $command, $args, $content) = @_;
$content = '' if (!defined($content));
$self->cancel_pending_formatted_inline_content($cmdname);
my $result;
if (!in_string($self)) {
my @classes;
if (exists($small_block_associated_command{$cmdname})) {
push @classes, $small_block_associated_command{$cmdname};
}
push @classes, $cmdname;
$result = $self->html_attribute_class('blockquote', \@classes).">\n"
. $content . "\n";
} else {
$result = $content;
}
my $quotation_titlepage_nr = $self->get_shared_conversion_state('quotation',
'quotation_titlepage_stack');
my $quotation_authors = [];
if (defined($quotation_titlepage_nr) and $quotation_titlepage_nr > 0) {
my $authors_nr
= $self->get_shared_conversion_state('quotation', 'element_authors_number',
$quotation_titlepage_nr);
if ($authors_nr < 0) {
print STDERR "BUG: unexpected negative element_authors_number"
." $authors_nr in convert_quotation_command\n";
$authors_nr = 0;
}
for (my $i = 0; $i < $authors_nr; $i++) {
my $author = $self->get_shared_conversion_state('quotation',
'elements_authors', $quotation_titlepage_nr, $i);
push @$quotation_authors, $author;
}
$quotation_titlepage_nr--;
$self->set_shared_conversion_state('quotation',
'quotation_titlepage_stack',
$quotation_titlepage_nr);
} else {
print STDERR "BUG: unexpected unset quotation_titlepage_stack"
."in convert_quotation_command\n";
}
# TODO there is no easy way to mark with a class the @author
# @-command. Add a span or a div (@center is in a div)?
foreach my $author (@$quotation_authors) {
if (exists($author->{'contents'}->[0]->{'contents'})) {
# TRANSLATORS: quotation author
my $centered_author = $self->cdt("\@center --- \@emph{{author}}",
{'author' => $author->{'contents'}->[0]});
$result .= $self->convert_tree($centered_author,
'convert quotation author');
}
}
return $result;
}
$default_commands_conversion{'quotation'} = \&_convert_quotation_command;
sub _convert_cartouche_command($$$$$) {
my ($self, $cmdname, $command, $args, $content) = @_;
$content = '' if (!defined($content));
return $content if (in_string($self));
my $title_content = '';
if (defined($args) and defined($args->[0])
and $args->[0]->{'normal'} ne '') {
$title_content = "
\n". $args->[0]->{'normal'} ."
";
}
my $cartouche_content = '';
if ($content =~ /\S/) {
$cartouche_content = "
\n". $content ."
";
}
if ($cartouche_content ne '' or $title_content ne '') {
return $self->html_attribute_class('table', [$cmdname])
. ">${title_content}${cartouche_content}"
. "\n";
}
return $content;
}
$default_commands_conversion{'cartouche'} = \&_convert_cartouche_command;
sub _convert_itemize_command($$$$$) {
my ($self, $cmdname, $command, $args, $content) = @_;
$content = '' if (!defined($content));
if (in_string($self)) {
return $content;
}
# arguments_line type element
my $arguments_line = $command->{'contents'}->[0];
my $block_line_arg = $arguments_line->{'contents'}->[0];
my $command_as_argument_name;
my $prepended_element
= Texinfo::Common::itemize_line_prepended_element($block_line_arg);
if (defined($prepended_element)) {
$command_as_argument_name = $prepended_element->{'cmdname'};
}
my $mark_class_name;
if (defined($command_as_argument_name)) {
if ($command_as_argument_name eq 'w') {
$mark_class_name = 'none';
} else {
$mark_class_name = $command_as_argument_name;
}
}
if (defined($mark_class_name)
and defined($self->css_get_selector_style('ul.mark-'.$mark_class_name))) {
return $self->html_attribute_class('ul', [$cmdname,
'mark-'.$mark_class_name])
.">\n" . $content. "\n";
} elsif ($self->get_conf('NO_CSS')) {
return $self->html_attribute_class('ul', [$cmdname])
.">\n" . $content. "\n";
} else {
my $css_string
= $self->html_convert_css_string_for_list_mark($block_line_arg,
'itemize arg');
if ($css_string ne '') {
return $self->html_attribute_class('ul', [$cmdname])
." style=\"list-style-type: '".
&{$self->formatting_function('format_protect_text')}($self,
$css_string)
. "'\">\n" . $content. "\n";
} else {
return $self->html_attribute_class('ul', [$cmdname])
.">\n" . $content. "\n";
}
}
}
$default_commands_conversion{'itemize'} = \&_convert_itemize_command;
sub _convert_enumerate_command($$$$$) {
my ($self, $cmdname, $command, $args, $content) = @_;
if (!defined($content) or $content eq '') {
return '';
} elsif (in_string($self)) {
return $content;
}
my $type_attribute = '';
my $start_attribute = '';
my ($start, $type);
# arguments_line type element
my $arguments_line = $command->{'contents'}->[0];
my $block_line_arg = $arguments_line->{'contents'}->[0];
if (exists($block_line_arg->{'contents'})
and exists($block_line_arg->{'contents'}->[0]->{'text'})) {
my $specification = $block_line_arg->{'contents'}->[0]->{'text'};
if ($specification =~ /^\d+$/ and $specification ne '1') {
$start = $specification;
} elsif ($specification =~ /^[A-Z]$/) {
$start = 1 + ord($specification) - ord('A');
$type = 'A';
} elsif ($specification =~ /^[a-z]$/) {
$start = 1 + ord($specification) - ord('a');
$type = 'a';
}
$type_attribute = " type=\"$type\"" if (defined($type));
$start_attribute = " start=\"$start\"" if (defined($start));
}
return $self->html_attribute_class('ol', [$cmdname]).$type_attribute
.$start_attribute.">\n" . $content . "\n";
}
$default_commands_conversion{'enumerate'} = \&_convert_enumerate_command;
sub _convert_multitable_command($$$$$) {
my ($self, $cmdname, $command, $args, $content) = @_;
if (!defined($content)) {
return '';
}
if (in_string($self)) {
return $content;
}
if ($content ne '') {
return $self->html_attribute_class('table', [$cmdname]).">\n"
. $content . "\n";
} else {
return '';
}
}
$default_commands_conversion{'multitable'} = \&_convert_multitable_command;
sub _convert_xtable_command($$$$$) {
my ($self, $cmdname, $command, $args, $content) = @_;
if (!defined($content)) {
return '';
}
if (in_string($self)) {
return $content;
}
if ($content ne '') {
return $self->html_attribute_class('dl', [$cmdname]).">\n"
. $content . "\n";
} else {
return '';
}
}
$default_commands_conversion{'table'} = \&_convert_xtable_command;
$default_commands_conversion{'ftable'} = \&_convert_xtable_command;
$default_commands_conversion{'vtable'} = \&_convert_xtable_command;
sub _convert_item_command($$$$$) {
my ($self, $cmdname, $command, $args, $content) = @_;
$content = '' if (!defined($content));
if (in_string($self)) {
return $content;
}
if (exists($command->{'parent'}->{'cmdname'})
and $command->{'parent'}->{'cmdname'} eq 'itemize') {
if ($content =~ /\S/) {
return '
';
} else {
return '';
}
} elsif (exists($command->{'contents'})
and exists($command->{'contents'}->[0]->{'type'})
and $command->{'contents'}->[0]->{'type'} eq 'line_arg') {
if (exists($command->{'contents'}->[0]->{'contents'})) {
my $result = ($cmdname eq 'item') ? '' : '
';
my $index_entry_id = $self->command_id($command);
my $anchor;
if (defined($index_entry_id)) {
$result .= "";
$anchor = _get_copiable_anchor($self, $index_entry_id);
if (defined($anchor)) {
$result .= '';
}
}
my $pre_class_close;
if (in_preformatted_context($self)) {
my $pre_classes = $self->preformatted_classes_stack();
foreach my $pre_class (@$pre_classes) {
if (exists($preformatted_code_commands{$pre_class})) {
$result .= $self->html_attribute_class('code',
['table-term-preformatted-code']).'>';
$pre_class_close = '';
last;
}
}
}
my $table_item_tree = $self->table_item_content_tree_noxs($command);
$table_item_tree = $command->{'contents'}->[0]
if (!defined($table_item_tree));
my $converted_item = $self->convert_tree($table_item_tree,
'convert table_item_tree');
$result .= $converted_item;
if (defined($pre_class_close)) {
$result .= $pre_class_close;
}
if (defined($anchor)) {
$result .= $anchor . '';
}
return $result . "
\n";
} else {
return '';
}
} elsif ($command->{'parent'}->{'type'}
and $command->{'parent'}->{'type'} eq 'row') {
return &{$self->command_conversion('tab')}($self, $cmdname, $command,
$args, $content);
}
return '';
}
$default_commands_conversion{'item'} = \&_convert_item_command;
$default_commands_conversion{'headitem'} = \&_convert_item_command;
$default_commands_conversion{'itemx'} = \&_convert_item_command;
sub _convert_tab_command($$$$$) {
my ($self, $cmdname, $command, $args, $content) = @_;
$content = '' if (!defined($content));
$content =~ s/^\s*//;
$content =~ s/\s*$//;
if (in_string($self)) {
return $content;
}
my $cell_nr = $command->{'extra'}->{'cell_number'};
my $row = $command->{'parent'};
my $row_cmdname = $row->{'contents'}->[0]->{'cmdname'};
my $multitable = $row->{'parent'}->{'parent'};
my $columnfractions
= Texinfo::Common::multitable_columnfractions($multitable);
my $fractions = '';
if (defined($columnfractions)) {
if (exists($columnfractions->{'extra'}->{'misc_args'}->[$cell_nr-1])) {
my $percent = sprintf('%.0f',
100. * $columnfractions->{'extra'}->{'misc_args'}->[$cell_nr-1]);
my $width = "$percent%";
if ($self->get_conf('_INLINE_STYLE_WIDTH')) {
$fractions = " style=\"width: $width\"";
} else {
$fractions = " width=\"$width\"";
}
}
}
if ($row_cmdname eq 'headitem') {
return "
" . $content . '
';
} else {
return "
" . $content . '
';
}
}
$default_commands_conversion{'tab'} = \&_convert_tab_command;
sub _convert_xref_commands($$$$) {
my ($self, $cmdname, $command, $args) = @_;
# may happen with bogus @-commands without argument, maybe only
# at the end of a document
if (!defined($args)) {
return '';
}
my $tree;
my $name;
if ($cmdname ne 'link' and $cmdname ne 'inforef'
and defined($args->[2])
and defined($args->[2]->{'normal'}) and $args->[2]->{'normal'} ne '') {
$name = $args->[2]->{'normal'};
} elsif (defined($args->[1])
and defined($args->[1]->{'normal'}) and $args->[1]->{'normal'} ne '') {
$name = $args->[1]->{'normal'}
}
my $file_arg;
if ($cmdname eq 'link' or $cmdname eq 'inforef') {
if (defined($args->[2])) {
$file_arg = $args->[2];
}
} elsif (defined($args->[3])) {
$file_arg = $args->[3];
}
my $file;
if (defined($file_arg)
and defined($file_arg->{'filenametext'})
and $file_arg->{'filenametext'} ne '') {
$file = $file_arg->{'filenametext'};
}
my $book;
$book = $args->[4]->{'normal'}
if (defined($args->[4]) and exists($args->[4]->{'normal'})
and $args->[4]->{'normal'} ne '');
my $arg_node = $command->{'contents'}->[0];
# internal reference
if ($cmdname ne 'inforef' and !defined($book) and !defined($file)
and defined($arg_node) and exists($arg_node->{'extra'})
and exists($arg_node->{'extra'}->{'normalized'})
and !exists($arg_node->{'extra'}->{'manual_content'})
and $self->label_command($arg_node->{'extra'}->{'normalized'})) {
my $target_node
= $self->label_command($arg_node->{'extra'}->{'normalized'});
# This is the node if USE_NODES, otherwise this may be the sectioning
# command (if the sectioning command is really associated to the node)
my $target_root = $self->command_root_element_command($target_node);
my $document = $self->get_info('document');
my $associated_section_relations;
my $associated_title_command;
if (defined($document) and $target_node->{'cmdname'} eq 'node') {
my $nodes_list = $document->nodes_list();
my $node_relations
= $nodes_list->[$target_node->{'extra'}->{'node_number'} -1];
$associated_section_relations = $node_relations->{'associated_section'};
$associated_title_command
= $node_relations->{'associated_title_command'};
}
if (!defined($associated_section_relations)
or $associated_section_relations->{'element'} ne $target_root) {
$target_root = $target_node;
}
my $href;
if (!in_string($self)) {
$href = $self->command_href($target_root, undef, $command);
}
if (!defined($name)) {
if ($self->get_conf('xrefautomaticsectiontitle') eq 'on'
and defined($associated_title_command)
# this condition avoids infinite recursions, indeed in that case
# the node will be used and not the section. There should not be
# @*ref in nodes, and even if there are, it does not seems to be
# possible to construct an infinite recursion with nodes only
# as the node must both be a reference target and refer to a specific
# target at the same time, which is not possible.
and not _command_is_in_referred_command_stack($self,
$associated_title_command)) {
if (in_string($self)) {
$name = $self->command_text($associated_title_command, 'string');
} else {
$name = $self->command_text($associated_title_command,
'text_nonumber');
}
} elsif ($target_node->{'cmdname'} eq 'float') {
if (!$self->get_conf('XREF_USE_FLOAT_LABEL')) {
if (in_string($self)) {
# not tested
$name = $self->command_text($target_root, 'string');
} else {
$name = $self->command_text($target_root);
}
}
if (!defined($name) or $name eq '') {
if (defined($args->[0]->{'monospace'})) {
$name = $args->[0]->{'monospace'};
} else {
$name = '';
}
}
} elsif (!$self->get_conf('XREF_USE_NODE_NAME_ARG')
and (defined($self->get_conf('XREF_USE_NODE_NAME_ARG'))
or !in_preformatted_context($self))
# this condition avoids infinite recursions, example with
# USE_NODES=0 and node referring to the section and section referring
# to the node
and not _command_is_in_referred_command_stack($self,
$target_root)) {
if ($self->get_conf('xrefautomaticsectiontitle') eq 'on') {
if (in_string($self)) {
$name = $self->command_name($target_root, 'string');
} else {
$name = $self->command_name($target_root, 'text_nonumber');
}
} elsif (in_string($self)) {
$name = $self->command_text($target_root, 'string');
} else {
$name = $self->command_text($target_root, 'text_nonumber');
}
#die "$target_root $target_root->{'normalized'}" if (!defined($name));
} elsif (defined($args->[0]->{'monospace'})) {
$name = $args->[0]->{'monospace'};
} else {
$name = '';
}
}
my $reference = $name;
if (defined($href)) {
$reference = $self->html_attribute_class('a', [$cmdname])
." href=\"$href\">$name";
}
my $substrings
= { 'reference_name'
=> Texinfo::TreeElement::new({'type' => '_converted',
'text' => $reference}) };
if ($cmdname eq 'pxref') {
$tree = $self->cdt('see {reference_name}', $substrings);
} elsif ($cmdname eq 'xref') {
$tree = $self->cdt('See {reference_name}', $substrings);
} elsif ($cmdname eq 'ref' or $cmdname eq 'link') {
$tree = $self->cdt('{reference_name}', $substrings);
}
} else {
# external reference, including unknown node without file nor book
# We setup a label_element based on the node argument and not directly the
# node argument to be able to use the $file argument
my $label_element;
my $node_content;
if (defined($arg_node) and exists($arg_node->{'extra'})
and exists($arg_node->{'extra'}->{'node_content'})) {
$node_content = $arg_node->{'extra'}->{'node_content'};
$label_element = Texinfo::TreeElement::new(
{'extra' => {'node_content' => $node_content}});
if (exists($arg_node->{'extra'}->{'normalized'})) {
$label_element->{'extra'}->{'normalized'}
= $arg_node->{'extra'}->{'normalized'};
}
}
# file argument takes precedence over the file in the node (file)node entry
if (defined($file)) {
if (!$label_element) {
$label_element = Texinfo::TreeElement::new({'extra' => {}});
} elsif (!exists($label_element->{'extra'})) {
$label_element->{'extra'} = {};
}
$label_element->{'extra'}->{'manual_content'} = $file_arg->{'arg_tree'};
} elsif (defined($arg_node) and exists($arg_node->{'extra'})
and exists($arg_node->{'extra'}->{'manual_content'})) {
my $manual_content = $arg_node->{'extra'}->{'manual_content'};
if (!defined($label_element)) {
$label_element = Texinfo::TreeElement::new({'extra' => {}});
} elsif (!exists($label_element->{'extra'})) {
$label_element->{'extra'} = {};
}
$label_element->{'extra'}->{'manual_content'} = $manual_content;
$file = $self->convert_tree_in_code_context($manual_content,
'node file in ref');
}
if (!defined($name)) {
if (defined($book)) {
if (defined($node_content)) {
my $node_name = $self->convert_tree_in_code_context($node_content,
'node in ref');
if (defined($node_name) and $node_name ne 'Top') {
$name = $node_name;
}
}
} else {
if (defined($label_element)) {
$name = $self->command_text($label_element);
}
if (!defined($name)
and defined($args->[0])
and defined($args->[0]->{'monospace'})
and $args->[0]->{'monospace'} ne ''
and $args->[0]->{'monospace'} ne 'Top') {
# unknown node (and no book nor file) or @inforef without file
$name = $args->[0]->{'monospace'};
}
}
}
my $href;
if (defined($label_element) and !in_string($self)) {
$href = $self->command_href($label_element, undef, $command);
}
my $reference;
my $book_reference;
if (defined($href)) {
# attribute to distiguish links to Texinfo manuals from other links
# and to provide manual name of target
my $manual_name_attribute = '';
if (defined($file)
and not $self->get_conf('NO_CUSTOM_HTML_ATTRIBUTE')) {
$manual_name_attribute = "data-manual=\"".
&{$self->formatting_function('format_protect_text')}($self, $file)."\" ";
}
if (defined($name)) {
$reference = "$name";
} elsif (defined($book)) {
$book_reference = "$book";
}
}
my $substrings;
if (defined($book) and defined($reference)) {
$substrings = {'reference' =>
Texinfo::TreeElement::new({'type' => '_converted', 'text' => $reference}),
'book' =>
Texinfo::TreeElement::new({'type' => '_converted', 'text' => $book })};
if ($cmdname eq 'pxref') {
$tree = $self->cdt('see {reference} in @cite{{book}}', $substrings);
} elsif ($cmdname eq 'xref' or $cmdname eq 'inforef') {
$tree = $self->cdt('See {reference} in @cite{{book}}', $substrings);
} else { # @ref
$tree = $self->cdt('{reference} in @cite{{book}}', $substrings);
}
} elsif (defined($book_reference)) {
$substrings = { 'book_reference' =>
Texinfo::TreeElement::new({'type' => '_converted',
'text' => $book_reference })};
if ($cmdname eq 'pxref') {
$tree = $self->cdt('see @cite{{book_reference}}', $substrings);
} elsif ($cmdname eq 'xref' or $cmdname eq 'inforef') {
$tree = $self->cdt('See @cite{{book_reference}}', $substrings);
} else { # @ref
$tree = $self->cdt('@cite{{book_reference}}', $substrings);
}
} elsif (defined($book) and defined($name)) {
$substrings = {
'section' =>
Texinfo::TreeElement::new({'type' => '_converted', 'text' => $name}),
'book' =>
Texinfo::TreeElement::new({'type' => '_converted', 'text' => $book })};
if ($cmdname eq 'pxref') {
$tree = $self->cdt('see `{section}\' in @cite{{book}}', $substrings);
} elsif ($cmdname eq 'xref' or $cmdname eq 'inforef') {
$tree = $self->cdt('See `{section}\' in @cite{{book}}', $substrings);
} else { # @ref
$tree = $self->cdt('`{section}\' in @cite{{book}}', $substrings);
}
} elsif (defined($book)) { # should seldom or even never happen
$substrings = {'book' =>
Texinfo::TreeElement::new({'type' => '_converted', 'text' => $book })};
if ($cmdname eq 'pxref') {
$tree = $self->cdt('see @cite{{book}}', $substrings);
} elsif ($cmdname eq 'xref' or $cmdname eq 'inforef') {
$tree = $self->cdt('See @cite{{book}}', $substrings);
} else { # @ref
$tree = $self->cdt('@cite{{book}}', $substrings);
}
} elsif (defined($reference)) {
$substrings = { 'reference' =>
Texinfo::TreeElement::new({'type' => '_converted',
'text' => $reference}) };
if ($cmdname eq 'pxref') {
$tree = $self->cdt('see {reference}', $substrings);
} elsif ($cmdname eq 'xref' or $cmdname eq 'inforef') {
$tree = $self->cdt('See {reference}', $substrings);
} else { # @ref
$tree = $self->cdt('{reference}', $substrings);
}
} elsif (defined($name)) {
$substrings = { 'section' =>
Texinfo::TreeElement::new({'type' => '_converted', 'text' => $name}) };
if ($cmdname eq 'pxref') {
$tree = $self->cdt('see `{section}\'', $substrings);
} elsif ($cmdname eq 'xref' or $cmdname eq 'inforef') {
$tree = $self->cdt('See `{section}\'', $substrings);
} else { # @ref
$tree = $self->cdt('`{section}\'', $substrings);
}
}
if (!defined($tree)) {
# May happen if there is no argument
#die "external: $cmdname, ($args), '$name' '$file' '$book' '$href' '$reference'. tree undef";
return '';
}
}
return $self->convert_tree($tree, "convert xref $cmdname");
}
foreach my $cmdname(keys(%ref_commands)) {
$default_commands_conversion{$cmdname} = \&_convert_xref_commands;
}
sub _convert_printindex_command($$$$) {
my ($self, $cmdname, $command, $args, $content) = @_;
return '' if (in_string($self));
my $index_name;
if (exists($command->{'extra'})
and exists($command->{'extra'}->{'misc_args'})
and defined($command->{'extra'}->{'misc_args'}->[0])) {
$index_name = $command->{'extra'}->{'misc_args'}->[0];
} else {
return '';
}
my $index_entries_by_letter
= $self->get_converter_indices_sorted_by_letter();
if (!defined($index_entries_by_letter)
or !exists($index_entries_by_letter->{$index_name})
or !scalar(@{$index_entries_by_letter->{$index_name}})) {
return '';
}
my $document = $self->get_info('document');
my $indices_information;
my $identifiers_target;
if (defined($document)) {
$indices_information = $document->indices_information();
$identifiers_target = $document->labels_information();
}
#foreach my $letter_entry (@{$index_entries_by_letter->{$index_name}}) {
# print STDERR "IDXLETTER $letter_entry->{'letter'}\n";
# foreach my $index_entry (@{$letter_entry->{'entries'}}) {
# print STDERR " ".join('|', keys(%$index_entry))."||| $index_entry->{'key'}\n";
# }
#}
my $index_element_id;
my $current_output_unit = $self->current_output_unit();
if (defined($current_output_unit)
and exists($current_output_unit->{'unit_command'})) {
$index_element_id
= $self->command_id($current_output_unit->{'unit_command'});
}
if (!defined($index_element_id)) {
my ($output_unit, $root_command)
= $self->get_element_root_command_element($command);
if (defined($root_command)) {
$index_element_id = $self->command_id($root_command);
}
if (not defined($index_element_id)) {
# to avoid duplicate names, use a prefix that cannot happen in anchors
my $target_prefix = 't_i';
$index_element_id = $target_prefix;
}
}
my %letter_id;
my %letter_is_symbol;
# First collect the links that are used in entries and in letter summaries
my $symbol_idx = 0;
my $normalized_letter_idx = 0;
my $no_unidecode;
$no_unidecode = 1 if (defined($self->get_conf('USE_UNIDECODE'))
and !$self->get_conf('USE_UNIDECODE'));
my $in_test;
$in_test = 1 if ($self->get_conf('TEST'));
foreach my $letter_entry (@{$index_entries_by_letter->{$index_name}}) {
my $letter = $letter_entry->{'letter'};
my $is_symbol = $letter !~ /^\p{Alpha}/;
$letter_is_symbol{$letter} = $is_symbol;
my $identifier;
if ($is_symbol) {
$symbol_idx++;
$identifier = $index_element_id . "_${index_name}_symbol-$symbol_idx";
} else {
my $normalized_letter =
Texinfo::Convert::NodeNameNormalization::normalize_transliterate_texinfo(
Texinfo::TreeElement::new({'text' => $letter}),
$in_test, $no_unidecode);
my $letter_identifier = $normalized_letter;
if ($normalized_letter ne $letter) {
# disambiguate, as it could be another letter, case of @l, for example
$normalized_letter_idx++;
$letter_identifier = "${normalized_letter}-${normalized_letter_idx}";
}
$identifier = $index_element_id
. "_${index_name}_letter-${letter_identifier}";
}
$letter_id{$letter} = $identifier;
}
# FIXME not part of the API
_new_document_context($self, $cmdname);
my $rule = $self->get_conf('DEFAULT_RULE');
$rule = '' if (!defined($rule));
my %formatted_letters;
# Next do the entries to determine the letters that are not empty
my @letter_entries;
my $result_index_entries = '';
foreach my $letter_entry (@{$index_entries_by_letter->{$index_name}}) {
my $letter = $letter_entry->{'letter'};
my $entries_text = '';
my $entry_nr = -1;
my $first_entry;
# since we normalize, a different formatting will not trigger a new
# formatting of the main entry or a subentry level. This is the
# same for Texinfo TeX
my @prev_normalized_entry_levels;
foreach my $index_entry_ref (@{$letter_entry->{'entries'}}) {
$entry_nr++;
my $main_entry_element = $index_entry_ref->{'entry_element'};
next if ($self->get_conf('NO_TOP_NODE_OUTPUT')
and exists($main_entry_element->{'extra'}->{'element_node'})
and $main_entry_element->{'extra'}->{'element_node'} eq 'Top');
# to avoid double error messages, call
# convert_tree_new_formatting_context below with a multiple_pass
# argument if an entry was already formatted once, for example if
# there are multiple printindex.
my $formatted_index_entry_nr
= $self->get_shared_conversion_state('printindex',
'formatted_index_entries',
$index_entry_ref);
$formatted_index_entry_nr = 0 if (!defined($formatted_index_entry_nr));
$formatted_index_entry_nr++;
$self->set_shared_conversion_state('printindex',
'formatted_index_entries',
$index_entry_ref, $formatted_index_entry_nr);
my $entry_content_element
= Texinfo::Common::index_content_element($main_entry_element);
my $in_code = 0;
$in_code = 1
if ($indices_information->{$index_entry_ref->{'index_name'}}->{'in_code'});
my $entry_ref_tree
= Texinfo::TreeElement::new({'contents' => [$entry_content_element]});
# determine the trees and normalized main entry and subentries, to be
# compared with the previous line normalized entries to determine
# what is already formatted as part of the previous lines and
# what levels should be added. The last level is always formatted.
my @new_normalized_entry_levels;
my @entry_trees;
# NOTE it seems that subentry is not followed in convert_to_normalized
$new_normalized_entry_levels[0]
= uc(Texinfo::Convert::NodeNameNormalization::convert_to_normalized(
$entry_ref_tree));
$entry_trees[0] = $entry_ref_tree;
my $subentry_level = 1;
my $subentries_max_level = 2;
my @subentries_list;
Texinfo::Common::collect_subentries($main_entry_element,
\@subentries_list);
if (scalar(@subentries_list)) {
foreach my $subentry (@subentries_list) {
my $subentry_tree;
my $line_arg = $subentry->{'contents'}->[0];
if (exists($line_arg->{'contents'})
and scalar(@{$line_arg->{'contents'}})) {
my @contents;
foreach my $content (@{$line_arg->{'contents'}}) {
push @contents, $content unless (exists($content->{'cmdname'})
and $content->{'cmdname'} eq 'subentry');
}
$subentry_tree
= Texinfo::TreeElement::new({'contents' => \@contents});
}
if ($subentry_level >= $subentries_max_level) {
# at the max, concatenate the remaining subentries
my $other_subentries_tree
= Texinfo::Convert::Utils::comma_index_subentries_tree($subentry);
if (defined($other_subentries_tree)) {
if (defined($subentry_tree)) {
push @{$subentry_tree->{'contents'}},
@{$other_subentries_tree->{'contents'}};
} else {
$subentry_tree = Texinfo::TreeElement::new(
{'contents' => [@{$other_subentries_tree->{'contents'}}]});
}
}
} elsif (defined($subentry_tree)) {
push @new_normalized_entry_levels,
uc(Texinfo::Convert::NodeNameNormalization::convert_to_normalized(
$subentry_tree));
}
push @entry_trees, $subentry_tree;
$subentry_level++;
last if ($subentry_level > $subentries_max_level);
}
}
my $formatting_context;
if ($in_code) {
$formatting_context = $CTXF_code;
}
#print STDERR join('|', @new_normalized_entry_levels)."\n";
# level/index of the last entry
my $last_entry_level = $subentry_level -1;
my $with_new_formatted_entry = 0;
# format the leading entries when there are subentries (all entries
# except the last one), and when there is not such a subentry already
# formatted on the previous lines.
# Each on a line with increasing indentation, no hyperlink.
for (my $level = 0; $level < $last_entry_level; $level++) {
# skip levels already formatted as part of the previous lines
if (!$with_new_formatted_entry
and scalar(@prev_normalized_entry_levels) > $level
and $prev_normalized_entry_levels[$level]
eq $new_normalized_entry_levels[$level]) {
next;
}
$with_new_formatted_entry = 1;
my $convert_info
= "index $index_name l $letter index entry $entry_nr subentry $level";
my $entry;
if ($formatted_index_entry_nr > 1) {
# call with multiple_pass argument
$entry
= $self->convert_tree_new_formatting_context($entry_trees[$level],
$convert_info, $formatting_context,
"index-formatted-$formatted_index_entry_nr");
} else {
if ($in_code) {
$entry = $self->convert_tree_in_code_context($entry_trees[$level],
$convert_info);
} else {
$entry = $self->convert_tree($entry_trees[$level],
$convert_info);
}
}
$entry = '' .$entry .'' if ($in_code);
my @td_entry_classes = ();
if ($level == 0) {
push @td_entry_classes, "$cmdname-index-entry";
} elsif ($level > 0) {
# indent
push @td_entry_classes, "$cmdname-index-subentry-level-$level";
}
$entries_text .= '
'
# TODO same class used for leading entry rows here and
# last element of the entry with the href below. Could be different.
.$self->html_attribute_class('td', \@td_entry_classes).'>'
. $entry . ''
# empty cell, no section for this line
. "
\n";
}
# last entry, always converted, associated to chapter/node and
# with an hyperlink or to seeentry/seealso
my $entry_tree = $entry_trees[$last_entry_level];
my $referred_entry;
my $seeentry
= Texinfo::Common::index_entry_referred_entry($main_entry_element,
'seeentry');
if (defined($seeentry)) {
$referred_entry = $seeentry;
} else {
$referred_entry
= Texinfo::Common::index_entry_referred_entry($main_entry_element,
'seealso');
}
# index entry with @seeentry or @seealso
if (defined($referred_entry)) {
my $entry;
# for @seealso, to appear where chapter/node ususally appear
my $reference = '';
my $delimiter = '';
my $section_class;
if (defined($seeentry)) {
my $result_tree;
if ($in_code) {
$result_tree
# TRANSLATORS: redirect to another index entry
# TRANSLATORS: @: is discardable and is used to avoid a msgfmt error
= $self->cdt('@code{{main_index_entry}}, @emph{See@:} @code{{seeentry}}',
{'main_index_entry' => $entry_tree,
'seeentry' => $referred_entry});
} else {
$result_tree
# TRANSLATORS: redirect to another index entry
# TRANSLATORS: @: is discardable and used to avoid a msgfmt error
= $self->cdt('{main_index_entry}, @emph{See@:} {seeentry}',
{'main_index_entry' => $entry_tree,
'seeentry' => $referred_entry});
}
my $convert_info
= "index $index_name l $letter index entry $entry_nr seeentry";
if ($formatted_index_entry_nr > 1) {
# call with multiple_pass argument
$entry = $self->convert_tree_new_formatting_context($result_tree,
$convert_info, undef,
"index-formatted-$formatted_index_entry_nr");
} else {
$entry = $self->convert_tree($result_tree, $convert_info);
}
$section_class = "$cmdname-index-see-entry-section";
} else {
my $reference_tree;
if ($in_code) {
# TRANSLATORS: refer to another index entry
$reference_tree
= $self->cdt('@emph{See also} @code{{see_also_entry}}',
{'see_also_entry' => $referred_entry});
} else {
# TRANSLATORS: refer to another index entry
$reference_tree
= $self->cdt('@emph{See also} {see_also_entry}',
{'see_also_entry' => $referred_entry});
}
my $conv_str_entry
= "index $index_name l $letter index entry $entry_nr (with seealso)";
my $conv_str_reference
= "index $index_name l $letter index entry $entry_nr seealso";
if ($formatted_index_entry_nr > 1) {
# call with multiple_pass argument
$entry = $self->convert_tree_new_formatting_context($entry_tree,
$conv_str_entry, $formatting_context,
"index-formatted-$formatted_index_entry_nr");
$reference
= $self->convert_tree_new_formatting_context($reference_tree,
$conv_str_reference, undef,
"index-formatted-$formatted_index_entry_nr");
} else {
if ($in_code) {
$entry = $self->convert_tree_in_code_context($entry_tree,
$conv_str_entry);
} else {
$entry = $self->convert_tree($entry_tree,
$conv_str_entry);
}
$reference = $self->convert_tree($reference_tree,
$conv_str_reference);
}
$entry = '' .$entry .'' if ($in_code);
$delimiter = $self->get_conf('INDEX_ENTRY_COLON');
$section_class = "$cmdname-index-see-also";
}
my @td_entry_classes = ();
if (defined($seeentry)) {
push @td_entry_classes, "$cmdname-index-see-entry";
}
if ($last_entry_level == 0) {
push @td_entry_classes, "$cmdname-index-entry";
} elsif ($last_entry_level > 0) {
push @td_entry_classes,
"$cmdname-index-subentry-level-$last_entry_level";
}
$entries_text .= '
\n";
@prev_normalized_entry_levels = @new_normalized_entry_levels;
} else {
my $entry;
if (!defined($entry_tree)) {
# can happen at least with an empty subentry
$entry = '';
} else {
my $convert_info
= "index $index_name l $letter index entry $entry_nr";
if ($formatted_index_entry_nr > 1) {
# call with multiple_pass argument
$entry = $self->convert_tree_new_formatting_context($entry_tree,
$convert_info, $formatting_context,
"index-formatted-$formatted_index_entry_nr");
} else {
if ($in_code) {
$entry = $self->convert_tree_in_code_context($entry_tree,
$convert_info);
} else {
$entry = $self->convert_tree($entry_tree, $convert_info);
}
}
}
next if ($entry !~ /\S/ and $last_entry_level == 0);
if (!defined($first_entry)) {
$first_entry = $index_entry_ref;
}
@prev_normalized_entry_levels = @new_normalized_entry_levels;
$entry = '' .$entry .'' if ($in_code);
my $target_element;
if (exists($index_entry_ref->{'entry_associated_element'})) {
$target_element = $index_entry_ref->{'entry_associated_element'};
} else {
$target_element = $main_entry_element;
}
my $entry_href = $self->command_href($target_element);
my $formatted_entry = "$entry";
my @td_entry_classes = ();
if ($last_entry_level == 0) {
push @td_entry_classes, "$cmdname-index-entry";
} elsif ($last_entry_level > 0) {
# subentry
push @td_entry_classes, "$cmdname-index-subentry-level-$last_entry_level";
}
$entries_text .= '
'
.$self->html_attribute_class('td', \@td_entry_classes).'>'
. $formatted_entry . $self->get_conf('INDEX_ENTRY_COLON') . '';
my $associated_command;
if ($self->get_conf('NODE_NAME_IN_INDEX')) {
my $associated_command_id
= $main_entry_element->{'extra'}->{'element_node'};
if (defined($associated_command_id)
and defined($identifiers_target)) {
$associated_command = $identifiers_target->{$associated_command_id};
}
if (!defined($associated_command)) {
$associated_command
= $self->command_node($target_element);
}
if (!defined($associated_command)
# do not warn if the entry is in a special region, like titlepage
and not $main_entry_element->{'extra'}->{'element_region'}
and $formatted_index_entry_nr == 1) {
# NOTE $self->in_multiple_conversions() is not checked as printindex
# should not happen in multiple tree conversion, but the error message
# is printed for the first entry formatting only.
$self->converter_line_warn(
sprintf(
__("entry for index `%s' for \@printindex %s outside of any node"),
$index_entry_ref->{'index_name'},
$index_name),
$main_entry_element->{'source_info'});
}
}
if (!defined($associated_command)) {
$associated_command
= $self->command_root_element_command($target_element);
if (!defined($associated_command)) {
# Use Top if not associated command found
$associated_command
= $self->global_direction_unit('Top')->{'unit_command'};
# NOTE the warning here catches the most relevant cases of
# index entry that is not associated to the right command, which
# are very few in the test suite. There is also a warning in the
# parser with a much broader scope with possible overlap, but the
# overlap is not a problem.
# NODE_NAME_IN_INDEX may be undef even with USE_NODES set if the
# converter is called as convert() as in the test suite
if (defined($self->get_conf('NODE_NAME_IN_INDEX'))
and not $self->get_conf('NODE_NAME_IN_INDEX')
# do not warn if the entry is in a special region, like titlepage
and not $main_entry_element->{'extra'}->{'element_region'}
and $formatted_index_entry_nr == 1) {
# NOTE $self->in_multiple_conversions() is not checked as printindex
# should not happen in multiple tree conversion, but the error message
# is printed for the first entry formatting only.
# NOTE the index entry may be associated to a node in that case.
$self->converter_line_warn(
sprintf(
__("entry for index `%s' for \@printindex %s outside of any section"),
$index_entry_ref->{'index_name'},
$index_name),
$main_entry_element->{'source_info'});
}
}
}
$entries_text .=
$self->html_attribute_class('td', ["$cmdname-index-section"]).'>';
if (defined($associated_command)) {
my $associated_command_href
= $self->command_href($associated_command);
my $associated_command_text
= $self->command_text($associated_command);
if (defined($associated_command_href)) {
$entries_text
.= ""
."$associated_command_text";
} elsif (defined($associated_command_text)) {
$entries_text .= $associated_command_text;
}
}
$entries_text .= "
\n";
}
}
# a letter and associated indice entries
if ($entries_text ne '') {
my $formatted_letter;
my $letter_command;
# may not be defined if there are only seeentry/seealso
if (defined($first_entry)) {
my $letter_text;
($letter_text, $letter_command)
= Texinfo::Indices::index_entry_first_letter_text_or_command(
$first_entry);
}
if (defined($letter_command)
and !exists($accent_commands{$letter_command->{'cmdname'}})
and $letter_command->{'cmdname'} ne 'U'
# special case, the uppercasing of that command is not done
# if as a command, while it is done correctly in $letter
and $letter_command->{'cmdname'} ne 'ss') {
my $cmdname = $letter_command->{'cmdname'};
if (exists($letter_no_arg_commands{$cmdname})
and exists($letter_no_arg_commands{uc($cmdname)})) {
$letter_command
= Texinfo::TreeElement::new({'cmdname' => uc($cmdname)});
}
$formatted_letter = $self->convert_tree($letter_command,
"index letter $letter command");
} else {
$formatted_letter
= &{$self->formatting_function('format_protect_text')}($self, $letter);
}
$formatted_letters{$letter} = $formatted_letter;
$result_index_entries .= '
\n";
push @letter_entries, $letter_entry;
}
}
# Do the summary letters linking to the letters done above
my @non_alpha = ();
my @alpha = ();
foreach my $letter_entry (@letter_entries) {
my $letter = $letter_entry->{'letter'};
my $summary_letter_link
= $self->html_attribute_class('a',["summary-letter-$cmdname"])
." href=\"#$letter_id{$letter}\">".$formatted_letters{$letter}
.'';
if ($letter_is_symbol{$letter}) {
push @non_alpha, $summary_letter_link;
} else {
push @alpha, $summary_letter_link;
}
}
if (scalar(@non_alpha) + scalar(@alpha) == 0) {
_pop_document_context($self);
return '';
}
my $non_breaking_space = $self->get_info('non_breaking_space');
# Format the summary letters
my $join = '';
my $non_alpha_text = '';
my $alpha_text = '';
if (scalar(@non_alpha) + scalar(@alpha) > 1) {
$join = " $non_breaking_space \n".$self->get_info('line_break_element')."\n"
if (scalar(@non_alpha) and scalar(@alpha));
if (scalar(@non_alpha)) {
$non_alpha_text = join("\n $non_breaking_space \n", @non_alpha) . "\n";
}
if (scalar(@alpha)) {
$alpha_text = join("\n $non_breaking_space \n", @alpha)
. "\n $non_breaking_space \n";
}
}
my $result = $self->html_attribute_class('div',
[$cmdname, "$index_name-$cmdname"]).">\n";
# format the summary
if (scalar(@non_alpha) + scalar(@alpha) > 1) {
my $summary_header = $self->html_attribute_class('table',
["index-letters-header-$cmdname",
"$index_name-letters-header-$cmdname"]).'>
'
# TRANSLATORS: before list of letters and symbols grouping index entries
. $self->convert_tree($self->cdt('Jump to'), 'Tr letters header text')
. ": $non_breaking_space
" .
$non_alpha_text . $join . $alpha_text . "
\n";
$result .= $summary_header;
}
# now format the index entries
$result
.= $self->html_attribute_class('table', ["index-entries-$cmdname",
"$index_name-entries-$cmdname"]).">\n";
$result .= "
'
# TRANSLATORS: before list of letters and symbols grouping index entries
. $self->convert_tree($self->cdt('Jump to'), 'Tr letters footer text')
. ": $non_breaking_space
"
. $non_alpha_text . $join . $alpha_text . "
\n";
$result .= $summary_footer
}
return $result . "\n";
}
$default_commands_conversion{'printindex'} = \&_convert_printindex_command;
sub _convert_informative_command($$$) {
my ($self, $cmdname, $command) = @_;
return '' if (in_string($self));
Texinfo::Common::set_informative_command_value($self, $command);
return '';
}
foreach my $informative_command (@informative_global_commands) {
$default_commands_conversion{$informative_command}
= \&_convert_informative_command;
}
sub _convert_contents_command($$$) {
my ($self, $cmdname, $command) = @_;
return '' if (in_string($self));
$cmdname = 'shortcontents' if ($cmdname eq 'summarycontents');
Texinfo::Common::set_informative_command_value($self, $command);
my $document = $self->get_info('document');
my $sections_list;
if (defined($document)) {
$sections_list = $document->sections_list();
}
if ($self->get_conf('CONTENTS_OUTPUT_LOCATION') eq 'inline'
and ($cmdname eq 'contents' or $cmdname eq 'shortcontents')
and $self->get_conf($cmdname)
and defined($sections_list)
and scalar(@{$sections_list}) > 1) {
return _contents_inline_element($self, $cmdname, $command);
}
return '';
}
foreach my $contents_command (@contents_commands) {
$default_commands_conversion{$contents_command} = \&_convert_contents_command;
}
sub _convert_def_command($$$$$) {
my ($self, $cmdname, $command, $args, $content) = @_;
$content = '' if (!defined($content));
return $content if (in_string($self));
my @classes;
if ($cmdname ne 'defblock') {
# The def* class is used for the def line, the first-def* class is
# used for the whole block.
my $command_name;
if (exists($Texinfo::Common::def_aliases{$cmdname})) {
$command_name = $Texinfo::Common::def_aliases{$cmdname};
push @classes, "first-$cmdname-alias-first-$command_name";
} else {
$command_name = $cmdname;
}
unshift @classes, "first-$command_name";
} else {
push @classes, $cmdname;
}
push @classes, 'def-block';
if (!$self->get_conf('DEF_TABLE')) {
return $self->html_attribute_class('dl', \@classes).">\n"
. $content ."\n";
} else {
return $self->html_attribute_class('table', \@classes).">\n"
. $content . "\n";
}
}
# Keys are tree element types, values are function references to convert
# elements of that type. Can be overridden accessing
# Texinfo::Config::GNUT_get_types_conversion, setup by
# Texinfo::Config::texinfo_register_type_formatting()
my %default_types_conversion;
foreach my $cmdname (keys(%def_commands), 'defblock') {
if (exists($line_commands{$cmdname})) {
$default_commands_conversion{$cmdname} = \&_convert_def_line_type;
} else {
$default_commands_conversion{$cmdname} = \&_convert_def_command;
}
}
# associate same formatting function for @small* command
# as for the associated @-command
foreach my $small_command (keys(%small_block_associated_command)) {
$default_commands_conversion{$small_command}
= $default_commands_conversion{$small_block_associated_command{$small_command}};
}
# Can be used to check that all the relevant commands are converted
if (0) {
foreach my $cmdname (keys(%Texinfo::Common::all_commands)) {
if (!exists($default_commands_conversion{$cmdname})) {
# should be @if* @*index and item_LINE
if ($cmdname =~ /^if/ or $cmdname =~ /index$/ or $cmdname eq 'item_LINE') {}
else
{
warn "MISSING $cmdname\n";
}
}
}
}
sub _open_node_part_command($$$) {
my ($self, $cmdname, $element) = @_;
if ($self->get_conf('NO_TOP_NODE_OUTPUT')) {
my $in_skipped_node_top
= $self->get_shared_conversion_state('top', 'in_skipped_node_top');
$in_skipped_node_top = 0 if (!defined($in_skipped_node_top));
my $node_element;
if ($cmdname eq 'node') {
$node_element = $element;
} elsif ($cmdname eq 'part') {
my $document = $self->get_info('document');
if (defined($document)) {
my $sections_list = $document->sections_list();
my $part_relations
= $sections_list->[$element->{'extra'}->{'section_number'} -1];
if (exists($part_relations->{'part_following_node'})) {
$node_element = $part_relations->{'part_following_node'}->{'element'};
}
}
}
if (defined($node_element) or $cmdname eq 'part') {
if (defined($node_element) and exists($node_element->{'extra'})
and exists($node_element->{'extra'}->{'normalized'})
and $node_element->{'extra'}->{'normalized'} eq 'Top') {
$in_skipped_node_top = 1;
$self->set_shared_conversion_state('top', 'in_skipped_node_top',
$in_skipped_node_top);
} elsif ($in_skipped_node_top == 1) {
$in_skipped_node_top = -1;
$self->set_shared_conversion_state('top', 'in_skipped_node_top',
$in_skipped_node_top);
}
}
}
return '';
}
$default_commands_open{'node'} = \&_open_node_part_command;
$default_commands_open{'part'} = \&_open_node_part_command;
sub _open_quotation_titlepage_stack($$) {
my ($self, $element_authors_number) = @_;
my $quotation_titlepage_nr = $self->get_shared_conversion_state('quotation',
'quotation_titlepage_stack');
$quotation_titlepage_nr = 0 if (!defined($quotation_titlepage_nr));
$quotation_titlepage_nr++;
$self->set_shared_conversion_state('quotation', 'quotation_titlepage_stack',
$quotation_titlepage_nr);
$self->set_shared_conversion_state('quotation', 'element_authors_number',
$quotation_titlepage_nr, $element_authors_number);
}
sub _open_quotation_command($$$) {
my ($self, $cmdname, $command) = @_;
my $formatted_quotation_arg_to_prepend;
# arguments_line type element
my $arguments_line = $command->{'contents'}->[0];
my $block_line_args = $arguments_line->{'contents'}->[0];
if (exists($block_line_args->{'contents'})
and scalar(@{$block_line_args->{'contents'}})) {
$formatted_quotation_arg_to_prepend
= $self->convert_tree($self->cdt('@b{{quotation_arg}:} ',
{'quotation_arg' => $block_line_args}),
"open $cmdname prepended arg");
}
$self->register_pending_formatted_inline_content($cmdname,
$formatted_quotation_arg_to_prepend);
_open_quotation_titlepage_stack($self, 0);
return '';
}
$default_commands_open{'quotation'} = \&_open_quotation_command;
# associate same opening function for @small* command
# as for the associated @-command
foreach my $small_command (keys(%small_block_associated_command)) {
if (exists($default_commands_open{$small_block_associated_command{$small_command}})) {
$default_commands_open{$small_command}
= $default_commands_open{$small_block_associated_command{$small_command}};
}
}
# Keys are output units types, values are function references to convert
# output units of that type. Can be overridden accessing
# Texinfo::Config::GNUT_get_output_units_conversion, setup by
# Texinfo::Config::texinfo_register_output_unit_formatting()
my %default_output_units_conversion;
sub default_output_unit_conversion($$) {
my ($self, $type) = @_;
return $default_output_units_conversion{$type};
}
sub output_unit_conversion($$) {
my ($self, $type) = @_;
return $self->{'output_units_conversion'}->{$type};
}
sub default_type_conversion($$) {
my ($self, $type) = @_;
return $default_types_conversion{$type};
}
sub type_conversion($$) {
my ($self, $type) = @_;
return $self->{'types_conversion'}->{$type};
}
my %default_types_open;
sub default_type_open($$) {
my ($self, $type) = @_;
return $default_types_open{$type};
}
# Ignored commands
foreach my $type (
'ignorable_spaces_after_command',
'ignorable_spaces_before_command',
'spaces_at_end',
'spaces_before_paragraph',
# may be better not to ignore spaces when a : is postpended
# and the user really wants a space
#'space_at_end_menu_node',
'spaces_after_close_brace') {
$default_types_conversion{$type} = undef;
}
foreach my $type (
'postamble_after_end',
'preamble_before_beginning',
'preamble_before_setfilename',
'arguments_line') {
$default_types_conversion{$type} = undef;
}
sub _convert_paragraph_type($$$$) {
my ($self, $type, $element, $content) = @_;
$content = '' if (!defined($content));
$content = $self->get_associated_formatted_inline_content($element).$content;
if (paragraph_number($self) == 1) {
my $in_format = top_block_command($self);
if ($in_format) {
# no first paragraph in those environment to avoid extra spacing
if ($in_format eq 'itemize'
or $in_format eq 'enumerate'
or $in_format eq 'multitable'
# this should only happen if in @nodedescriptionblock, otherwise
# there are no paragraphs, but preformatted
or $in_format eq 'menu') {
return $content;
}
}
}
return $content if (in_string($self));
if ($content =~ /\S/) {
my $align = $self->in_align();
if ($align and exists($HTML_align_commands{$align})) {
return $self->html_attribute_class('p', [$align.'-paragraph']).">"
.$content."";
} else {
return "
".$content."
";
}
} else {
return '';
}
}
$default_types_conversion{'paragraph'} = \&_convert_paragraph_type;
sub _open_inline_container_type($$$) {
my ($self, $type, $element) = @_;
my $pending_formatted = $self->get_pending_formatted_inline_content();
if (defined($pending_formatted)) {
$self->associate_pending_formatted_inline_content($element, $pending_formatted);
}
return '';
}
$default_types_open{'paragraph'} = \&_open_inline_container_type;
$default_types_open{'preformatted'} = \&_open_inline_container_type;
sub _preformatted_class($) {
my $self = shift;
my $pre_class;
my $pre_classes = $self->preformatted_classes_stack();
foreach my $class (@$pre_classes) {
$pre_class = $class unless (defined($pre_class)
and exists($preformatted_code_commands{$pre_class})
and !(exists($preformatted_code_commands{$class})
or $class eq 'menu'));
}
return $pre_class.'-preformatted';
}
sub _convert_preformatted_type($$$$) {
my ($self, $type, $element, $content) = @_;
$content = '' if (!defined($content));
$content = $self->get_associated_formatted_inline_content($element).$content;
return '' if ($content eq '');
if (top_block_command($self) eq 'multitable') {
$content =~ s/^\s*//;
$content =~ s/\s*$//;
}
if (in_string($self)) {
return $content;
}
my $pre_class;
# menu_entry_description is always in a preformatted container
# in the tree, as the whole menu is meant to be an
# environment where spaces and newlines are preserved.
if (exists($element->{'parent'}->{'type'})
and $element->{'parent'}->{'type'} eq 'menu_entry_description') {
if (!inside_preformatted($self)) {
# If not in preformatted block command,
# we don't preserve spaces and newlines in menu_entry_description,
# instead the whole menu_entry is in a table, so no
in that situation
return $content;
} else {
# if directly in description, we want to avoid the linebreak that
# comes with pre being a block level element, so set a special class
$pre_class = 'menu-entry-description-preformatted';
}
}
$content =~ s/^\n/\n\n/; # a newline immediately after a
is ignored.
$pre_class = _preformatted_class($self) if (!defined($pre_class));
my $result = $self->html_attribute_class('pre', [$pre_class]).'>'
. $content . '
';
return $result;
}
$default_types_conversion{'preformatted'} = \&_convert_preformatted_type;
sub _convert_balanced_braces_type($$$$) {
my ($self, $type, $element, $content) = @_;
$content = '' if (!defined($content));
return $content;
}
$default_types_conversion{'balanced_braces'} = \&_convert_balanced_braces_type;
# use the type and not the index commands names, as they are diverse and
# can be dynamically added, so it is difficult to use as selector for output
# formatting. The command name can be obtained here as $element->{'cmdname'}.
sub _convert_index_entry_command_type($$$$) {
my ($self, $type, $element, $content) = @_;
my $index_id = $self->command_id($element);
# since in_multiple_conversions returning true while multi_expanded_region
# returns undef can only happen when expanding target and heading commands
# text, in which there should not be index commands, using one function
# or the other in the conddition should not give a different result.
if (defined($index_id) and $index_id ne ''
and !in_multiple_conversions($self)
and !in_string($self)) {
my $result = &{$self->formatting_function('format_separate_anchor')}($self,
$index_id, 'index-entry-id');
$result .= "\n" unless (in_preformatted_context($self));
return $result;
}
return '';
}
$default_types_conversion{'index_entry_command'} = \&_convert_index_entry_command_type;
sub _convert_definfoenclose_type($$$$) {
my ($self, $type, $element, $content) = @_;
$content = '' if (!defined($content));
# TODO add a span to mark the original command as a class?
# Not to be done as long as definfoenclose is deprecated.
return &{$self->formatting_function('format_protect_text')}($self,
$element->{'extra'}->{'begin'})
. $content .
&{$self->formatting_function('format_protect_text')}($self,
$element->{'extra'}->{'end'});
}
$default_types_conversion{'definfoenclose_command'}
= \&_convert_definfoenclose_type;
# Note: has an XS override
sub _entity_text {
my $text = shift;
$text =~ s/---/\&mdash\;/g;
$text =~ s/--/\&ndash\;/g;
$text =~ s/``/\&ldquo\;/g;
$text =~ s/''/\&rdquo\;/g;
$text =~ s/'/\&rsquo\;/g;
$text =~ s/`/\&lsquo\;/g;
return $text;
}
sub _convert_text($$$$) {
my ($self, $type, $element, $text) = @_;
if (in_verbatim($self)) {
# API info: using the API to allow for customization would be:
#return &{$self->formatting_function('format_protect_text')}($self, $text);
return _default_format_protect_text($self, $text);
}
return $text if (in_raw($self));
$text = uc($text) if (in_upper_case($self));
# API info: using the API to allow for customization would be:
#$text = &{$self->formatting_function('format_protect_text')}($self, $text);
$text = _default_format_protect_text($self, $text);
# API info: for efficiency, we cache the result of the calls to configuration
# in $self->{'use_unicode_text'}.
# API code conforming would be:
#if ($self->get_conf('OUTPUT_CHARACTERS')
# and $self->get_conf('OUTPUT_ENCODING_NAME')
# and $self->get_conf('OUTPUT_ENCODING_NAME') eq 'utf-8') {
if ($self->{'use_unicode_text'}) {
$text = Texinfo::Convert::Unicode::unicode_text($text,
(in_code($self) or in_math($self)));
} elsif (!in_code($self) and !in_math($self)) {
if ($self->get_conf('USE_NUMERIC_ENTITY')) {
$text = $self->xml_format_text_with_numeric_entities($text);
} elsif ($self->get_conf('USE_ISO')) {
$text = _entity_text($text);
} else {
$text =~ s/``/"/g;
$text =~ s/''/"/g;
$text =~ s/---/\x{1F}/g;
$text =~ s/--/-/g;
$text =~ s/\x{1F}/--/g;
}
}
return $text if (in_preformatted_context($self));
if (in_non_breakable_space($self)) {
my $non_breaking_space = $self->get_info('non_breaking_space');
$text =~ s/\n/ /g;
$text =~ s/ +/$non_breaking_space/g;
} elsif (in_space_protected($self)) {
if (chomp($text)) {
my $line_break_element = $self->get_info('line_break_element');
# protect spaces in line_break_element formatting.
# Note that this case is theoretical right now, as it is not possible
# to redefine the line_break_element and there are no spaces
# in the possible values. However this could be a deficiency of the API,
# it could be better to be able to redefine line_break_element
$line_break_element =~ s/ /\x{1F}/g;
$text .= $line_break_element;
}
# Protect spaces within text
my $non_breaking_space = $self->get_info('non_breaking_space');
$text =~ s/ /$non_breaking_space/g;
# Revert protected spaces in leading html attribute
$text =~ s/\x{1F}/ /g;
}
return $text;
}
$default_types_conversion{'text'} = \&_convert_text;
sub _css_string_convert_text($$$$) {
my ($self, $type, $element, $text) = @_;
$text = uc($text) if (in_upper_case($self));
# need to hide \ otherwise it is protected in protect_text
if (!in_code($self) and !in_math($self)) {
$text =~ s/---/\x{1F}2014 /g;
$text =~ s/--/\x{1F}2013 /g;
$text =~ s/``/\x{1F}201C /g;
$text =~ s/''/\x{1F}201D /g;
$text =~ s/'/\x{1F}2019 /g;
$text =~ s/`/\x{1F}2018 /g;
}
$text
= &{$self->formatting_function('format_protect_text')}($self, $text);
$text =~ s/\x{1F}/\\/g;
return $text;
}
$default_css_string_types_conversion{'text'} = \&_css_string_convert_text;
sub _simplify_text_for_comparison($) {
my $text = shift;
$text =~ s/[^\p{Word}]//g;
return $text;
}
sub _convert_untranslated_def_line_arg_type($$$$) {
my ($self, $type, $element, $content) = @_;
my $translated;
my $category_text = $element->{'contents'}->[0]->{'text'};
if (exists($element->{'extra'})
and exists($element->{'extra'}->{'translation_context'})) {
$translated = $self->pcdt($element->{'extra'}->{'translation_context'},
$category_text);
} else {
$translated = $self->cdt($category_text);
}
my $result = $self->convert_tree($translated, 'translated TEXT');
return $result;
}
$default_types_conversion{'untranslated_def_line_arg'}
= \&_convert_untranslated_def_line_arg_type;
sub _convert_row_type($$$$) {
my ($self, $type, $element, $content) = @_;
$content = '' if (!defined($content));
return $content if (in_string($self));
if ($content =~ /\S/) {
my $result = '
' . $content . '
';
if (exists($element->{'contents'})
and $element->{'contents'}->[0]->{'cmdname'} ne 'headitem') {
# if headitem, end of line added in _convert_multitable_head_type
$result .= "\n";
}
return $result;
} else {
return '';
}
}
$default_types_conversion{'row'} = \&_convert_row_type;
sub _convert_multitable_head_type($$$$) {
my ($self, $type, $element, $content) = @_;
$content = '' if (!defined($content));
return $content if (in_string($self));
if ($content =~ /\S/) {
return '' . $content . '' . "\n";
} else {
return '';
}
}
$default_types_conversion{'multitable_head'} = \&_convert_multitable_head_type;
sub _convert_multitable_body_type($$$$) {
my ($self, $type, $element, $content) = @_;
return $content if (in_string($self));
if ($content =~ /\S/) {
return '' . $content . '' . "\n";
} else {
return '';
}
}
$default_types_conversion{'multitable_body'} = \&_convert_multitable_body_type;
# The node is used, not the nodedescription because it is easier to
# find the node in XS
sub _formatted_nodedescription_nr($$) {
my ($self, $node) = @_;
# update the number of time the node description was formatted
my $formatted_nodedescription_nr
= $self->get_shared_conversion_state('nodedescription',
'formatted_nodedescriptions',
$node);
$formatted_nodedescription_nr = 0
if (!defined($formatted_nodedescription_nr));
$formatted_nodedescription_nr++;
$self->set_shared_conversion_state('nodedescription',
'formatted_nodedescriptions',
$node, $formatted_nodedescription_nr);
return $formatted_nodedescription_nr;
}
sub _convert_menu_entry_type($$$) {
my ($self, $type, $element) = @_;
my $name_entry;
my $menu_description;
my $menu_entry_node;
my $menu_entry_leading_text;
my @menu_entry_separators;
foreach my $arg (@{$element->{'contents'}}) {
if ($arg->{'type'} eq 'menu_entry_leading_text') {
$menu_entry_leading_text = $arg;
} elsif ($arg->{'type'} eq 'menu_entry_name') {
$name_entry = $arg;
} elsif ($arg->{'type'} eq 'menu_entry_description') {
$menu_description = $arg;
} elsif ($arg->{'type'} eq 'menu_entry_separator') {
push @menu_entry_separators, $arg;
} elsif ($arg->{'type'} eq 'menu_entry_node') {
$menu_entry_node = $arg;
}
}
my $href;
my $rel = '';
my $associated_title_command;
my $node_description;
my $long_description = 0;
my $formatted_nodedescription_nr;
# external node
my $external_node;
if (exists($menu_entry_node->{'extra'})
and exists($menu_entry_node->{'extra'}->{'manual_content'})) {
$href = $self->command_href($menu_entry_node, undef, $element);
$external_node = 1;
# may not exist in case of menu entry node consisting only of spaces
} elsif (exists($menu_entry_node->{'extra'})
and exists($menu_entry_node->{'extra'}->{'normalized'})) {
my $node = $self->label_command($menu_entry_node->{'extra'}->{'normalized'});
if ($node) {
my $node_relations;
if ($node->{'cmdname'} eq 'node') {
my $document = $self->get_info('document');
if (defined($document)) {
my $nodes_list = $document->nodes_list();
$node_relations
= $nodes_list->[$node->{'extra'}->{'node_number'} -1];
if (exists($node_relations->{'node_description'})) {
$node_description = $node_relations->{'node_description'};
} elsif (exists($node_relations->{'node_long_description'})) {
$node_description = $node_relations->{'node_long_description'};
$long_description = 1;
}
}
}
# if !NODE_NAME_IN_MENU, we pick the associated title command element
if (!$self->get_conf('NODE_NAME_IN_MENU') and defined($node_relations)) {
$associated_title_command
= $node_relations->{'associated_title_command'};
}
if (defined($associated_title_command)) {
$href = $self->command_href($associated_title_command,
undef, $element);
} else {
$href = $self->command_href($node, undef, $element);
}
if (exists($node->{'extra'}) and $node->{'extra'}->{'isindex'}) {
# Mark the target as an index. See
# http://microformats.org/wiki/existing-rel-values#HTML5_link_type_extensions
$rel = ' rel="index"';
}
if (defined($node_description)
# not menu_description probably cannot happen
and (not defined($menu_description)
# empty description
or (not exists($menu_description->{'contents'})
or (scalar(@{$menu_description->{'contents'}}) == 1
# preformatted inside menu_entry_description
and (not (exists($menu_description->{'contents'}->[0]
->{'contents'}))
or (scalar(@{$menu_description->{'contents'}->[0]
->{'contents'}}) == 1)
and exists($menu_description->{'contents'}->[0]
->{'contents'}->[0]->{'text'})
and $menu_description->{'contents'}->[0]
->{'contents'}->[0]->{'text'} !~ /\S/))))) {
$formatted_nodedescription_nr
= _formatted_nodedescription_nr($self, $node);
}
}
}
my $html_menu_entry_index
= $self->get_shared_conversion_state('menu', 'html_menu_entry_index');
$html_menu_entry_index = 0 if (!defined($html_menu_entry_index));
$html_menu_entry_index++;
$self->set_shared_conversion_state('menu', 'html_menu_entry_index',
$html_menu_entry_index);
my $accesskey = '';
$accesskey = " accesskey=\"$html_menu_entry_index\""
if ($self->get_conf('USE_ACCESSKEY') and $html_menu_entry_index < 10);
my $MENU_SYMBOL = $self->get_conf('MENU_SYMBOL');
my $MENU_ENTRY_COLON = $self->get_conf('MENU_ENTRY_COLON');
my $in_string = in_string($self);
if (inside_preformatted($self) or $in_string) {
my $leading_text = $menu_entry_leading_text->{'text'};
$leading_text =~ s/\*/$MENU_SYMBOL/;
my $result_name_node = $leading_text;
if (defined($name_entry)) {
$result_name_node
.= $self->convert_tree($name_entry,
"menu_arg menu_entry_name preformatted");
my $name_separator = shift @menu_entry_separators;
$result_name_node
.= $self->convert_tree($name_separator,
"menu_arg name separator preformatted");
}
if (defined($menu_entry_node)) {
my $name = $self->convert_tree_in_code_context($menu_entry_node,
"menu_arg menu_entry_node preformatted");
if (defined($href) and !$in_string) {
$result_name_node .= "$name";
} else {
$result_name_node .= $name;
}
}
if (scalar(@menu_entry_separators)) {
my $node_separator = shift @menu_entry_separators;
$result_name_node
.= $self->convert_tree($node_separator,
"menu_arg node separator preformatted");
}
if (not $in_string) {
my $pre_class = _preformatted_class($self);
$result_name_node = $self->html_attribute_class('pre', [$pre_class]).'>'
. $result_name_node . '
';
}
my $description = '';
if ($formatted_nodedescription_nr) {
my $description_element;
if (!$long_description) {
$description_element = $node_description->{'contents'}->[0];
} else {
# nodedescriptionblock
$description_element = Texinfo::TreeElement::new(
{'contents' => $node_description->{'contents'}});
}
my $multiple_formatted;
if ($formatted_nodedescription_nr > 1) {
$multiple_formatted
= 'preformatted-node-description-'.$formatted_nodedescription_nr;
}
$description
.= $self->convert_tree_new_formatting_context($description_element,
'menu_arg node description preformatted',
undef, $multiple_formatted, undef,
'menu');
} elsif ($menu_description) {
$description .= $self->convert_tree($menu_description,
'menu_arg description preformatted');
}
return $result_name_node . $description;
}
my $name;
my $name_no_number;
if (defined($associated_title_command) and defined($href)) {
$name = $self->command_text($associated_title_command);
if ($name ne '') {
$name = "$name";
$name_no_number
= $self->command_text($associated_title_command, 'text_nonumber');
}
}
# A leading menu symbol is only inserted if the section name is not
# used since the section name usually comes with a section number (unless
# NUMBER_SECTIONS is 0, or the section is unnumbered/heading/xrefname)
if (!defined($name) or $name eq '') {
if (defined($name_entry)) {
$name = $self->convert_tree($name_entry, 'convert menu_entry_name');
}
if (!defined($name) or $name eq '') {
if (exists($menu_entry_node->{'extra'})
and exists($menu_entry_node->{'extra'}->{'manual_content'})) {
$name = $self->command_text($menu_entry_node);
} elsif (exists($menu_entry_node->{'extra'})
and exists($menu_entry_node->{'extra'}->{'node_content'})) {
$name = $self->convert_tree_in_code_context(
Texinfo::TreeElement::new({
'contents' => [$menu_entry_node->{'extra'}->{'node_content'}]}),
'menu_arg name');
} else {
$name = '';
}
}
$name =~ s/^\s*//;
$name_no_number = $name;
if (defined($href)) {
$name = "$name";
}
$name = "$MENU_SYMBOL ".$name;
}
my $description = '';
if ($formatted_nodedescription_nr) {
my $description_element;
if (!$long_description) {
$description_element = $node_description->{'contents'}->[0];
} else {
# nodedescriptionblock
$description_element = Texinfo::TreeElement::new(
{'contents' => $node_description->{'contents'}});
}
my $multiple_formatted;
if ($formatted_nodedescription_nr > 1) {
$multiple_formatted
= 'node-description-'.$formatted_nodedescription_nr;
}
$description
= $self->convert_tree_new_formatting_context($description_element,
'menu_arg node description', undef,
$multiple_formatted, undef, 'menu');
} elsif (defined($menu_description)) {
$description = $self->convert_tree($menu_description,
'menu_arg description');
}
my $non_breaking_space = $self->get_info('non_breaking_space');
return '
'."\n";
}
}
$default_types_conversion{'before_item'} = \&_convert_before_item_type;
sub _convert_table_term_type($$$$) {
my ($self, $type, $element, $content) = @_;
$content = '' if (!defined($content));
return '
'.$content;
}
$default_types_conversion{'table_term'} = \&_convert_table_term_type;
sub _convert_def_line_type($$$$) {
my ($self, $type, $element, $content) = @_;
if (in_string($self)) {
# should probably never happen
return &{$self->formatting_function('format_protect_text')}($self,
Texinfo::Convert::Text::convert_to_text(
$element, $self->{'convert_text_options'}));
}
my $index_label = '';
my $index_id = $self->command_id($element);
# since in_multiple_conversions returning true while multi_expanded_region
# returns undef can only happen when expanding target and heading commands
# text, in which there should not be @def*, using one function
# or the other in the conddition should not give a different result.
if (defined($index_id) and $index_id ne ''
and !in_multiple_conversions($self)) {
$index_label = " id=\"$index_id\"";
}
my ($category_element, $class_element,
$type_element, $name_element, $arguments)
= Texinfo::Convert::Utils::definition_arguments_content($element);
my $original_def_cmdname = $element->{'extra'}->{'original_def_cmdname'};
my $original_command_name;
my $alias_class;
if (exists($Texinfo::Common::def_aliases{$original_def_cmdname})) {
$original_command_name = $Texinfo::Common::def_aliases{$original_def_cmdname};
$alias_class = "$original_def_cmdname-alias-$original_command_name";
} else {
$original_command_name = $original_def_cmdname;
}
my $def_command = $element->{'extra'}->{'def_command'};
my $base_command_name;
if (exists($Texinfo::Common::def_aliases{$def_command})) {
$base_command_name
= $Texinfo::Common::def_aliases{$def_command};
} else {
$base_command_name = $def_command;
}
my @classes = ();
push @classes, $original_command_name;
if (defined($alias_class)) {
push @classes, $alias_class;
}
if ($base_command_name ne $original_command_name) {
push @classes, "def-cmd-$base_command_name";
}
push @classes, 'def-line';
my $def_call = '';
if (defined($type_element)) {
my $explanation = "DEF_TYPE $def_command";
my $type_text = $self->convert_tree_in_code_context($type_element,
$explanation);
if ($type_text ne '') {
$def_call .= $self->html_attribute_class('code', ['def-type']).'>'.
$type_text .'';
}
if (($base_command_name eq 'deftypefn'
or $base_command_name eq 'deftypeop')
and $self->get_conf('deftypefnnewline')
and $self->get_conf('deftypefnnewline') eq 'on') {
$def_call .= $self->get_info('line_break_element') . ' ';
} elsif ($type_text ne '') {
$def_call .= ' ';
}
}
if (defined($name_element)) {
my $def_name_text = $self->convert_tree_in_code_context($name_element,
"DEF_NAME $def_command");
$def_call .= $self->html_attribute_class('strong', ['def-name']).'>'.
$def_name_text .'';
}
if (defined($arguments)) {
my $explanation = "DEF_ARGS $def_command";
# arguments not only metasyntactic variables
# (deftypefn, deftypevr, deftypeop, deftypecv)
if (exists($Texinfo::Common::def_no_var_arg_commands{$base_command_name})) {
my $arguments_formatted = $self->convert_tree_in_code_context($arguments,
$explanation);
if ($arguments_formatted =~ /\S/) {
$def_call .= ' ' unless($element->{'extra'}->{'omit_def_name_space'});
$def_call .= $self->html_attribute_class('code',
['def-code-arguments']).'>'
. $arguments_formatted.'';
}
} else {
# only metasyntactic variable arguments (deffn, defvr, deftp, defop, defcv)
# TODO not in API
# Has an effect only for those @def* in @example and similar block
# commands that sets code expansion.
_set_code_context($self, 0);
my $arguments_formatted = $self->convert_tree($arguments, $explanation);
_pop_code_context($self);
if ($arguments_formatted =~ /\S/) {
$def_call .= ' ' unless($element->{'extra'}->{'omit_def_name_space'});
$def_call .= $self->html_attribute_class('var',
['def-var-arguments']).'>'
. $arguments_formatted .'';
}
}
}
if ($self->get_conf('DEF_TABLE')) {
my $category_result = '';
my $def_category_tree
= Texinfo::Convert::Utils::definition_category_tree($element,
$self->{'current_lang_translations'},
$self->get_conf('COMMAND_LINE_ENCODING'),
$self->get_conf('DEBUG'), $self);
$category_result
= $self->convert_tree($def_category_tree)
if (defined($def_category_tree));
return $self->html_attribute_class('tr', \@classes)
. "$index_label>".$self->html_attribute_class('td', ['call-def']).'>'
. $def_call . ''.$self->html_attribute_class('td', ['category-def'])
. '>' . '[' . $category_result . ']' . "\n";
}
my $result = $self->html_attribute_class('dt', \@classes) . "$index_label>";
if (defined($category_element)) {
my $e_category_tree;
if (defined($class_element)) {
my $substrings = {'category' => $category_element,
'class' => $class_element};
if ($base_command_name eq 'deftypeop'
and defined($type_element)
and $self->get_conf('deftypefnnewline')
and $self->get_conf('deftypefnnewline') eq 'on') {
$e_category_tree = $self->cdt('{category} on @code{{class}}:@* ',
$substrings);
} elsif ($base_command_name eq 'defop'
or $base_command_name eq 'deftypeop') {
$e_category_tree = $self->cdt('{category} on @code{{class}}: ',
$substrings);
} elsif ($base_command_name eq 'defcv'
or $base_command_name eq 'deftypecv') {
$e_category_tree = $self->cdt('{category} of @code{{class}}: ',
$substrings);
}
} else {
my $substrings = {'category' => $category_element};
if (defined($type_element)
and ($base_command_name eq 'deftypefn'
or $base_command_name eq 'deftypeop')
and $self->get_conf('deftypefnnewline')
and $self->get_conf('deftypefnnewline') eq 'on') {
# TODO if in @def* in @example and with @deftypefnnewline
# on there is no effect of @deftypefnnewline on, as @* in
# preformatted environment becomes an end of line, but the def*
# line is not in a preformatted environment. There should be
# an explicit in that case. Probably requires changing
# the conversion of @* in a @def* line in preformatted, nothing
# really specific of @deftypefnnewline on.
$e_category_tree = $self->cdt('{category}:@* ', $substrings);
} else {
$e_category_tree = $self->cdt('{category}: ', $substrings);
}
}
if (defined($e_category_tree)) {
my $open = $self->html_attribute_class('span', ['category-def']);
if ($open ne '') {
$result .= $open.'>';
}
my $explanation = "DEF_CATEGORY $def_command";
$result .= $self->convert_tree($e_category_tree, $explanation);
if ($open ne '') {
$result .= '';
}
}
}
my $anchor = _get_copiable_anchor($self, $index_id);
if (defined($anchor)) {
$result .= '';
}
$result .= $def_call;
if (defined($anchor)) {
$result .= $anchor . '';
}
$result .= "
\n";
return $result;
}
sub _get_copiable_anchor($$) {
my ($self, $id) = @_;
if (defined($id) and $id ne '' and $self->get_conf('COPIABLE_LINKS')) {
my $paragraph_symbol = $self->get_info('paragraph_symbol');
return $self->html_attribute_class('a', ['copiable-link'])
." href=\"#$id\"> $paragraph_symbol";
}
return undef;
}
$default_types_conversion{'def_line'} = \&_convert_def_line_type;
sub _convert_def_item_type($$$$) {
my ($self, $type, $element, $content) = @_;
$content = '' if (!defined($content));
return $content if (in_string($self));
if ($content =~ /\S/) {
if (! $self->get_conf('DEF_TABLE')) {
return '
' . $content . '
';
} else {
return '
' . $content . '
';
}
}
}
$default_types_conversion{'def_item'} = \&_convert_def_item_type;
$default_types_conversion{'inter_def_item'} = \&_convert_def_item_type;
$default_types_conversion{'before_defline'} = \&_convert_def_item_type;
sub _convert_table_definition_type($$$$) {
my ($self, $type, $element, $content) = @_;
$content = '' if (!defined($content));
return $content if (in_string($self));
if ($content =~ /\S/) {
return '
' . $content . '
'."\n";
}
}
$default_types_conversion{'table_definition'}
= \&_convert_table_definition_type;
$default_types_conversion{'inter_item'}
= \&_convert_table_definition_type;
# Function for converting special output units
sub _convert_special_unit_type($$$$) {
my ($self, $type, $output_unit, $content) = @_;
$content = '' if (!defined($content));
if (in_string($self)) {
return '';
}
my $result = '';
my $special_unit_variety = $output_unit->{'special_unit_variety'};
my $closed_strings = $self->close_registered_sections_level(
$self->current_filename(), 0);
$result .= join('', @{$closed_strings});
my $special_unit_body
.= &{$self->special_unit_body_formatting($special_unit_variety)}($self,
$special_unit_variety, $output_unit);
# This may happen with footnotes in regions that are not expanded,
# like @copying or @titlepage
if ($special_unit_body eq '') {
return '';
}
my $unit_command = $output_unit->{'unit_command'};
my $id = $self->command_id($unit_command);
my $class_base
= $self->special_unit_info('class', $special_unit_variety);
$result .= $self->html_attribute_class('div', ["element-${class_base}"]);
if ($id ne '') {
$result .= " id=\"$id\"";
}
$result .= ">\n";
if ($self->get_conf('HEADERS')
# first in page
or (exists($output_unit->{'unit_filename'})
and $self->count_elements_in_filename('current',
$output_unit->{'unit_filename'}) == 1)) {
$result .= &{$self->formatting_function('format_navigation_header')}($self,
$self->get_conf('MISC_BUTTONS'), undef, $unit_command);
}
my $heading = $self->command_text($unit_command);
my $level = $self->get_conf('CHAPTER_HEADER_LEVEL');
if ($special_unit_variety eq 'footnotes') {
$level = $self->get_conf('FOOTNOTE_SEPARATE_HEADER_LEVEL');
}
$result .= &{$self->formatting_function('format_heading_text')}($self,
undef, [$class_base.'-heading'], $heading, $level)."\n";
$result .= $special_unit_body . '';
$result .= &{$self->formatting_function('format_element_footer')}($self,
$type, $output_unit, $content, $unit_command);
return $result;
}
$default_output_units_conversion{'special_unit'}
= \&_convert_special_unit_type;
# Function for converting the output units. The node and associated section
# appear together in the output unit. $OUTPUT_UNIT was created in this
# module (in _prepare_conversion_units), it's not a tree element (created
# by the parser).
# $CONTENT is the contents of the output unit, already converted.
sub _convert_unit_type($$$$) {
my ($self, $type, $output_unit, $content) = @_;
$content = '' if (!defined($content));
if (in_string($self)) {
return $content;
}
my $result = '';
if (not exists($output_unit->{'tree_unit_directions'})
or not exists($output_unit->{'tree_unit_directions'}->{'prev'})) {
my $global_commands;
my $document = $self->get_info('document');
if (defined($document)) {
$global_commands = $document->global_commands_information();
}
if (!(defined($global_commands)
and exists($global_commands->{'maketitle'}))) {
$result .= $self->get_info('title_titlepage');
}
if (not exists($output_unit->{'tree_unit_directions'})
or not exists($output_unit->{'tree_unit_directions'}->{'next'})) {
# only one unit, use simplified formatting
$result .= $content;
# if there is one unit it also means that there is no formatting
# of footnotes in a separate unit. And if footnotestyle is end
# the footnotes won't be done in format_element_footer either.
$result
.= &{$self->formatting_function('format_footnotes_segment')}($self);
$result .= $self->get_conf('DEFAULT_RULE') ."\n"
if ($self->get_conf('PROGRAM_NAME_IN_FOOTER')
and defined($self->get_conf('DEFAULT_RULE')));
# do it here, as it is won't be done at end of page in
# format_element_footer
my $closed_strings = $self->close_registered_sections_level(
$self->current_filename(), 0);
$result .= join('', @{$closed_strings});
return $result;
}
}
$result .= $content;
my $unit_command;
if (exists($output_unit->{'unit_command'})) {
$unit_command = $output_unit->{'unit_command'};
}
$result .= &{$self->formatting_function('format_element_footer')}($self, $type,
$output_unit, $content, $unit_command);
return $result;
}
$default_output_units_conversion{'unit'} = \&_convert_unit_type;
sub _contents_shortcontents_in_title($) {
my $self = shift;
my $result = '';
my $document = $self->get_info('document');
my $sections_list;
if (defined($document)) {
$sections_list = $document->sections_list();
}
if (defined($sections_list)
and scalar(@{$sections_list}) > 1
and $self->get_conf('CONTENTS_OUTPUT_LOCATION') eq 'after_title') {
foreach my $cmdname ('shortcontents', 'contents') {
if ($self->get_conf($cmdname)) {
my $contents_text = _contents_inline_element($self, $cmdname, undef);
if ($contents_text ne '') {
$result .= $contents_text;
my $rule = $self->get_conf('DEFAULT_RULE');
if (defined($rule)) {
$result .= $rule ."\n";
}
}
}
}
}
return $result;
}
sub _format_maketile($$) {
my ($self, $document) = @_;
my $document_info
= Texinfo::Convert::Utils::get_document_documentinfo($document);
if (defined($document_info)) {
my @contents;
my $titlepage_text
= $self->html_attribute_class('div', ['maketitle-titlepage']).">\n";
foreach my $cmdname ('title', 'subtitle', 'author') {
if (exists($document_info->{$cmdname})) {
push @contents, @{$document_info->{$cmdname}};
}
}
my $element = Texinfo::TreeElement::new({'contents' => \@contents});
# we do not need to collect the author commands in documentinfo, so
# we use a little trick to initialize the authors number to -1
# to mean that we are in documentinfo
_open_quotation_titlepage_stack($self, -1);
my $quotation_titlepage_nr = $self->get_shared_conversion_state('quotation',
'quotation_titlepage_stack');
$titlepage_text .= $self->convert_tree($element, 'format maketitle');
$quotation_titlepage_nr--;
$self->set_shared_conversion_state('quotation',
'quotation_titlepage_stack',
$quotation_titlepage_nr);
$titlepage_text .= "\n";
return $titlepage_text;
}
return undef;
}
# Convert @titlepage. Falls back to simpletitle.
sub _default_format_titlepage($) {
my $self = shift;
my $titlepage_text;
my $global_commands;
my $document = $self->get_info('document');
if (defined($document)) {
$global_commands = $document->global_commands_information();
}
if (defined($global_commands) and exists($global_commands->{'titlepage'})) {
# we do not need to collect the author commands in titlepage, so
# we use a little trick to initialize the authors number to -1
# to mean that we are in titlepage
_open_quotation_titlepage_stack($self, -1);
$titlepage_text = $self->convert_tree(
Texinfo::TreeElement::new(
{'contents' => $global_commands->{'titlepage'}->{'contents'}}),
'convert titlepage');
my $quotation_titlepage_nr = $self->get_shared_conversion_state('quotation',
'quotation_titlepage_stack');
$quotation_titlepage_nr--;
$self->set_shared_conversion_state('quotation',
'quotation_titlepage_stack',
$quotation_titlepage_nr);
} elsif (defined($global_commands)
and exists($global_commands->{'maketitle'})
and defined($document)) {
$titlepage_text = _format_maketile($self, $document);
} else {
my $simpletitle_tree = $self->get_info('simpletitle_tree');
if (defined($simpletitle_tree)) {
my $simpletitle_command_name
= $self->get_info('simpletitle_command_name');
my $title_text
= $self->convert_tree_new_formatting_context($simpletitle_tree,
"$simpletitle_command_name simpletitle");
$titlepage_text
= &{$self->formatting_function('format_heading_text')}($self,
$simpletitle_command_name,
[$simpletitle_command_name], $title_text, 0);
}
}
my $result = '';
if (defined($titlepage_text)) {
$result .= $titlepage_text;
my $rule = $self->get_conf('DEFAULT_RULE');
if (defined($rule)) {
$result .= $rule."\n";
}
}
$result .= _contents_shortcontents_in_title($self);
return $result;
}
sub _default_format_title_titlepage($) {
my $self = shift;
if ($self->get_conf('SHOW_TITLE')) {
if ($self->get_conf('USE_TITLEPAGE_FOR_TITLE')) {
return &{$self->formatting_function('format_titlepage')}($self);
} else {
my $result = '';
my $simpletitle_tree = $self->get_info('simpletitle_tree');
if (defined($simpletitle_tree)) {
my $simpletitle_command_name
= $self->get_info('simpletitle_command_name');
my $title_text
= $self->convert_tree_new_formatting_context($simpletitle_tree,
"$simpletitle_command_name simpletitle");
$result
.= &{$self->formatting_function('format_heading_text')}($self,
$simpletitle_command_name,
[$simpletitle_command_name], $title_text, 0);
}
$result .= _contents_shortcontents_in_title($self);
return $result;
}
}
return '';
}
# for output units, both normal and special
sub _default_format_element_footer($$$$;$) {
my ($self, $type, $output_unit, $content, $command) = @_;
my $result = '';
my $is_top = $self->unit_is_top_output_unit($output_unit);
my $next_is_top = (exists($output_unit->{'tree_unit_directions'}->{'next'})
and $self->unit_is_top_output_unit(
$output_unit->{'tree_unit_directions'}->{'next'}));
my $next_is_special
= (exists($output_unit->{'tree_unit_directions'}->{'next'})
and exists($output_unit->{'tree_unit_directions'}->{'next'}
->{'unit_type'})
and $output_unit->{'tree_unit_directions'}->{'next'}
->{'unit_type'} eq 'special_unit');
my $is_end_page = (!exists($output_unit->{'tree_unit_directions'}->{'next'})
or (exists($output_unit->{'unit_filename'})
and $output_unit->{'unit_filename'}
ne $output_unit->{'tree_unit_directions'}->{'next'}
->{'unit_filename'}
and $self->count_elements_in_filename('remaining',
$output_unit->{'unit_filename'}) == 1));
my $is_special = (defined($output_unit->{'unit_type'})
and $output_unit->{'unit_type'} eq 'special_unit');
my $split = $self->get_conf('SPLIT');
if (($is_end_page or $next_is_top or $next_is_special or $is_top)
and $self->get_conf('VERTICAL_HEAD_NAVIGATION')
and (!defined($split) or $split ne 'node'
or $self->get_conf('HEADERS') or $is_special or $is_top)) {
$result .= "
"."\n";
}
my $buttons;
if ($is_end_page) {
my $closed_strings = $self->close_registered_sections_level(
$self->current_filename(), 0);
$result .= join('', @{$closed_strings});
my $split = $self->get_conf('SPLIT');
# setup buttons for navigation footer
if (($is_top or $is_special)
and ($split or !$self->get_conf('MONOLITHIC'))
and (($self->get_conf('HEADERS')
or (defined($split) and $split ne 'node')))) {
if ($is_top) {
$buttons = $self->get_conf('TOP_FOOTER_BUTTONS');
} else {
$buttons = $self->get_conf('MISC_BUTTONS');
}
} elsif (defined($split) and $split eq 'section') {
$buttons = $self->get_conf('SECTION_FOOTER_BUTTONS');
} elsif (defined($split) and $split eq 'chapter') {
$buttons = $self->get_conf('CHAPTER_FOOTER_BUTTONS');
} elsif (defined($split) and $split eq 'node') {
if ($self->get_conf('HEADERS')) {
my $no_footer_word_count;
if ($self->get_conf('WORDS_IN_PAGE')) {
$content = '' if (!defined($content));
# NOTE it would have been better to skip a leading space, but
# it cannot happen as the content should start with an HTML element.
# splitting at [\h\v] may have been relevant, but then the result
# would be different from XS code result and could give different
# results in perl in some cases.
# NOTE it seems that NO-BREAK SPACE and NEXT LINE (NEL) may
# not be in \h and \v in some case, but not sure when.
# It is supposed to be explained but it is not very clear
# https://perldoc.perl.org/perlrecharclass#Whitespace
# [\h\v]+ does not match on solaris 11 with perl 5.10.1, not sure
# why.
#my @cnt = split(/[\h\v]+/, $content);
# Use an explicit list to match the same in all versions of perl.
# TODO starting in Perl v5.14 could be replaced by \s\cK (with /a)
# TODO starting in Perl v5.18 could be replaced by \s (with /a)
my @cnt = split(/[\t\n\f\r \cK]+/, $content);
if (scalar(@cnt) < $self->get_conf('WORDS_IN_PAGE')) {
$no_footer_word_count = 1;
}
}
$buttons = $self->get_conf('NODE_FOOTER_BUTTONS')
unless ($no_footer_word_count);
}
}
}
# NOTE the following condition is almost a duplication of the
# condition appearing in end_page except that the file counter
# needs not to be 1
if (!exists($output_unit->{'tree_unit_directions'}->{'next'})
or (exists($output_unit->{'unit_filename'})
and $output_unit->{'unit_filename'}
ne $output_unit->{'tree_unit_directions'}->{'next'}
->{'unit_filename'})) {
my $footnotestyle = $self->get_conf('footnotestyle');
if (!defined($footnotestyle) or $footnotestyle ne 'separate') {
$result
.= &{$self->formatting_function('format_footnotes_segment')}($self);
}
}
if ($buttons or !$is_end_page
or $self->get_conf('PROGRAM_NAME_IN_FOOTER')) {
my $rule;
my $split = $self->get_conf('SPLIT');
if (!$is_end_page and ($is_top or $next_is_top or ($next_is_special
and !$is_special))) {
$rule = $self->get_conf('BIG_RULE');
} elsif (!$buttons or $is_top or $is_special
or ($is_end_page and defined($split)
and ($split eq 'chapter' or $split eq 'section'))
or (defined($split) and $split eq 'node'
and $self->get_conf('HEADERS'))) {
$rule = $self->get_conf('DEFAULT_RULE');
}
$result .= "$rule\n" if (defined($rule) and $rule ne '');
}
if ($buttons) {
my $cmdname;
$cmdname = $command->{'cmdname'} if (defined($command)
and exists($command->{'cmdname'}));
$result .= &{$self->formatting_function('format_navigation_panel')}($self,
$buttons, $cmdname, $command);
}
return $result;
}
# if $document_global_context is set, it means that the formatting
# is not done within the document formatting flow, but the formatted
# output may still end up in the document. In particular for
# command_text() which caches its computations.
sub _new_document_context($$;$$$) {
my ($self, $context, $context_type, $document_global_context,
$block_command) = @_;
my $doc_context =
{'context' => $context,
'formatting_context' => [{'context_name' => '_format'}],
'composition_context' => [''],
'preformatted_context' => [0],
'inside_preformatted' => 0,
'document_global_context' => $document_global_context,
'block_commands' => [],
};
if (defined($document_global_context)) {
$self->{'document_global_context_counter'}++;
}
if (defined($context_type) and ($context_type & $CTXF_code)) {
$doc_context->{'monospace'} = [1];
} else {
$doc_context->{'monospace'} = [0];
}
if (defined($block_command)) {
push @{$doc_context->{'block_commands'}},
$block_command;
}
if (defined($context_type) and ($context_type & $CTXF_string)) {
$doc_context->{'string'}++;
}
push @{$self->{'document_context'}}, $doc_context;
}
sub _pop_document_context($) {
my $self = shift;
my $context = pop @{$self->{'document_context'}};
if (defined($context->{'document_global_context'})) {
$self->{'document_global_context_counter'}--;
}
}
sub _set_string_context($) {
my $self = shift;
$self->{'document_context'}->[-1]->{'string'}++;
}
sub _unset_string_context($) {
my $self = shift;
$self->{'document_context'}->[-1]->{'string'}--;
}
sub _set_raw_context($) {
my $self = shift;
$self->{'document_context'}->[-1]->{'raw'}++;
}
sub _unset_raw_context($) {
my $self = shift;
$self->{'document_context'}->[-1]->{'raw'}--;
}
sub _set_multiple_conversions($$) {
my ($self, $multiple_pass) = @_;
push @{$self->{'multiple_pass'}}, $multiple_pass;
}
sub _unset_multiple_conversions($) {
my $self = shift;
pop @{$self->{'multiple_pass'}};
}
# can be set through Texinfo::Config::texinfo_register_file_id_setting_function
my %customizable_file_id_setting_references;
foreach my $customized_reference ('external_target_split_name',
'external_target_non_split_name',
'label_target_name', 'node_file_name',
'redirection_file_names',
'sectioning_command_target_name',
'special_unit_target_file_name', 'unit_file_name') {
$customizable_file_id_setting_references{$customized_reference} = 1;
}
# Functions accessed with e.g. 'format_heading_text'.
# used in Texinfo::Config
%default_formatting_references = (
'format_begin_file' => \&_default_format_begin_file,
'format_button' => \&_default_format_button,
'format_button_icon_img' => \&_default_format_button_icon_img,
'format_css_lines' => \&_default_format_css_lines,
'format_comment' => \&_default_format_comment,
'format_contents' => \&_default_format_contents,
'format_element_header' => \&_default_format_element_header,
'format_element_footer' => \&_default_format_element_footer,
'format_end_file' => \&_default_format_end_file,
'format_footnotes_segment' => \&_default_format_footnotes_segment,
'format_footnotes_sequence' => \&_default_format_footnotes_sequence,
'format_single_footnote' => \&_default_format_single_footnote,
'format_heading_text' => \&_default_format_heading_text,
'format_navigation_header' => \&_default_format_navigation_header,
'format_navigation_panel' => \&_default_format_navigation_panel,
'format_node_redirection_page' => \&_default_format_node_redirection_page,
'format_program_string' => \&_default_format_program_string,
'format_protect_text' => \&_default_format_protect_text,
'format_separate_anchor' => \&_default_format_separate_anchor,
'format_titlepage' => \&_default_format_titlepage,
'format_title_titlepage' => \&_default_format_title_titlepage,
'format_translate_message' => undef,
);
# not up for customization
%default_css_string_formatting_references = (
'format_protect_text' => \&_default_css_string_format_protect_text,
);
%defaults_format_special_unit_body_contents = (
'contents' => \&_default_format_special_body_contents,
'about' => \&_default_format_special_body_about,
'footnotes' => \&_default_format_special_body_footnotes,
'shortcontents' => \&_default_format_special_body_shortcontents,
);
# $RESET_CONTEXT is the context being set or reset.
# $REF_CONTEXT is the context that should be used for defaults, if
# defined and there is no information for $CMDNAME $RESET_CONTEXT.
# In that case, 'unset' should exist for $RESET_CONTEXT.
sub _reset_unset_no_arg_commands_formatting_context($$$$;$) {
my ($self, $cmdname, $reset_context, $ref_context, $translate) = @_;
my $conversion_contexts = $self->{'no_arg_commands_formatting'}->{$cmdname};
# should never happen as unset is set at initialization in that case
#if (!exists($conversion_contexts->{$reset_context})) {
# die "Non-existing no_arg_commands_formatting $cmdname $reset_context";
#}
my $no_arg_command_context = $conversion_contexts->{$reset_context};
if (defined($ref_context) and exists($no_arg_command_context->{'unset'})) {
foreach my $key (keys(%{$conversion_contexts->{$ref_context}})) {
# If present, both 'translated_converted' and (possibly translated)
# 'text' are referred to.
# In case of 'text', if 'translated_tree' is referred to and
# $translate is set, the 'text' will be replaced just below.
$no_arg_command_context->{$key}
= $conversion_contexts->{$ref_context}->{$key};
}
}
if ($translate
and exists($conversion_contexts->{'translated_tree'})
and not exists($no_arg_command_context->{'translated_converted'})) {
my $translated_tree
= $conversion_contexts->{'translated_tree'};
my $translation_result;
my $explanation = "Translated NO ARG \@$cmdname ctx $reset_context";
my $context_str = "Tr $cmdname ctx $reset_context";
if ($reset_context eq 'normal') {
$translation_result
= $self->convert_tree($translated_tree, $explanation);
} elsif ($reset_context eq 'preformatted') {
# there does not seems to be anything simpler...
my $preformatted_cmdname = 'example';
_new_document_context($self, $context_str);
_open_command_update_context($self, $preformatted_cmdname);
$translation_result
= $self->convert_tree($translated_tree, $explanation);
_convert_command_update_context($self, $preformatted_cmdname);
_pop_document_context($self);
} elsif ($reset_context eq 'string') {
_new_document_context($self, $context_str, $CTXF_string);
$translation_result = $self->convert_tree($translated_tree,
$explanation);
_pop_document_context($self);
} elsif ($reset_context eq 'css_string') {
$translation_result = $self->html_convert_css_string($translated_tree,
$context_str);
}
$no_arg_command_context->{'text'} = $translation_result;
}
}
sub _complete_no_arg_commands_formatting($$;$) {
my ($self, $cmdname, $translate) = @_;
_reset_unset_no_arg_commands_formatting_context($self, $cmdname,
'normal', undef, $translate);
_reset_unset_no_arg_commands_formatting_context($self, $cmdname,
'preformatted', 'normal', $translate);
_reset_unset_no_arg_commands_formatting_context($self, $cmdname,
'string', 'preformatted', $translate);
_reset_unset_no_arg_commands_formatting_context($self, $cmdname,
'css_string', 'string', $translate);
}
# transform to
sub _xhtml_re_close_lone_element($) {
my $element = shift;
if ($element =~ /\/\s*>$/) {
# already a closed lone element
return $element;
}
$element =~ s/^(<[a-zA-Z][^<>]*)>$/$1\/>/;
return $element;
}
my %htmlxref_entries = (
'node' => [ 'node', 'section', 'chapter', 'mono' ],
'section' => [ 'section', 'chapter','node', 'mono' ],
'chapter' => [ 'chapter', 'section', 'node', 'mono' ],
'mono' => [ 'mono', 'chapter', 'section', 'node' ],
);
# $FILES is an array reference of file names binary strings.
sub _parse_htmlxref_files($$) {
my ($self, $files) = @_;
my $htmlxref = {};
foreach my $file (@$files) {
my $fname = $file;
if ($self->get_conf('TEST')) {
my ($volume, $directories);
# strip directories for out-of-source builds reproducible file names
($volume, $directories, $fname) = File::Spec->splitpath($file);
}
print STDERR "html refs config file: $file\n" if ($self->get_conf('DEBUG'));
unless (open(HTMLXREF, $file)) {
my $htmlxref_file_name = $file;
my $encoding = $self->get_conf('COMMAND_LINE_ENCODING');
if (defined($encoding)) {
$htmlxref_file_name = decode($encoding, $htmlxref_file_name);
}
$self->converter_document_warn(
sprintf(__("could not open html refs config file %s: %s"),
$htmlxref_file_name, $!));
next;
}
my $line_nr = 0;
my %variables;
while (1) {
my $hline = ;
last if (!defined($hline));
#my $line = $hline;
$line_nr++;
$hline =~ s/^\s*//;
next if $hline =~ /^#/;
next if $hline =~ /^$/;
chomp ($hline);
if ($hline =~ s/^(\w+)\s*=\s*//) {
# handle variables
my $var = $1;
my $re = join '|', map { quotemeta $_ } keys %variables;
$hline =~ s/\$\{($re)\}/defined $variables{$1} ? $variables{$1}
: "\${$1}"/ge;
$variables{$var} = $hline;
next;
}
my @htmlxref = split /\s+/, $hline;
my $manual = shift @htmlxref;
my $split_or_mono = shift @htmlxref;
#print STDERR "$fname: $line_nr: $manual $split_or_mono\n";
if (!defined($split_or_mono)) {
$self->converter_line_warn(__("missing type"),
{'file_name' => $fname, 'line_nr' => $line_nr});
next;
} elsif (!exists($htmlxref_entries{$split_or_mono})) {
$self->converter_line_warn(sprintf(__("unrecognized type: %s"),
$split_or_mono),
{'file_name' => $fname, 'line_nr' => $line_nr});
next;
}
my $href = shift @htmlxref;
# No warning for an empty URL prefix as it is the only way to
# override an entry appearing in a file processed later on
#if (!defined($href)) {
# $self->converter_line_warn(sprintf(
# __("missing %s URL prefix for `%s'"), $split_or_mono, $manual),
# {'file_name' => $fname, 'line_nr' => $line_nr});
#}
# keep previously set value
next if (exists($htmlxref->{$manual})
and exists($htmlxref->{$manual}->{$split_or_mono}));
if (defined($href)) { # substitute 'variables'
my $re = join '|', map { quotemeta $_ } keys %variables;
$href =~ s/\$\{($re)\}/defined $variables{$1} ? $variables{$1}
: "\${$1}"/ge;
$href =~ s/\/*$// if ($split_or_mono ne 'mono');
} else {
# Store empty text instead of undef, such that exists can safely
# be used.
$href = '';
}
$htmlxref->{$manual} = {} if (!exists($htmlxref->{$manual}));
$htmlxref->{$manual}->{$split_or_mono} = $href;
}
if (!close (HTMLXREF)) {
my $htmlxref_file_name = $file;
my $encoding = $self->get_conf('COMMAND_LINE_ENCODING');
if (defined($encoding)) {
$htmlxref_file_name = decode($encoding, $htmlxref_file_name);
}
$self->converter_document_warn(sprintf(__(
"error on closing html refs config file %s: %s"),
$htmlxref_file_name, $!));
}
}
return $htmlxref;
}
sub _load_htmlxref_files($) {
my $self = shift;
my $deprecated_dirs = $self->{'deprecated_config_directories'};
my @htmlxref_files;
my $htmlxref_mode = $self->get_conf('HTMLXREF_MODE');
return if (defined($htmlxref_mode) and $htmlxref_mode eq 'none');
my $htmlxref_file_name = 'htmlxref.cnf';
if (defined($htmlxref_mode) and $htmlxref_mode eq 'file') {
if (defined($self->get_conf('HTMLXREF_FILE'))) {
$htmlxref_file_name = $self->get_conf('HTMLXREF_FILE');
}
my ($encoded_htmlxref_file_name, $htmlxref_file_encoding)
= $self->encoded_output_file_name($htmlxref_file_name);
if (-e $encoded_htmlxref_file_name and -r $encoded_htmlxref_file_name) {
@htmlxref_files = ($encoded_htmlxref_file_name);
} else {
$self->converter_document_warn(
sprintf(__("could not find html refs config file %s"),
$htmlxref_file_name));
}
} else {
my @htmlxref_dirs;
if ($self->get_conf('TEST')) {
my $curdir = File::Spec->curdir();
# to have reproducible tests, do not use system or user
# directories if TEST is set.
@htmlxref_dirs = join('/', ($curdir, '.texinfo'));
if ($Texinfo::ModulePath::texinfo_uninstalled) {
unshift @htmlxref_dirs, join('/', (
$Texinfo::ModulePath::t2a_srcdir, 'perl', 't', 'input_files'));
}
} elsif ($self->get_conf('TEXINFO_LANGUAGE_DIRECTORIES')
and scalar(@{$self->get_conf('TEXINFO_LANGUAGE_DIRECTORIES')}) > 0) {
@htmlxref_dirs = @{$self->get_conf('TEXINFO_LANGUAGE_DIRECTORIES')};
}
my $cnf_directory_name;
# no htmlxref for tests, unless explicitly specified
if ($self->get_conf('TEST')) {
if (defined($self->get_conf('HTMLXREF_FILE'))) {
$htmlxref_file_name = $self->get_conf('HTMLXREF_FILE');
} else {
$htmlxref_file_name = undef;
}
} else {
$cnf_directory_name = 'htmlxref.d';
if (defined($self->get_conf('HTMLXREF_FILE'))) {
$htmlxref_file_name = $self->get_conf('HTMLXREF_FILE');
}
}
my ($encoded_htmlxref_file_name, $htmlxref_file_encoding);
# encode file name and handle specific cases for the main htmlxref file
# without search in directories.
if (defined($htmlxref_file_name)) {
($encoded_htmlxref_file_name, $htmlxref_file_encoding)
= $self->encoded_output_file_name($htmlxref_file_name);
if (File::Spec->file_name_is_absolute($encoded_htmlxref_file_name)) {
if (-e $encoded_htmlxref_file_name and -r $encoded_htmlxref_file_name) {
push @htmlxref_files, $encoded_htmlxref_file_name;
}
$htmlxref_file_name = undef;
} else {
my ($volume, $path_directories, $file)
= File::Spec->splitpath($htmlxref_file_name);
my @path_directories = File::Spec->splitdir($path_directories);
# do not search in directories if the file name already contains
# directories.
if (scalar(@path_directories) > 0) {
if (-e $encoded_htmlxref_file_name
and -r $encoded_htmlxref_file_name) {
push @htmlxref_files, $encoded_htmlxref_file_name;
}
$htmlxref_file_name = undef;
}
}
}
# now search in directories
if (defined($htmlxref_file_name) or defined($cnf_directory_name)) {
my ($encoded_cnf_directory_name, $cnf_directory_encoding);
if (defined($cnf_directory_name)) {
($encoded_cnf_directory_name, $cnf_directory_encoding)
= $self->encoded_output_file_name($cnf_directory_name);
}
my $deprecated_dirs_used;
foreach my $dir (@htmlxref_dirs) {
next unless (-d $dir);
my $deprecated_dir_set = 0;
if (defined($htmlxref_file_name)) {
my $possible_file = "$dir/$encoded_htmlxref_file_name";
if (-e $possible_file and -r $possible_file) {
if (defined($deprecated_dirs) and $deprecated_dirs->{$dir}) {
$deprecated_dirs_used = [] if (!defined($deprecated_dirs_used));
push @$deprecated_dirs_used, $dir;
$deprecated_dir_set = 1;
}
push (@htmlxref_files, $possible_file);
}
}
if (defined($cnf_directory_name)) {
my $cnf_dir = "$dir/$encoded_cnf_directory_name";
if (-d $cnf_dir) {
my $file_found = 0;
# the internal simple quotes are for the case of spaces in $cnf_dir.
my @possible_files = glob("'$cnf_dir/*.cnf'");
foreach my $possible_file (sort(@possible_files)) {
if (-e $possible_file and -r $possible_file) {
push (@htmlxref_files, $possible_file);
$file_found = 1;
}
}
if (!$deprecated_dir_set and $file_found
and $deprecated_dirs and $deprecated_dirs->{$dir}) {
$deprecated_dirs_used = [] if (!defined($deprecated_dirs_used));
push @$deprecated_dirs_used, $dir;
$deprecated_dir_set = 1;
}
}
}
}
if (defined($deprecated_dirs_used)) {
foreach my $dir (@$deprecated_dirs_used) {
my $encoding = $self->get_conf('COMMAND_LINE_ENCODING');
my ($dir_name, $replacement_dir);
if (defined($encoding)) {
$dir_name = decode($encoding, $dir);
$replacement_dir = decode($encoding, $deprecated_dirs->{$dir})
} else {
$dir_name = $dir;
$replacement_dir = $deprecated_dirs->{$dir};
}
$self->converter_document_warn(sprintf(__(
"%s directory is deprecated. Use %s instead"),
$dir_name, $replacement_dir));
}
}
}
}
$self->{'htmlxref'} = {};
if (scalar(@htmlxref_files)) {
$self->{'htmlxref'} = _parse_htmlxref_files($self,
\@htmlxref_files);
}
}
# converter state
#
# No API
# all_directions # determined parallelly in C
# deprecated_config_directories
#
# API exists
#
# Get through converter set_global_document_commands with 'before'. No
# specific API to set, but can use get_conf or force_conf in setup handler
# commands_init_conf
#
# shared_conversion_state
# Set through the shared_conversion_state API (among others):
# explained_commands # used only in an @-command conversion function
#
# API converter_info get_info
# document_name
# destination_directory
# paragraph_symbol
# line_break_element
# non_breaking_space
# simpletitle_tree
# simpletitle_command_name
# title_string
# title_tree
# documentdescription_string
# copying_comment
# jslicenses
#
# API exists
# current_filename
# current_output_unit
# index_entries
# index_entries_by_letter
#
# API exists in Texinfo::Config for setting, not for getting
# stage_handlers
#
# No API, but set by command-line and can be overriden by CSS
# change API functions
# files_css_import_lines
# files_css_rule_lines
#
# API exists
# css_element_class_styles
# css_import_lines
# css_rule_lines
#
# API exists
# file_id_setting
# commands_conversion
# commands_open
# types_conversion
# types_open
#
# API exists for setting, no need to access directly, accessed through
# no_arg_commands_formatting
# customized_no_arg_commands_formatting
#
# API exists for setting (through customized_no_arg_commands_formatting
# for no_arg_commands_formatting), not for getting and used in
# commands_conversion
# no_arg_commands_formatting
# style_commands_formatting
#
# API exists
# code_types
# pre_class_types
#
# API exists
# document_context
#
# API exists
# pending_closes
#
# API exists
# pending_footnotes
#
# API exists
# pending_inline_content
# associated_inline_content
#
# API exists
# multiple_pass
#
# API exists
# targets for directions. Keys are elements references, values are
# target information hash references described above before
# the API functions used to access this information.
# special_targets
# global_units_directions
#
# API exists for setting, not getting
# customized_direction_strings
# directions_strings
# translated_direction_strings
#
# API exists
# special_unit_info
# translated_special_unit_info
#
# API exists
# elements_in_file_count # the number of output units in file
# file_counters # begin at elements_in_file_count decrease
# # each time the unit is closed
#
# API exists
# document_global_context_css
# page_css
#
# API exists
# files_information
#
# No API, converter internals
# document_units
# out_filepaths (partially common with Texinfo::Converter)
# seen_ids
# options_latex_math
# htmlxref
# check_htmlxref_already_warned
# referred_command_stack
#
# from Converter
# labels
my %special_characters = (
'paragraph_symbol' => ['¶', '00B6'],
'left_quote' => ['‘', '2018'],
'right_quote' => ['’', '2019'],
'bullet' => ['•', '2022'],
'non_breaking_space' => [$xml_named_entity_nbsp, '00A0'],
);
sub _XS_html_converter_initialize_beginning($) {
}
sub _XS_html_converter_get_customization($$$$$$$$$$$$$$$$$$$) {
}
# this allows to get some debugging output for the file without setting
# the customization variable.
my $debug; # whether to print debugging output
sub converter_initialize($) {
my $self = shift;
# beginning of initialization done either in Perl or XS
if ($self->{'converter_descriptor'} and $XS_convert) {
_XS_html_converter_initialize_beginning($self);
} else {
# used in initialization. Set if undef
if (!defined($self->get_conf('FORMAT_MENU'))) {
$self->force_conf('FORMAT_MENU', '');
}
# NOTE we reset silently if the split specification is not one known.
# The main program warns if the specific command line option value is
# not known. We could add a warning here to catch mistakes in init
# files. Wait for user reports.
my $split = $self->get_conf('SPLIT');
if ($split and $split ne 'chapter'
and $split ne 'section'
and $split ne 'node') {
$self->force_conf('SPLIT', 'node');
}
my $max_header_level = $self->get_conf('MAX_HEADER_LEVEL');
if (!defined($max_header_level)) {
$self->force_conf('MAX_HEADER_LEVEL', $defaults{'MAX_HEADER_LEVEL'});
} elsif ($max_header_level < 1) {
$self->force_conf('MAX_HEADER_LEVEL', 1);
}
# For CONTENTS_OUTPUT_LOCATION
# should lead to contents not output, but if not, it is not an issue,
# the way to set contents to be output or not should be through the
# contents and shortcontents @-commands and customization options.
foreach my $conf ('CONTENTS_OUTPUT_LOCATION', 'INDEX_ENTRY_COLON',
'MENU_ENTRY_COLON') {
if (!defined($self->get_conf($conf))) {
$self->force_conf($conf, '');
}
}
_load_htmlxref_files($self);
_prepare_css($self);
}
$self->{'output_units_conversion'} = {};
my $customized_output_units_conversion
= Texinfo::Config::GNUT_get_output_units_conversion();
$customized_output_units_conversion = {}
if (!defined($customized_output_units_conversion));
foreach my $type (keys(%default_output_units_conversion)) {
if (exists($customized_output_units_conversion->{$type})) {
$self->{'output_units_conversion'}->{$type}
= $customized_output_units_conversion->{$type};
} else {
$self->{'output_units_conversion'}->{$type}
= $default_output_units_conversion{$type};
}
}
$self->{'types_conversion'} = {};
my $customized_types_conversion
= Texinfo::Config::GNUT_get_types_conversion();
$customized_types_conversion = {}
if (!defined($customized_types_conversion));
foreach my $type (keys(%default_types_conversion)) {
if (exists($customized_types_conversion->{$type})) {
$self->{'types_conversion'}->{$type}
= $customized_types_conversion->{$type};
} else {
$self->{'types_conversion'}->{$type}
= $default_types_conversion{$type};
}
}
$self->{'types_open'} = {};
my $customized_types_open = Texinfo::Config::GNUT_get_types_open();
$customized_types_open = {} if (!defined($customized_types_open));
foreach my $type (keys(%default_types_conversion)) {
if (exists($customized_types_open->{$type})) {
$self->{'types_open'}->{$type}
= $customized_types_open->{$type};
} elsif (exists($default_types_open{$type})) {
$self->{'types_open'}->{$type}
= $default_types_open{$type};
}
}
$self->{'code_types'} = {};
foreach my $type (keys(%default_code_types)) {
$self->{'code_types'}->{$type} = $default_code_types{$type};
}
$self->{'pre_class_types'} = {};
foreach my $type (keys(%default_pre_class_types)) {
$self->{'pre_class_types'}->{$type} = $default_pre_class_types{$type};
}
my $customized_code_types = Texinfo::Config::GNUT_get_types_code_info();
if (defined($customized_code_types)) {
foreach my $type (keys(%$customized_code_types)) {
$self->{'code_types'}->{$type} = $customized_code_types->{$type};
}
}
my $customized_pre_class_types = Texinfo::Config::GNUT_get_types_pre_class();
if (defined($customized_pre_class_types)) {
foreach my $type (keys(%$customized_pre_class_types)) {
$self->{'pre_class_types'}->{$type}
= $customized_pre_class_types->{$type};
}
}
$self->{'upper_case_commands'} = {};
foreach my $cmdname (keys(%default_upper_case_commands)) {
$self->{'upper_case_commands'}->{$cmdname}
= $default_upper_case_commands{$cmdname};
}
my $customized_upper_case_commands
= Texinfo::Config::GNUT_get_upper_case_commands_info();
if (defined($customized_upper_case_commands)) {
foreach my $cmdname (keys(%$customized_upper_case_commands)) {
$self->{'upper_case_commands'}->{$cmdname}
= $customized_upper_case_commands->{$cmdname};
}
}
$self->{'commands_conversion'} = {};
my $customized_commands_conversion
= Texinfo::Config::GNUT_get_commands_conversion();
$customized_commands_conversion = {}
if (!defined($customized_commands_conversion));
foreach my $cmdname (keys(%line_commands), keys(%brace_commands),
keys (%block_commands), keys(%nobrace_commands)) {
if (exists($customized_commands_conversion->{$cmdname})) {
$self->{'commands_conversion'}->{$cmdname}
= $customized_commands_conversion->{$cmdname};
} else {
my $format_menu = $self->get_conf('FORMAT_MENU');
if ($format_menu ne 'menu' and $format_menu ne 'menu_no_detailmenu'
and ($cmdname eq 'menu' or $cmdname eq 'detailmenu')) {
$self->{'commands_conversion'}->{$cmdname} = undef;
} elsif (exists($format_raw_commands{$cmdname})
and !$self->{'expanded_formats'}->{$cmdname}) {
$self->{'commands_conversion'}->{$cmdname} = undef;
} elsif (exists($default_commands_conversion{$cmdname})) {
$self->{'commands_conversion'}->{$cmdname}
= $default_commands_conversion{$cmdname};
}
}
}
$self->{'commands_open'} = {};
my $customized_commands_open
= Texinfo::Config::GNUT_get_commands_open();
$customized_commands_open = {} if (!defined($customized_commands_open));
foreach my $cmdname (keys(%line_commands), keys(%brace_commands),
keys (%block_commands), keys(%nobrace_commands)) {
if (exists($customized_commands_open->{$cmdname})) {
$self->{'commands_open'}->{$cmdname}
= $customized_commands_open->{$cmdname};
} elsif (exists($default_commands_open{$cmdname})) {
$self->{'commands_open'}->{$cmdname}
= $default_commands_open{$cmdname};
}
}
# get all the customization
my %style_commands_customized_formatting_info;
foreach my $cmdname (keys(%default_style_commands_formatting)) {
foreach my $context (@style_commands_contexts) {
my $style_commands_formatting_info
= Texinfo::Config::GNUT_get_style_command_formatting($cmdname, $context);
if (defined($style_commands_formatting_info)) {
if (!exists($style_commands_customized_formatting_info{$cmdname})) {
$style_commands_customized_formatting_info{$cmdname} = {};
}
$style_commands_customized_formatting_info{$cmdname}->{$context}
= $style_commands_formatting_info;
}
}
}
$self->{'style_commands_formatting'} = {};
foreach my $cmdname (keys(%default_style_commands_formatting)) {
$self->{'style_commands_formatting'}->{$cmdname} = {};
foreach my $context (@style_commands_contexts) {
if (exists($style_commands_customized_formatting_info{$cmdname})
and $style_commands_customized_formatting_info{$cmdname}->{$context}) {
$self->{'style_commands_formatting'}->{$cmdname}->{$context}
= $style_commands_customized_formatting_info{$cmdname}->{$context};
} elsif (exists($default_style_commands_formatting{$cmdname}->{$context})) {
$self->{'style_commands_formatting'}->{$cmdname}->{$context}
= $default_style_commands_formatting{$cmdname}->{$context};
}
}
}
my %customized_accent_entities;
foreach my $accent_command
(keys(%Texinfo::Convert::Converter::xml_accent_entities)) {
my ($accent_command_entity, $accent_command_text_with_entities)
= Texinfo::Config::GNUT_get_accent_command_formatting($accent_command);
if (defined($accent_command_entity)
or defined($accent_command_text_with_entities)) {
$customized_accent_entities{$accent_command} = [$accent_command_entity,
$accent_command_text_with_entities];
}
}
$self->{'accent_entities'} = {};
foreach my $accent_command
(keys(%Texinfo::Convert::Converter::xml_accent_entities)) {
$self->{'accent_entities'}->{$accent_command} = [];
my ($accent_command_entity, $accent_command_text_with_entities);
if (exists($customized_accent_entities{$accent_command})) {
($accent_command_entity, $accent_command_text_with_entities)
= @{$customized_accent_entities{$accent_command}};
}
if (not defined($accent_command_entity)
and defined($Texinfo::Convert::Converter::xml_accent_text_with_entities{
$accent_command})) {
$accent_command_entity
= $Texinfo::Convert::Converter::xml_accent_entities{$accent_command};
}
if (not defined($accent_command_text_with_entities)
and defined($Texinfo::Convert::Converter::xml_accent_text_with_entities{
$accent_command})) {
$accent_command_text_with_entities
= $Texinfo::Convert::Converter::xml_accent_text_with_entities{$accent_command};
}
# an empty string means no formatting
if (defined($accent_command_entity)) {
$self->{'accent_entities'}->{$accent_command} = [$accent_command_entity,
$accent_command_text_with_entities];
}
}
#print STDERR Data::Dumper->Dump([$self->{'accent_entities'}]);
# get customization only at that point, as the defaults may be changed
# with the encoding
my $customized_no_arg_commands_formatting = {};
foreach my $cmdname (keys(%{$default_no_arg_commands_formatting{'normal'}})) {
$customized_no_arg_commands_formatting->{$cmdname} = {};
foreach my $context (@no_args_commands_contexts, 'translated_to_convert') {
my $no_arg_command_customized_formatting
= Texinfo::Config::GNUT_get_no_arg_command_formatting($cmdname,
$context);
if (defined($no_arg_command_customized_formatting)) {
$customized_no_arg_commands_formatting->{$cmdname}->{$context}
= $no_arg_command_customized_formatting;
}
}
}
$self->{'customized_no_arg_commands_formatting'}
= $customized_no_arg_commands_formatting;
$self->{'file_id_setting'} = {};
my $customized_file_id_setting_references
= Texinfo::Config::GNUT_get_file_id_setting_references();
if (defined($customized_file_id_setting_references)) {
# first check the validity of the names
foreach my $custom_file_id_setting
(sort(keys(%{$customized_file_id_setting_references}))) {
if (!exists($customizable_file_id_setting_references{
$custom_file_id_setting})) {
$self->converter_document_warn(
sprintf(__("Unknown file and id setting function: %s"),
$custom_file_id_setting));
} else {
$self->{'file_id_setting'}->{$custom_file_id_setting}
= $customized_file_id_setting_references->{$custom_file_id_setting};
}
}
}
my $customized_formatting_references
= Texinfo::Config::GNUT_get_formatting_references();
# first check that all the customized_formatting_references
# are in default_formatting_references
if (defined($customized_formatting_references)) {
foreach my $custom_formatting_ref
(sort(keys(%{$customized_formatting_references}))) {
if (!exists($default_formatting_references{$custom_formatting_ref})) {
$self->converter_document_warn(
sprintf(__("Unknown formatting function: %s"),
$custom_formatting_ref));
}
}
} else {
$customized_formatting_references = {};
}
$self->{'formatting_function'} = {};
foreach my $formatting_reference (keys(%default_formatting_references)) {
if (defined($customized_formatting_references->{$formatting_reference})) {
$self->{'formatting_function'}->{$formatting_reference}
= $customized_formatting_references->{$formatting_reference};
} else {
$self->{'formatting_function'}->{$formatting_reference}
= $default_formatting_references{$formatting_reference};
}
}
my $customized_special_unit_info
= Texinfo::Config::GNUT_get_special_unit_info();
$customized_special_unit_info = {}
if (!defined($customized_special_unit_info));
$self->{'special_unit_info'} = {};
foreach my $type (keys(%default_special_unit_info)) {
$self->{'special_unit_info'}->{$type} = {};
foreach my $special_unit_variety
(keys(%{$default_special_unit_info{$type}})) {
if (exists($customized_special_unit_info->{$type})
and exists($customized_special_unit_info
->{$type}->{$special_unit_variety})) {
$self->{'special_unit_info'}->{$type}->{$special_unit_variety}
= $customized_special_unit_info->{$type}->{$special_unit_variety};
} else {
$self->{'special_unit_info'}->{$type}->{$special_unit_variety}
= $default_special_unit_info{$type}->{$special_unit_variety};
}
}
}
# Note that with XS this information is not actually used, as
# it is only accessed through functions that are overriden
$self->{'translated_special_unit_info_texinfo'} = {};
$self->{'translated_special_unit_info_tree'} = {};
foreach my $type (keys(%default_translated_special_unit_info)) {
$self->{'translated_special_unit_info_texinfo'}->{$type} = {};
$self->{'translated_special_unit_info_tree'}->{$type} = {};
foreach my $special_unit_variety
(keys(%{$default_translated_special_unit_info{$type}})) {
if (exists($customized_special_unit_info->{$type})
and exists($customized_special_unit_info
->{$type}->{$special_unit_variety})) {
$self->{'translated_special_unit_info_texinfo'}->{$type}
->{$special_unit_variety}
= $customized_special_unit_info->{$type}->{$special_unit_variety};
} else {
$self->{'translated_special_unit_info_texinfo'}->{$type}
->{$special_unit_variety}
= $default_translated_special_unit_info{$type}
->{$special_unit_variety};
}
}
}
my $customized_special_unit_body
= Texinfo::Config::GNUT_get_formatting_special_unit_body_references();
$self->{'special_unit_body'} = {};
foreach my $special_unit_variety (keys(%defaults_format_special_unit_body_contents)) {
$self->{'special_unit_body'}->{$special_unit_variety}
= $defaults_format_special_unit_body_contents{$special_unit_variety};
}
foreach my $special_unit_variety (keys(%$customized_special_unit_body)) {
$self->{'special_unit_body'}->{$special_unit_variety}
= $customized_special_unit_body->{$special_unit_variety};
}
# "directions" not associated to output units, but associated to text.
$self->{'global_texts_directions'} = {};
$self->{'global_texts_directions'}->{'Space'} = 1;
$self->{'all_directions'} = {};
foreach my $direction (@all_directions_except_special_units) {
$self->{'all_directions'}->{$direction} = 1;
}
$self->{'customized_text_directions'}
= Texinfo::Config::GNUT_get_text_directions();
if (defined($self->{'customized_text_directions'})) {
foreach my $direction (keys(%{$self->{'customized_text_directions'}})) {
if (!exists($self->{'all_directions'}->{$direction})) {
$self->{'global_texts_directions'}->{$direction} = 1;
$self->{'all_directions'}->{$direction} = 1;
}
}
}
$self->{'customized_global_directions'}
= Texinfo::Config::GNUT_get_global_directions();
if (defined($self->{'customized_global_directions'})) {
foreach my $direction (keys(%{$self->{'customized_global_directions'}})) {
$self->{'all_directions'}->{$direction} = 1;
}
}
# customized_global_directions are not used further here, as the output
# unit need to be found with the document
foreach my $variety (keys(%{$self->{'special_unit_info'}->{'direction'}})) {
my $direction = $self->{'special_unit_info'}->{'direction'}->{$variety};
if (defined($direction)) {
$self->{'all_directions'}->{$direction} = 1;
}
}
#print STDERR join('|', sort(keys(%all_directions)))."\n";
my $customized_direction_strings
= Texinfo::Config::GNUT_get_direction_string_info();
$customized_direction_strings = {}
if (!defined($customized_direction_strings));
# Fill the translated direction strings information, corresponding to:
# - strings already converted
# - strings not already converted
# Each of those types of translated strings are translated later on
# and the translated values are put in $self->{'direction_strings'}.
$self->{'translated_direction_strings'} = {};
foreach my $string_type (keys(%default_translated_directions_strings)) {
$self->{'translated_direction_strings'}->{$string_type} = {};
foreach my $direction (keys(%{$self->{'all_directions'}})) {
if (exists($customized_direction_strings->{$string_type})
and exists($customized_direction_strings->{$string_type}->{$direction})) {
$self->{'translated_direction_strings'}->{$string_type}->{$direction}
= $customized_direction_strings->{$string_type}->{$direction};
} else {
if (exists($default_translated_directions_strings{$string_type}
->{$direction})
and exists($default_translated_directions_strings{$string_type}
->{$direction}->{'converted'})) {
$self->{'translated_direction_strings'}->{$string_type}
->{$direction} = {'converted' => {}};
foreach my $context ('normal', 'string') {
$self->{'translated_direction_strings'}->{$string_type}
->{$direction}->{'converted'}->{$context}
= $default_translated_directions_strings{$string_type}
->{$direction}->{'converted'};
}
} else {
$self->{'translated_direction_strings'}->{$string_type}->{$direction}
= $default_translated_directions_strings{$string_type}->{$direction};
}
}
}
}
# the customization information are not used further here, as
# substitute_html_non_breaking_space is used and it depends on the document
$self->{'customized_direction_strings'} = $customized_direction_strings;
$self->{'stage_handlers'} = Texinfo::Config::GNUT_get_stage_handlers();
# XS parser initialization
if ($self->{'converter_descriptor'} and $XS_convert) {
_XS_html_converter_get_customization($self,
\%default_formatting_references,
\%default_css_string_formatting_references,
\%default_commands_open,
\%default_commands_conversion,
\%default_css_string_commands_conversion,
\%default_types_open,
\%default_types_conversion,
\%default_css_string_types_conversion,
\%default_output_units_conversion,
\%defaults_format_special_unit_body_contents,
$customized_upper_case_commands,
$customized_code_types,
$customized_pre_class_types,
\%customized_accent_entities,
\%style_commands_customized_formatting_info,
$customized_no_arg_commands_formatting,
$customized_special_unit_info,
$customized_direction_strings
);
}
return $self;
}
# remove data that leads to cycles related to output units and references
# to output units.
sub converter_release_output_units($) {
my $self = shift;
# remove references to output units
if (exists($self->{'global_units_directions'})) {
%{$self->{'global_units_directions'}} = ();
}
# Cannot do that, the content is still needed by the Converter
#@{$self->{'document_units'}} = ();
$self->{'document_units'} = [];
}
# remove data that leads to cycles and references to elements.
sub converter_destroy($) {
my $self = shift;
if (exists($self->{'converter_info'})) {
foreach my $key ('document', 'simpletitle_tree', 'title_tree') {
delete $self->{'converter_info'}->{$key};
}
}
delete $self->{'current_node'};
delete $self->{'current_root_command'};
# a separate cache used if the user defines the translate_message function.
delete $self->{'translation_cache'};
# remove shared conversion states pointing to elements
if (exists($self->{'shared_conversion_state'})) {
if (exists($self->{'shared_conversion_state'}->{'nodedescription'})
and exists($self->{'shared_conversion_state'}->{'nodedescription'}
->{'formatted_nodedescriptions'})) {
delete $self->{'shared_conversion_state'}->{'nodedescription'}
->{'formatted_nodedescriptions'};
}
if (exists($self->{'shared_conversion_state'}->{'quotation'})
and exists($self->{'shared_conversion_state'}->{'quotation'}
->{'elements_authors'})) {
delete $self->{'shared_conversion_state'}->{'quotation'}
->{'elements_authors'};
}
}
if (exists($self->{'no_arg_commands_formatting'})) {
foreach my $cmdname (keys(%{$self->{'no_arg_commands_formatting'}})) {
my $no_arg_command_ctx = $self->{'no_arg_commands_formatting'}->{$cmdname};
if (defined($no_arg_command_ctx->{'translated_tree'})) {
my $tree = $no_arg_command_ctx->{'translated_tree'};
# always a copy
Texinfo::ManipulateTree::tree_remove_parents($tree);
}
}
}
delete $self->{'translated_special_unit_info_tree'};
if (exists($self->{'targets'})) {
foreach my $command (keys(%{$self->{'targets'}})) {
my $target = $self->{'targets'}->{$command};
# can be tree elements or results of translations through cdt
delete $target->{'tree'};
delete $target->{'tree_nonumber'};
# tree elements
delete $target->{'name_tree'};
delete $target->{'name_tree_nonumber'};
delete $target->{'root_element_command'};
delete $target->{'node_command'};
}
}
}
sub _XS_html_convert_tree($$;$) {
return undef;
}
# the entry point for _convert
sub convert_tree($$;$) {
my ($self, $tree, $explanation) = @_;
# No XS, convert_tree is not called on trees registered in XS
#my $XS_result = _XS_html_convert_tree($self, $tree, $explanation);
#return $XS_result if (defined($XS_result));
# when formatting accents, goes through xml_accent without
# explanation, as explanation is not in the standard API, but
# otherwise the coverage of explanations should be pretty good
#cluck if (! defined($explanation));
#print STDERR "CONVERT_TREE".(defined($explanation) ? " ".$explanation : '')."\n"
# if ($self->get_conf('DEBUG'));
return _convert($self, $tree, $explanation);
}
# Protect an url, in which characters with specific meaning in url are
# considered to have their specific meaning.
sub url_protect_url_text($$) {
my ($self, $input_string) = @_;
# turn end of lines to spaces, as it is most likely what is expected
# rather than a percent encoded end of line.
$input_string =~ s/[\n\r]+/ /g;
# percent encode character string. It is better use UTF-8 irrespective
# of the actual charset of the HTML output file, according to the tests done.
my $href = encode("UTF-8", $input_string);
# protect 'ligntly', do not protect unreserved and reserved characters + the % itself
$href =~ s/([^^A-Za-z0-9\-_.!~*'()\$&+,\/:;=\?@\[\]\#%])/ sprintf "%%%02x", ord $1 /eg;
return &{$self->formatting_function('format_protect_text')}($self, $href);
}
# Protect a file path used in an url. Characters appearing in file paths
# are not protected. All the other characters that can be percent
# protected are protected, including characters with specific meaning in url.
sub url_protect_file_text($$) {
my ($self, $input_string) = @_;
# turn end of lines to spaces, as it is most likely what is expected.
$input_string =~ s/[\n\r]+/ /g;
# percent encode character string. It is better use UTF-8 irrespective
# of the actual charset of the HTML output file, according to the tests done.
my $href = encode("UTF-8", $input_string);
# protect everything that can be special in url except ~, / and : that could
# appear in file names and does not have much risk in being incorrectly
# interpreted (for :, the interpretation as a scheme delimiter may be possible).
$href =~ s/([^^A-Za-z0-9\-_.~\/:])/ sprintf "%%%02x", ord $1 /eg;
return &{$self->formatting_function('format_protect_text')}($self, $href);
}
sub _normalized_to_id($) {
my $id = shift;
if (!defined($id)) {
cluck "_normalized_to_id id not defined";
return '';
}
$id =~ s/^([0-9_])/g_t$1/;
return $id;
}
sub _default_format_css_lines($;$) {
my ($self, $filename) = @_;
return '' if ($self->get_conf('NO_CSS'));
my $css_refs = $self->get_conf('CSS_REFS');
my $css_element_classes = $self->html_get_css_elements_classes($filename);
my $css_import_lines = $self->css_get_info('imports');
my $css_rule_lines = $self->css_get_info('rules');
return '' if !@$css_import_lines and !@$css_element_classes
and !@$css_rule_lines
and (!defined($css_refs) or !@$css_refs);
my $css_text = "\n";
foreach my $ref (@$css_refs) {
$css_text .= $self->close_html_lone_element(
';
last if (!defined($input_line));
my $line = Encode::decode($input_perl_encoding, $input_line);
$line_nr++;
if ($line_nr == 1) {
# should always be the first line
if ($line =~ /^\@charset *"([^"]+)" *; *$/) {
my $charset = $1;
my $Encode_encoding_object = find_encoding($charset);
if (defined($Encode_encoding_object)) {
my $perl_encoding = $Encode_encoding_object->name();
if (defined($perl_encoding) and $perl_encoding ne '') {
$input_perl_encoding = $perl_encoding;
}
}
next;
}
}
#print STDERR "Line: $line";
if ($in_rules) {
push @$rules, $line;
next;
}
my $text = '';
while (1) {
#sleep 1;
#print STDERR "${text}!in_comment $in_comment in_rules $in_rules in_import $in_import in_string $in_string: $line";
if ($in_comment) {
if ($line =~ s/^(.*?\*\/)//) {
$text .= $1;
$in_comment = 0;
} else {
push @$imports, $text . $line;
last;
}
} elsif (!$in_string and $line =~ s/^\///) {
if ($line =~ s/^\*//) {
$text .= '/*';
$in_comment = 1;
} else {
push (@$imports, $text. "\n") if ($text ne '');
push (@$rules, '/' . $line);
$in_rules = 1;
last;
}
} elsif (!$in_string and $in_import and $line =~ s/^([\"\'])//) {
# strings outside of import start rules
$text .= "$1";
$in_string = quotemeta("$1");
} elsif ($in_string and $line =~ s/^(\\$in_string)//) {
$text .= $1;
} elsif ($in_string and $line =~ s/^($in_string)//) {
$text .= $1;
$in_string = 0;
} elsif ((! $in_string and !$in_import)
and ($line =~ s/^([\\]?\@import)$//
or $line =~ s/^([\\]?\@import\s+)//)) {
$text .= $1;
$in_import = 1;
} elsif (!$in_string and $in_import and $line =~ s/^\;//) {
$text .= ';';
$in_import = 0;
} elsif (($in_import or $in_string) and $line =~ s/^(.)//) {
$text .= $1;
} elsif (!$in_import and $line =~ s/^([^\s])//) {
push (@$imports, $text. "\n") if ($text ne '');
push (@$rules, $1 . $line);
$in_rules = 1;
last;
} elsif ($line =~ s/^(\s)//) {
$text .= $1;
} elsif ($line eq '') {
push (@$imports, $text);
last;
}
}
}
$self->converter_line_warn(__("string not closed in css file"),
{'file_name' => $file, 'line_nr' => $line_nr}) if ($in_string);
$self->converter_line_warn(__("--css-include ended in comment"),
{'file_name' => $file, 'line_nr' => $line_nr}) if ($in_comment);
$self->converter_line_warn(__("\@import not finished in css file"),
{'file_name' => $file, 'line_nr' => $line_nr})
if ($in_import and !$in_comment and !$in_string);
return ($imports, $rules);
}
sub _prepare_css($) {
my $self = shift;
$self->{'files_css_rule_lines'} = [];
$self->{'files_css_import_lines'} = [];
return if ($self->get_conf('NO_CSS'));
my @css_import_lines;
my @css_rule_lines;
my $css_files = $self->get_conf('CSS_FILES');
foreach my $css_file (@$css_files) {
my $css_file_fh;
my $css_file_path;
if ($css_file eq '-') {
$css_file_fh = \*STDIN;
$css_file_path = '-';
} else {
$css_file_path = Texinfo::Common::locate_include_file($css_file,
$self->get_conf('INCLUDE_DIRECTORIES'));
unless (defined($css_file_path)) {
my $css_input_file_name;
my $encoding = $self->get_conf('COMMAND_LINE_ENCODING');
if (defined($encoding)) {
$css_input_file_name = decode($encoding, $css_file);
} else {
$css_input_file_name = $css_file;
}
$self->converter_document_warn(sprintf(
__("CSS file %s not found"), $css_input_file_name));
next;
}
unless (open(CSSFILE, $css_file_path)) {
my $css_file_name;
my $encoding = $self->get_conf('COMMAND_LINE_ENCODING');
if (defined($encoding)) {
$css_file_name = decode($encoding, $css_file_path);
} else {
$css_file_name = $css_file_path;
}
$self->converter_document_warn(sprintf(__(
"could not open --include-file %s: %s"),
$css_file_name, $!));
next;
}
$css_file_fh = \*CSSFILE;
}
my ($import_lines, $rules_lines);
($import_lines, $rules_lines)
= _process_css_file($self, $css_file_fh, $css_file_path);
if (!close($css_file_fh)) {
my $css_file_name;
my $encoding = $self->get_conf('COMMAND_LINE_ENCODING');
if (defined($encoding)) {
$css_file_name = decode($encoding, $css_file_path);
} else {
$css_file_name = $css_file_path;
}
$self->converter_document_warn(
sprintf(__("error on closing CSS file %s: %s"),
$css_file_name, $!));
}
push @css_import_lines, @$import_lines;
push @css_rule_lines, @$rules_lines;
}
if ($self->get_conf('DEBUG')) {
if (@css_import_lines) {
print STDERR "# css import lines\n";
foreach my $line (@css_import_lines) {
print STDERR "$line";
}
}
if (@css_rule_lines) {
print STDERR "# css rule lines\n";
foreach my $line (@css_rule_lines) {
print STDERR "$line";
}
}
}
push @{$self->{'files_css_import_lines'}}, @css_import_lines;
push @{$self->{'files_css_rule_lines'}}, @css_rule_lines;
}
# Get the name of a file containing a label, as well as the identifier within
# that file to link to that label. $normalized is the normalized label name
# and $label_element is the label contents element. Labels are typically
# associated to @node, @*anchor or @float and to external nodes.
sub _normalized_label_id_file($$$) {
my ($self, $normalized, $label_element) = @_;
my $target;
if (!defined($normalized) and defined($label_element)) {
$normalized
= Texinfo::Convert::NodeNameNormalization::convert_to_node_identifier(
$label_element);
}
if (defined($normalized)) {
$target = _normalized_to_id($normalized);
} else {
$target = '';
}
# to find out the Top node, one could check $normalized
if (defined($self->{'file_id_setting'}->{'label_target_name'})) {
$target = &{$self->{'file_id_setting'}->{'label_target_name'}}($self,
$normalized, $label_element, $target);
}
my $filename = $self->node_information_filename($normalized,
$label_element);
return ($filename, $target);
}
sub _register_id($$) {
my ($self, $id) = @_;
$self->{'seen_ids'}->{$id} = 1;
}
sub _id_is_registered($$) {
my ($self, $id) = @_;
if (exists($self->{'seen_ids'}->{$id})) {
return 1;
} else {
return 0;
}
}
sub _unique_target($$) {
my ($self, $target_base) = @_;
my $nr=1;
my $target = $target_base;
while (_id_is_registered($self, $target)) {
$target = $target_base.'-'.$nr;
$nr++;
# Avoid integer overflow
die if ($nr == 0);
}
return $target;
}
sub _new_sectioning_command_target($$) {
my ($self, $command) = @_;
my ($normalized_name, $filename)
= $self->normalized_sectioning_command_filename($command);
my $target_base = _normalized_to_id($normalized_name);
if ($target_base !~ /\S/ and $command->{'cmdname'} eq 'top') {
# @top is allowed to be empty. In that case it gets this target name
$target_base = 'SEC_Top';
$normalized_name = $target_base;
}
my $nr=1;
my $target = $target_base;
if ($target_base ne '') {
$target = _unique_target($self, $target_base);
} else {
$target = '';
}
# These are undefined if the $target is set to ''.
my $target_contents;
my $target_shortcontents;
if (exists($sectioning_heading_commands{$command->{'cmdname'}})) {
if ($target ne '') {
my $target_base_contents = 'toc-'.$normalized_name;
$target_contents = _unique_target($self, $target_base_contents);
my $target_base_shortcontents = 'stoc-'.$normalized_name;
$target_shortcontents
= _unique_target($self, $target_base_shortcontents);
}
}
if (defined($self->{'file_id_setting'}->{'sectioning_command_target_name'})) {
($target, $target_contents,
$target_shortcontents, $filename)
= &{$self->{'file_id_setting'}->{'sectioning_command_target_name'}}($self,
$command, $target,
$target_contents,
$target_shortcontents,
$filename);
}
if ($self->get_conf('DEBUG')) {
print STDERR "Register $command->{'cmdname'} $target\n";
}
$self->{'targets'}->{$command} = {
'target' => $target,
'section_filename' => $filename,
};
_register_id($self, $target);
if (defined($target_contents)) {
$self->{'targets'}->{$command}->{'contents_target'} = $target_contents;
_register_id($self, $target_contents);
} else {
$self->{'targets'}->{$command}->{'contents_target'} = '';
}
if (defined($target_shortcontents)) {
$self->{'targets'}->{$command}->{'shortcontents_target'}
= $target_shortcontents;
_register_id($self, $target_shortcontents);
} else {
$self->{'targets'}->{$command}->{'shortcontents_target'} = '';
}
}
# This set with two different codes
# * the target information, id and normalized filename of 'identifiers_target',
# ie everything that may be the target of a ref: @node, @float label,
# @anchor, @namedanchor.
# * The target information of sectioning elements
# @node and section commands targets are therefore both set.
#
# conversion to HTML is done on-demand, upon call to command_text
# and similar functions.
# Note that 'node_filename', which is set here for Top target information
# too, is not used later for Top anchors or links, see the NOTE below
# associated with setting TOP_NODE_FILE_TARGET.
sub _set_root_commands_targets_node_files($) {
my $self = shift;
my $sections_list;
my $labels_list;
if (exists($self->{'document'})) {
$sections_list = $self->{'document'}->sections_list();
$labels_list = $self->{'document'}->labels_list();
}
if (defined($labels_list)) {
my $extension = '';
$extension = '.'.$self->get_conf('EXTENSION')
if (defined($self->get_conf('EXTENSION'))
and $self->get_conf('EXTENSION') ne '');
foreach my $target_element (@$labels_list) {
next if (not exists($target_element->{'extra'})
or not $target_element->{'extra'}->{'is_target'});
my $label_element = Texinfo::Common::get_label_element($target_element);
my ($node_filename, $target)
= _normalized_label_id_file($self, $target_element->{'extra'}
->{'normalized'},
$label_element);
$node_filename .= $extension;
if (defined($self->{'file_id_setting'}->{'node_file_name'})) {
# a non defined filename is ok if called with convert, but not
# if output in files. We reset if undef, silently unless verbose
# in case called by convert.
my $user_node_filename
= &{$self->{'file_id_setting'}->{'node_file_name'}}(
$self, $target_element, $node_filename);
if (defined($user_node_filename)) {
$node_filename = $user_node_filename;
} elsif ($self->get_conf('VERBOSE')) {
$self->converter_document_warn(sprintf(__(
"user-defined node file name not set for `%s'"),
$node_filename));
} elsif ($self->get_conf('DEBUG')) {
warn "user-defined node file name undef for `$node_filename'\n";
}
}
if ($self->get_conf('DEBUG')) {
print STDERR 'Label'
# uncomment to get the perl object names
#."($target_element)"
." \@$target_element->{'cmdname'} $target, $node_filename\n";
}
$self->{'targets'}->{$target_element} = {'target' => $target,
'node_filename' => $node_filename};
_register_id($self, $target);
}
}
if (defined($sections_list)) {
foreach my $section_relations (@{$sections_list}) {
my $section_element = $section_relations->{'element'};
_new_sectioning_command_target($self, $section_element);
}
}
}
sub _set_heading_commands_targets($) {
my $self = shift;
my $global_commands;
if (exists($self->{'document'})) {
$global_commands = $self->{'document'}->global_commands_information();
}
if (defined($global_commands)) {
foreach my $cmdname (sort(keys(%sectioning_heading_commands)),
'xrefname') {
if (!exists($root_commands{$cmdname})
and exists($global_commands->{$cmdname})) {
foreach my $command (@{$global_commands->{$cmdname}}) {
_new_sectioning_command_target($self, $command);
}
}
}
}
}
sub _html_get_tree_root_element($$;$);
# If $FIND_CONTAINER is set, the element that holds the command output
# is found, otherwise the element that holds the command is found. This is
# mostly relevant for footnote only.
# If no known root element type is found, the returned root element is undef,
# and not set to the element at the tree root
sub _html_get_tree_root_element($$;$) {
my ($self, $command, $find_container) = @_;
# can be used to debug/understand what is going on
#my $debug = 1;
my $current = $command;
#print STDERR "START ".Texinfo::Common::debug_print_element($current)."\n" if ($debug);
my ($output_unit, $root_command);
while (1) {
if (exists($current->{'type'})
and $current->{'type'} eq 'special_unit_element') {
return ($current->{'associated_unit'}, $current);
}
if (exists($current->{'cmdname'})) {
if (exists($root_commands{$current->{'cmdname'}})) {
$root_command = $current;
#print STDERR "CMD ROOT $current->{'cmdname'}\n" if ($debug);
} elsif (exists($block_commands{$current->{'cmdname'}})
and $block_commands{$current->{'cmdname'}} eq 'region') {
if ($current->{'cmdname'} eq 'copying'
and exists($self->{'document'})) {
my $global_commands
= $self->{'document'}->global_commands_information();
if (defined($global_commands)
and exists($global_commands->{'insertcopying'})) {
foreach my $insertcopying (@{$global_commands
->{'insertcopying'}}) {
#print STDERR "INSERTCOPYING\n" if ($debug);
my ($output_unit, $root_command)
= _html_get_tree_root_element($self, $insertcopying,
$find_container);
return ($output_unit, $root_command)
if (defined($output_unit) or defined($root_command));
}
}
} elsif ($current->{'cmdname'} eq 'titlepage'
and $self->get_conf('USE_TITLEPAGE_FOR_TITLE')
and $self->get_conf('SHOW_TITLE')) {
#print STDERR "FOR titlepage document_units [0]\n" if ($debug);
return ($self->{'document_units'}->[0],
$self->{'document_units'}->[0]->{'unit_command'});
}
die "Problem $output_unit, $root_command" if (defined($output_unit)
or defined($root_command));
return (undef, undef);
} elsif ($find_container) {
# @footnote and possibly @*contents when a separate element is set
my ($special_unit_variety, $special_unit, $class_base,
$special_unit_direction)
= $self->command_name_special_unit_information($current->{'cmdname'});
if (defined($special_unit)) {
#print STDERR "SPECIAL $current->{'cmdname'}: $special_unit_variety ($special_unit_direction)\n" if ($debug);
return ($special_unit, undef);
}
}
}
if (exists($current->{'associated_unit'})) {
#print STDERR "ASSOCIATED_UNIT ".Texinfo::Common::debug_print_output_unit($current->{'associated_unit'})."\n" if ($debug);
return ($current->{'associated_unit'}, $root_command);
} elsif (exists($current->{'parent'})) {
#print STDERR "PARENT ".Texinfo::Common::debug_print_element($current->{'parent'})."\n" if ($debug);
$current = $current->{'parent'};
} else {
#print STDERR "UNKNOWN ROOT ".Texinfo::Common::debug_print_element($current)."\n" if ($debug);
return (undef, $root_command);
}
}
}
sub _html_set_pages_files($$$$$$$$) {
my ($self, $output_units, $special_units, $associated_output_units,
$output_file, $destination_directory, $output_filename,
$document_name) = @_;
my @filenames_order;
my %unit_file_name_paths;
# associate a file to the source information leading to set the file
# name. Use the first element source information associated to a file.
# The source information can be either a tree element associated to
# the 'file_info_element' key, with a 'file_info_type' 'node' or
# 'section'... or a specific source associated to the 'file_info_name'
# key with 'file_info_type' 'special_file', or a source set if
# nothing was found, with 'file_info_type' 'stand_in_file' and a
# 'file_info_name'. Redirection files are added in the output()
# function.
my %files_source_info = ();
if (!$self->get_conf('SPLIT')) {
push @filenames_order, $output_filename;
foreach my $output_unit (@$output_units) {
$unit_file_name_paths{$output_unit} = $output_filename;
}
$files_source_info{$output_filename}
= {'file_info_type' => 'special_file',
'file_info_name' => 'non_split',
'file_info_path' => $output_file};
} else {
my $identifiers_target;
if (exists($self->{'document'})) {
$identifiers_target = $self->{'document'}->labels_information();
}
# first determine the top node file name.
my $node_top;
$node_top = $identifiers_target->{'Top'}
if (defined($identifiers_target));
my $top_node_filename = $self->top_node_filename($document_name);
my $node_top_output_unit;
if (defined($node_top) and defined($top_node_filename)) {
$node_top_output_unit = $node_top->{'associated_unit'};
die "BUG: No output unit for top node" if (!defined($node_top_output_unit));
push @filenames_order, $top_node_filename;
$unit_file_name_paths{$node_top_output_unit} = $top_node_filename;
$files_source_info{$top_node_filename}
= {'file_info_type' => 'special_file',
'file_info_name' => 'Top',
'file_info_path' => undef};
}
my $file_nr = 0;
my $extension = '';
$extension = '.'.$self->get_conf('EXTENSION')
if (defined($self->get_conf('EXTENSION'))
and $self->get_conf('EXTENSION') ne '');
foreach my $output_unit (@$output_units) {
# For Top node.
next if ($node_top_output_unit and $output_unit eq $node_top_output_unit);
my $file_output_unit = $output_unit->{'first_in_page'};
if (!defined($file_output_unit)) {
cluck ("No first_in_page for $output_unit\n");
}
if (not exists($unit_file_name_paths{$file_output_unit})) {
my $node_filename;
foreach my $root_command (@{$file_output_unit->{'unit_contents'}}) {
if (exists($root_command->{'cmdname'})
and $root_command->{'cmdname'} eq 'node') {
# double node are not normalized, they are handled here
if (!exists($root_command->{'extra'})
or !exists($root_command->{'extra'}->{'normalized'})
or !exists($identifiers_target->{
$root_command->{'extra'}->{'normalized'}})) {
$node_filename = 'unknown_node';
$node_filename .= $extension;
if (!exists($files_source_info{$node_filename})) {
push @filenames_order, $node_filename;
$files_source_info{$node_filename}
= {'file_info_type' => 'stand_in_file',
'file_info_name' => 'unknown_node',
'file_info_path' => undef};
}
} else {
# Nodes with {'extra'}->{'is_target'} should always be in
# 'identifiers_target', and thus in targets. It is a bug otherwise.
$node_filename
= $self->{'targets'}->{$root_command}->{'node_filename'};
if (not exists($files_source_info{$node_filename})
or $files_source_info{$node_filename}
->{'file_info_type'} ne 'stand_in_file') {
push @filenames_order, $node_filename
unless ($files_source_info{$node_filename});
$files_source_info{$node_filename}
= {'file_info_type' => 'node',
'file_info_element' => $root_command,
'file_info_path' => undef};
}
}
$unit_file_name_paths{$file_output_unit} = $node_filename;
last;
}
}
if (not defined($node_filename)) {
# use section to do the file name if there is no node
my $command = $file_output_unit->{'unit_section'};
if (defined($command)) {
if ($command->{'element'}->{'cmdname'} eq 'top'
and !defined($node_top) and defined($top_node_filename)) {
$unit_file_name_paths{$file_output_unit} = $top_node_filename;
# existing top_node_filename can happen, see
# html_tests.t top_file_name_and_node_name_collision
push @filenames_order, $top_node_filename
unless exists($files_source_info{$top_node_filename});
$files_source_info{$top_node_filename}
= {'file_info_type' => 'special_file',
'file_info_name' => 'Top',
'file_info_path' => undef};
} else {
my $section_filename
= $self->{'targets'}->{$command->{'element'}}
->{'section_filename'};
$unit_file_name_paths{$file_output_unit} = $section_filename;
if (not exists($files_source_info{$section_filename})
or $files_source_info{$section_filename}
->{'file_info_type'} ne 'stand_in_file') {
push @filenames_order, $section_filename
unless (exists($files_source_info{$section_filename}));
$files_source_info{$section_filename}
= {'file_info_type' => 'section',
'file_info_element' => $command->{'element'},
'file_info_path' => undef};
}
}
} else {
# when everything else has failed
if ($file_nr == 0 and !defined($node_top)
and defined($top_node_filename)) {
$unit_file_name_paths{$file_output_unit} = $top_node_filename;
unless (exists($files_source_info{$top_node_filename})) {
push @filenames_order, $top_node_filename;
$files_source_info{$top_node_filename}
= {'file_info_type' => 'stand_in_file',
'file_info_name' => 'Top',
'file_info_path' => undef};
}
} else {
my $filename = $document_name . "_$file_nr";
$filename .= $extension;
$unit_file_name_paths{$file_output_unit} = $filename;
unless (exists($files_source_info{$filename})) {
push @filenames_order, $filename;
$files_source_info{$filename}
= {'file_info_type' => 'stand_in_file',
'file_info_name' => 'unknown',
'file_info_path' => undef};
}
}
$file_nr++;
}
}
}
if ($output_unit ne $file_output_unit) {
$unit_file_name_paths{$output_unit}
= $unit_file_name_paths{$file_output_unit}
}
}
}
foreach my $output_unit (@$output_units) {
my $filename = $unit_file_name_paths{$output_unit};
my $file_source_info = $files_source_info{$filename};
# check
if (!defined($file_source_info)) {
print STDERR "BUG: no files_source_info: $filename\n";
}
my $filepath = $file_source_info->{'file_info_path'};
if (defined($self->{'file_id_setting'}->{'unit_file_name'})) {
# NOTE the information that it is associated with @top or @node Top
# may be determined with $self->unit_is_top_output_unit($output_unit);
my ($user_filename, $user_filepath)
= &{$self->{'file_id_setting'}->{'unit_file_name'}}(
$self, $output_unit, $filename, $filepath);
if (defined($user_filename)) {
my $user_file_source_info;
if (exists($files_source_info{$user_filename})) {
$user_file_source_info = $files_source_info{$user_filename};
my $previous_filepath = $user_file_source_info->{'file_info_path'};
# It is likely that setting different paths for the same file is
# not intended, so we warn.
if (defined($user_filepath) and defined($previous_filepath)
and $user_filepath ne $previous_filepath) {
$self->converter_document_warn(
sprintf(__("resetting %s file path %s to %s"),
$user_filename, $previous_filepath, $user_filepath));
} elsif (defined($user_filepath) and !defined($previous_filepath)) {
$self->converter_document_warn(
sprintf(__("resetting %s file path from a relative path to %s"),
$user_filename, $user_filepath));
} elsif (!defined($user_filepath) and defined($previous_filepath)) {
$self->converter_document_warn(
sprintf(__("resetting %s file path from %s to a relative path"),
$user_filename, $previous_filepath));
}
}
$filename = $user_filename;
push @filenames_order, $filename
unless (defined($user_file_source_info));
$files_source_info{$filename} = {'file_info_type' => 'special_file',
'file_info_name' => 'user_defined',
'file_info_path' => $user_filepath};
}
}
$self->set_output_unit_file($output_unit, $filename);
my $output_unit_filename = $output_unit->{'unit_filename'};
$self->{'file_counters'}->{$output_unit_filename} = 0
if (!exists($self->{'file_counters'}->{$output_unit_filename}));
$self->{'file_counters'}->{$output_unit_filename}++;
print STDERR 'Page '
# uncomment for perl object name
#."$output_unit "
.Texinfo::OutputUnits::output_unit_texi($output_unit)
.": $output_unit_filename($self->{'file_counters'}->{$output_unit_filename})\n"
if ($self->get_conf('DEBUG'));
}
if (defined($special_units)) {
foreach my $special_unit (@$special_units) {
my $unit_command = $special_unit->{'unit_command'};
my $filename
= $self->{'targets'}->{$unit_command}->{'special_unit_filename'};
# Associate the special elements that have no page with the main page.
# This may only happen if not split.
if (!defined($filename)
and defined($output_units->[0]->{'unit_filename'})) {
$filename = $output_units->[0]->{'unit_filename'};
}
if (defined($filename)) {
push @filenames_order, $filename
unless exists($files_source_info{$filename});
$self->set_output_unit_file($special_unit, $filename);
$self->{'file_counters'}->{$filename} = 0
if (!exists($self->{'file_counters'}->{$filename}));
$self->{'file_counters'}->{$filename}++;
print STDERR 'Special page'
# uncomment for perl object name
#." $special_unit"
.": $filename($self->{'file_counters'}->{$filename})\n"
if ($self->get_conf('DEBUG'));
my $file_source_info = {'file_info_element' => $unit_command,
'file_info_type' => 'special_unit',
'file_info_path' => undef};
$files_source_info{$filename} = $file_source_info
unless(exists($files_source_info{$filename})
and $files_source_info{$filename}->{'file_info_type'}
ne 'stand_in_file');
}
}
}
foreach my $filename (@filenames_order) {
$self->set_file_path($filename, $destination_directory,
$files_source_info{$filename}->{'file_info_path'});
}
# to be able to associate to the output unit file the associated
# output units will be output into, this is done after document output
# units got files.
# In practice only used for contents and shortcontents.
if (defined($associated_output_units)
and scalar(@$associated_output_units)) {
foreach my $special_unit (@$associated_output_units) {
my $associated_output_unit = $special_unit->{'associated_document_unit'};
my $unit_command = $special_unit->{'unit_command'};
my $filename;
my $command_target = $self->{'targets'}->{$unit_command};
# set by the user
if (defined($command_target->{'special_unit_filename'})) {
$filename = $command_target->{'special_unit_filename'};
} else {
$filename = $associated_output_unit->{'unit_filename'}
if ($associated_output_unit);
$command_target->{'special_unit_filename'} = $filename;
}
# set here the file name, but do not associate a counter as it is already
# set for the output unit the special output unit is in.
$self->set_output_unit_file($special_unit, $filename)
if (defined($filename));
}
}
return \%files_source_info;
}
# $ROOT is a parsed Texinfo tree. Return a list of the "elements" we need to
# output in the HTML file(s). Each "element" is what can go in one HTML file,
# such as the content between @node lines in the Texinfo source.
# Also setup targets associated to tree elements and to elements associated
# to special units.
sub _prepare_conversion_units($$$) {
my ($self, $document, $document_name) = @_;
my ($output_units, $special_units, $associated_special_units);
if ($self->get_conf('USE_NODES')) {
$output_units = Texinfo::OutputUnits::split_by_node($document);
} else {
$output_units = Texinfo::OutputUnits::split_by_section($document);
}
# Needs to be set early in case it would be needed to find some region
# command associated root command.
$self->{'document_units'} = $output_units;
# configuration used to determine if a special element is to be done
# (in addition to contents)
my @conf_for_special_units = ('footnotestyle');
$self->set_global_document_commands('last', \@conf_for_special_units);
# NOTE if the last value of footnotestyle is separate, all the footnotes
# formatted text are set to the special element set in _prepare_special_units
# as _html_get_tree_root_element uses the Footnote direction for every
# footnote. Therefore if @footnotestyle separate is set late in the
# document the current value may not be consistent with the link obtained
# for the footnote formatted text. This is not an issue, as the manual
# says that @footnotestyle should only appear in the preamble, and it
# makes sense to have something consistent in the whole document for
# footnotes position.
($special_units, $associated_special_units)
= _prepare_special_units($self, $output_units);
# reset to the default
$self->set_global_document_commands('before', \@conf_for_special_units);
# Do that before the other elements, to be sure that special page ids
# are registered before elements id are.
_set_special_units_targets_files($self, $special_units, $document_name);
_prepare_associated_special_units_targets($self, $associated_special_units);
_set_root_commands_targets_node_files($self);
_prepare_index_entries_targets($self);
_prepare_footnotes_targets($self);
_set_heading_commands_targets($self);
$self->register_output_units_lists([$output_units,
$special_units, $associated_special_units]);
return ($output_units, $special_units, $associated_special_units);
}
sub _prepare_units_directions_files($$$$$$$$) {
my ($self, $output_units, $special_units, $associated_special_units,
$output_file, $destination_directory, $output_filename,
$document_name) = @_;
my $identifiers_target;
my $nodes_list;
if (exists($self->{'document'})) {
$identifiers_target = $self->{'document'}->labels_information();
$nodes_list = $self->{'document'}->nodes_list();
}
_prepare_output_units_global_targets($self, $output_units, $special_units,
$associated_special_units);
Texinfo::OutputUnits::split_pages($output_units, $nodes_list,
$self->get_conf('SPLIT'));
# reset even if $output_file eq '', as 'file_counters' is accessed
# below, so it needs to be empty in the case of $output_file eq ''.
$self->initialize_output_units_files();
# determine file names associated with the different pages, and setup
# the counters for special element pages.
my $files_source_info;
if ($output_file ne '') {
$files_source_info =
_html_set_pages_files($self, $output_units, $special_units,
$associated_special_units, $output_file,
$destination_directory, $output_filename, $document_name);
}
# do output units directions.
Texinfo::OutputUnits::units_directions($identifiers_target, $nodes_list,
$output_units,
$self->get_conf('DEBUG'));
_prepare_special_units_directions($self, $special_units);
# do output units directions related to files.
# Here such that PrevFile and NextFile can be set.
Texinfo::OutputUnits::units_file_directions($output_units);
# elements_in_file_count is only set in HTML, not in
# Texinfo::Convert::Converter
$self->{'elements_in_file_count'} = {};
# condition could also be based on $output_file ne ''
if (exists($self->{'file_counters'})) {
# 'file_counters' is dynamic, decreased when the element is encountered
# 'elements_in_file_count' is not modified afterwards
foreach my $filename (keys(%{$self->{'file_counters'}})) {
$self->{'elements_in_file_count'}->{$filename}
= $self->{'file_counters'}->{$filename};
}
}
#if (1 or $self->get_conf('DEBUG') >= 30) {
# if ($self->{'document'}) {
# my $tree = $self->{'document'}->tree();
# my $use_filename = 0;
# if ($self->get_conf('TEST')) {
# $use_filename = 1;
# }
# my $output_units_output
# = Texinfo::OutputUnits::print_output_units_tree_details($output_units,
# $tree, $use_filename);
# }
#}
return $files_source_info;
}
sub _register_special_unit($$) {
my ($self, $special_unit_variety) = @_;
my $special_unit = {'unit_type' => 'special_unit',
'special_unit_variety' => $special_unit_variety,
'directions' => {}};
# a "virtual" out of tree element used for targets
my $unit_command
= Texinfo::TreeElement::new({'type' => 'special_unit_element',
'associated_unit' => $special_unit});
$special_unit->{'unit_command'} = $unit_command;
return $special_unit;
}
# prepare both special output units in separate output units, and
# special output units associated to a regular document output unit,
# output as part of regular output but also possible target of
# special output unit direction. In practice, only contents and
# shortcontents are associated with special output unit directions
# and can be output as part of document output units.
sub _prepare_special_units($$) {
my ($self, $output_units) = @_;
my $global_commands;
my $sections_list;
if (exists($self->{'document'})) {
$global_commands = $self->{'document'}->global_commands_information();
$sections_list = $self->{'document'}->sections_list();
}
# for separate special output units
my %do_special;
# for associated special output units
my $associated_special_units = [];
if (defined($sections_list) and scalar(@{$sections_list}) > 1) {
foreach my $cmdname ('shortcontents', 'contents') {
my $special_unit_variety
= $contents_command_special_unit_variety{$cmdname};
if ($self->get_conf($cmdname)) {
my $contents_location = $self->get_conf('CONTENTS_OUTPUT_LOCATION');
if ($contents_location eq 'separate_element') {
$do_special{$special_unit_variety} = 1;
} else {
my $associated_output_unit;
if ($contents_location eq 'after_title') {
$associated_output_unit = $output_units->[0];
} elsif ($contents_location eq 'after_top') {
if (defined($global_commands)
and exists($global_commands->{'top'})) {
my $section_top = $global_commands->{'top'};
if (exists($section_top->{'associated_unit'})) {
$associated_output_unit = $section_top->{'associated_unit'};
}
}
next unless ($associated_output_unit);
} elsif ($contents_location eq 'inline') {
if (defined($global_commands)
and exists($global_commands->{$cmdname})) {
foreach my $command(@{$global_commands->{$cmdname}}) {
my $root_command;
($associated_output_unit, $root_command)
= _html_get_tree_root_element($self, $command);
if (defined($associated_output_unit)) {
last;
}
}
} else {
next;
}
} else {
# only happens with an unknown CONTENTS_OUTPUT_LOCATION
next;
}
my $special_unit = _register_special_unit($self, $special_unit_variety);
$special_unit->{'associated_document_unit'} = $associated_output_unit;
push @$associated_special_units, $special_unit;
}
}
}
}
if (defined($global_commands) and exists($global_commands->{'footnote'})
and scalar(@$output_units) > 1) {
my $footnotestyle = $self->get_conf('footnotestyle');
if (defined($footnotestyle) and $footnotestyle eq 'separate') {
$do_special{'footnotes'} = 1;
}
}
if ((!defined($self->get_conf('DO_ABOUT'))
and scalar(@$output_units) > 1
and ($self->get_conf('SPLIT') or $self->get_conf('HEADERS')))
or ($self->get_conf('DO_ABOUT'))) {
$do_special{'about'} = 1;
}
my $special_units = [];
# sort special elements according to their index order from
# special_unit_info 'order'.
# First reverse the hash, using arrays in case some elements are at the
# same index, and sort to get alphabetically sorted special element
# varieties that are at the same index.
my %special_units_indices;
foreach my $special_unit_variety
(sort($self->get_special_unit_info_varieties('order'))) {
next unless ($do_special{$special_unit_variety});
my $index = $self->special_unit_info('order', $special_unit_variety);
$special_units_indices{$index} = []
if (not exists($special_units_indices{$index}));
push @{$special_units_indices{$index}}, $special_unit_variety;
}
# now sort according to indices
my @sorted_elements_varieties;
foreach my $index (sort { $a <=> $b } (keys(%special_units_indices))) {
push @sorted_elements_varieties, @{$special_units_indices{$index}};
}
# Setup separate special output units
my $previous_output_unit;
$previous_output_unit = $output_units->[-1];
foreach my $special_unit_variety (@sorted_elements_varieties) {
my $special_unit = _register_special_unit($self, $special_unit_variety);
push @$special_units, $special_unit;
if (defined($previous_output_unit)) {
$special_unit->{'tree_unit_directions'} = {};
$previous_output_unit->{'tree_unit_directions'} = {}
if (not exists($previous_output_unit->{'tree_unit_directions'}));
$special_unit->{'tree_unit_directions'}->{'prev'} = $previous_output_unit;
$previous_output_unit->{'tree_unit_directions'}->{'next'} = $special_unit;
}
$previous_output_unit = $special_unit;
}
return $special_units, $associated_special_units;
}
sub _set_special_units_targets_files($$$) {
my ($self, $special_units, $document_name) = @_;
my $extension = '';
$extension = $self->get_conf('EXTENSION')
if (defined($self->get_conf('EXTENSION')));
foreach my $special_unit (@$special_units) {
my $special_unit_variety = $special_unit->{'special_unit_variety'};
# it may be undef'ined in user customization code
my $target
= $self->special_unit_info('target', $special_unit_variety);
next if (!defined($target));
my $default_filename;
if ($self->get_conf('SPLIT') or !$self->get_conf('MONOLITHIC')
# in general $document_name not defined means called through convert
and defined($document_name)) {
my $special_unit_file_string =
$self->special_unit_info('file_string', $special_unit_variety);
$special_unit_file_string = '' if (!defined($special_unit_file_string));
$default_filename = $document_name . $special_unit_file_string;
$default_filename .= '.'.$extension if (defined($extension));
} else {
$default_filename = undef;
}
my $filename;
if (defined($self->{'file_id_setting'}->{'special_unit_target_file_name'})) {
($target, $filename)
= &{$self->{'file_id_setting'}->{'special_unit_target_file_name'}}(
$self,
$special_unit,
$target,
$default_filename);
}
$filename = $default_filename if (!defined($filename));
if ($self->get_conf('DEBUG')) {
my $fileout = $filename;
$fileout = 'UNDEF' if (!defined($fileout));
print STDERR 'Add special'
# uncomment for the perl object name
#." $special_unit"
." $special_unit_variety: target $target,\n".
" filename $fileout\n";
}
my $unit_command = $special_unit->{'unit_command'};
$self->{'targets'}->{$unit_command} = {'target' => $target,
'special_unit_filename' => $filename,
};
_register_id($self, $target);
}
}
sub _prepare_associated_special_units_targets($$) {
my ($self, $associated_output_units) = @_;
return unless (defined($associated_output_units));
foreach my $special_unit (@$associated_output_units) {
my $special_unit_variety = $special_unit->{'special_unit_variety'};
# it may be undef'ined in user customization code
my $target
= $self->special_unit_info('target', $special_unit_variety);
my $default_filename;
my $filename;
if (defined($self->{'file_id_setting'}->{'special_unit_target_file_name'})) {
($target, $filename)
= &{$self->{'file_id_setting'}->{'special_unit_target_file_name'}}(
$self,
$special_unit,
$target,
$default_filename);
}
$filename = $default_filename if (!defined($filename));
if ($self->get_conf('DEBUG')) {
my $str_filename = $filename;
$str_filename = 'UNDEF (default)' if (not defined($str_filename));
my $str_target = $target;
$str_target = 'UNDEF' if (not defined($str_target));
print STDERR 'Add content'
# uncomment to get the perl object name
#." $special_unit"
." $special_unit_variety: target $str_target,\n".
" filename $str_filename\n";
}
my $unit_command = $special_unit->{'unit_command'};
my $command_target = {'target' => $target};
$self->{'targets'}->{$unit_command} = $command_target;
if (defined($target)) {
_register_id($self, $target);
}
if (defined ($filename)) {
$command_target->{'special_unit_filename'}
= $filename;
}
}
}
sub _prepare_special_units_directions($$) {
my ($self, $special_units) = @_;
return unless(defined($special_units));
foreach my $special_unit (@$special_units) {
$special_unit->{'directions'}->{'This'} = $special_unit;
}
}
# Associate output units to the global targets, First, Last, Top, Index.
sub _prepare_output_units_global_targets($$$$) {
my ($self, $output_units, $special_units, $associated_special_units) = @_;
$self->{'global_units_directions'}->{'First'} = $output_units->[0];
$self->{'global_units_directions'}->{'Last'} = $output_units->[-1];
$self->{'global_units_directions'}->{'Top'}
= _get_top_unit($self, $output_units);
my $global_commands;
my $nodes_list;
my $sections_list;
if (exists($self->{'document'})) {
$global_commands = $self->{'document'}->global_commands_information();
$nodes_list = $self->{'document'}->nodes_list();
$sections_list = $self->{'document'}->sections_list();
}
# Associate Index with the last @printindex. According to Werner Lemberg,
# "the most general index is normally the last one, not the first"
# https://lists.gnu.org/archive/html/bug-texinfo/2025-01/msg00019.html
#
# It is always the last printindex, even if it is not output (for example
# it is in @copying and @titlepage, which are certainly wrong constructs).
if (defined($global_commands) and exists($global_commands->{'printindex'})) {
# Here document_unit can only be a document unit, or maybe undef if there
# are no document unit at all
my ($document_unit, $root_command)
= _html_get_tree_root_element($self,
$global_commands->{'printindex'}->[-1]);
if (defined($document_unit)) {
if (defined($root_command)) {
my $section_relations;
if ($root_command->{'cmdname'} eq 'node') {
if (defined($nodes_list)) {
my $node_relations
= $nodes_list->[$root_command->{'extra'}->{'node_number'} -1];
if (exists($node_relations->{'associated_section'})) {
$section_relations = $node_relations->{'associated_section'};
}
}
} else {
$section_relations
= $sections_list->[$root_command->{'extra'}->{'section_number'} -1];
}
# find the first level 1 sectioning element to associate the printindex
# with. May not work correctly if structuring was not done
if ($section_relations) {
my $current_command = $section_relations->{'element'};
while (exists($current_command->{'extra'})
and defined($current_command->{'extra'}->{'section_level'})
and $current_command->{'extra'}->{'section_level'} > 1
and exists($section_relations->{'section_directions'})
and exists($section_relations->{'section_directions'}->{'up'})
and exists($section_relations->{'section_directions'}->{'up'}
->{'element'}->{'associated_unit'})) {
$section_relations
= $section_relations->{'section_directions'}->{'up'};
$current_command = $section_relations->{'element'};
$document_unit = $current_command->{'associated_unit'};
}
}
}
$self->{'global_units_directions'}->{'Index'} = $document_unit;
}
}
if ($self->{'customized_global_directions'}) {
foreach my $direction (sort(keys(%{$self->{'customized_global_directions'}}))) {
my $node_texi_name
= $self->{'customized_global_directions'}->{$direction};
if (defined($node_texi_name)
and not defined($self->global_direction_text($direction))) {
# FIXME check that relative directions are not replaced by
# global_units_directions (as done in C)? It may not be an issue.
# Determine the document unit corresponding to the direction
# node name Texinfo code
# Parse the customized direction node name Texinfo code
my $node_element;
my $parser = Texinfo::Parser::parser({'NO_INDEX' => 1,
'NO_USER_COMMANDS' => 1,});
my $tree = $parser->parse_texi_line($node_texi_name, undef, 1);
my $errors = $parser->errors();
my $errors_count = Texinfo::Report::count_errors($errors);
if ($errors_count) {
warn "Global $direction node name parsing $errors_count error(s)\n";
warn "node name: $node_texi_name\n";
warn "Error messages: \n";
foreach my $error_message (@$errors) {
warn $error_message->{'error_line'};
}
}
# convert to identifier and determine the node element target
if ($tree) {
my $normalized_node
= Texinfo::Convert::NodeNameNormalization::convert_to_node_identifier($tree);
if ($normalized_node ne '' and $normalized_node =~ /[^-]/) {
$node_element = $self->label_command($normalized_node);
}
}
if (!defined($node_element)) {
$self->converter_document_warn(
sprintf(__("could not find %s node `%s'"),
$direction, $node_texi_name));
} else {
$self->{'global_units_directions'}->{$direction}
= $node_element->{'associated_unit'};
}
}
}
}
if ($self->get_conf('DEBUG')) {
# TODO also show the global directions added by the user
print STDERR "GLOBAL DIRECTIONS:\n";
foreach my $global_direction (@global_directions_order) {
if (defined($self->global_direction_unit($global_direction))) {
my $global_unit = $self->global_direction_unit($global_direction);
print STDERR " $global_direction"
# uncomment to get the perl object name
# ."($global_unit)"
.': '. Texinfo::OutputUnits::output_unit_texi($global_unit)."\n";
}
}
print STDERR "\n";
}
# TODO show the special units directions if DEBUG is set?
foreach my $units_list ($special_units, $associated_special_units) {
if (defined($units_list) and scalar(@$units_list)) {
foreach my $special_unit (@$units_list) {
my $special_unit_variety = $special_unit->{'special_unit_variety'};
my $special_unit_direction
= $self->special_unit_info('direction', $special_unit_variety);
$self->{'global_units_directions'}->{$special_unit_direction}
= $special_unit;
}
}
}
}
sub _prepare_index_entries_targets($) {
my $self = shift;
my $indices_information;
if (exists($self->{'document'})) {
$indices_information = $self->{'document'}->indices_information();
}
if (defined($indices_information)) {
my $no_unidecode;
$no_unidecode = 1 if (defined($self->get_conf('USE_UNIDECODE'))
and !$self->get_conf('USE_UNIDECODE'));
my $in_test;
$in_test = 1 if ($self->get_conf('TEST'));
foreach my $index_name (sort(keys(%$indices_information))) {
foreach my $index_entry (@{$indices_information->{$index_name}
->{'index_entries'}}) {
my $main_entry_element = $index_entry->{'entry_element'};
# does not refer to the document
my $seeentry
= Texinfo::Common::index_entry_referred_entry($main_entry_element,
'seeentry');
next if (defined($seeentry));
my $seealso
= Texinfo::Common::index_entry_referred_entry($main_entry_element,
'seealso');
next if (defined($seealso));
my $region = '';
$region = "$main_entry_element->{'extra'}->{'element_region'}-"
if (defined($main_entry_element->{'extra'}->{'element_region'}));
my $entry_reference_content_element
= Texinfo::Common::index_content_element($main_entry_element, 1);
# construct element to convert to a normalized identifier to use as
# hrefs target
my $normalize_index_element = Texinfo::TreeElement::new(
{'contents' => [$entry_reference_content_element]});
my $subentries_tree
= Texinfo::Convert::Utils::comma_index_subentries_tree(
$main_entry_element, ' ');
if (defined($subentries_tree)) {
push @{$normalize_index_element->{'contents'}},
@{$subentries_tree->{'contents'}};
}
my $normalized_index =
Texinfo::Convert::NodeNameNormalization::normalize_transliterate_texinfo(
$normalize_index_element, $in_test, $no_unidecode);
my $target_base = "index-" . $region .$normalized_index;
my $target = _unique_target($self, $target_base);
_register_id($self, $target);
my $target_element = $main_entry_element;
$target_element = $index_entry->{'entry_associated_element'}
if ($index_entry->{'entry_associated_element'});
$self->{'targets'}->{$target_element} = {'target' => $target, };
}
}
}
}
sub _prepare_footnotes_targets($) {
my $self = shift;
my $footid_base = 'FOOT';
my $docid_base = 'DOCF';
my $global_commands;
if (exists($self->{'document'})) {
$global_commands = $self->{'document'}->global_commands_information();
}
if (defined($global_commands) and exists($global_commands->{'footnote'})) {
my $footnote_nr = 0;
foreach my $footnote (@{$global_commands->{'footnote'}}) {
$footnote_nr++;
my $nr = $footnote_nr;
# anchor for the footnote text
my $footid = $footid_base.$nr;
# anchor for the location of the @footnote in the document
my $docid = $docid_base.$nr;
while (_id_is_registered($self, $docid)
or _id_is_registered($self, $footid)) {
$nr++;
$footid = $footid_base.$nr;
$docid = $docid_base.$nr;
# Avoid integer overflow
die if ($nr == 0);
}
_register_id($self, $footid);
_register_id($self, $docid);
$self->{'targets'}->{$footnote} = { 'target' => $footid };
$self->{'special_targets'}->{'footnote_location'}->{$footnote}
= { 'target' => $docid };
print STDERR 'Enter footnote'
# uncomment for the perl object name
#." $footnote"
.": target $footid, nr $footnote_nr\n"
.Texinfo::Convert::Texinfo::convert_to_texinfo($footnote)."\n"
if ($self->get_conf('DEBUG'));
}
}
}
sub _source_info_id($) {
my $source_info = shift;
my $result;
if (exists($source_info->{'file_name'})) {
$result = $source_info->{'file_name'};
} else {
$result = '';
}
$result .= '-';
if (exists($source_info->{'macro'})) {
$result .= $source_info->{'macro'};
}
$result .= '-';
if (exists($source_info->{'line_nr'})) {
$result .= $source_info->{'line_nr'};
} else {
$result .= '0';
}
return $result;
}
sub _check_htmlxref_already_warned($$$) {
my ($self, $manual_name, $source_info) = @_;
my $node_manual_key;
if (defined($source_info)) {
$node_manual_key = _source_info_id($source_info).'-'.$manual_name;
} else {
$node_manual_key = 'UNDEF-'.$manual_name;
}
if (exists($self->{'check_htmlxref_already_warned'}->{$node_manual_key})) {
return 1;
} else {
$self->{'check_htmlxref_already_warned'}->{$node_manual_key} = 1;
return 0;
}
}
# returns file base name, extension and anchor associated to node
# (anchor, float...) command adhering strictly to the HTML Xref specification.
# The $CROSSREF_EXTENSION argument should be the external crossreference
# filename extension, if undef, the $EXTENSION argument is used.
sub standard_label_id_file($$$$$) {
my ($self, $normalized, $label_element, $crossref_extension,
$extension) = @_;
my $target;
my $filename;
if (!defined($normalized) and defined($label_element)) {
$normalized
= Texinfo::Convert::NodeNameNormalization::convert_to_node_identifier(
$label_element);
}
my $options = \%Texinfo::Options::converter_customization_options;
if (defined($normalized)) {
$target = _normalized_to_id($normalized);
# use default, not user-defined value
my $basefilename_length = $options->{'BASEFILENAME_LENGTH'};
$filename = substr($normalized, 0, $basefilename_length);
} else {
$target = '';
$filename = '';
}
# to find out the Top node, one could check $normalized
if (defined($self->{'file_id_setting'}->{'label_target_name'})) {
$target = &{$self->{'file_id_setting'}->{'label_target_name'}}($self,
$normalized, $label_element, $target);
}
my $file_extension = '';
my $external_extension = $crossref_extension;
$external_extension = $extension
if (not defined($external_extension));
$file_extension = '.' . $external_extension
if (defined($external_extension) and $external_extension ne '');
return ($filename, $file_extension, $target);
}
sub _external_node_href($$$) {
my ($self, $external_node,
# for messages only
$source_command) = @_;
my $normalized = $external_node->{'extra'}->{'normalized'};
my $node_contents = $external_node->{'extra'}->{'node_content'};
#print STDERR "external_node: ".join('|', keys(%$external_node))."\n";
my ($target_filebase, $external_file_extension, $target)
= $self->standard_label_id_file($normalized, $node_contents,
$self->get_conf('EXTERNAL_CROSSREF_EXTENSION'),
$defaults{'EXTENSION'});
# always undef if conversion is called through convert()
my $default_target_split = $self->get_conf('EXTERNAL_CROSSREF_SPLIT');
# initialize to $default_target_split
my $is_target_split;
if ($default_target_split) {
$is_target_split = 1;
} else {
$is_target_split = 0;
}
# used if !$is_target_split
my $file = '';
# used if $is_target_split
my $directory = '';
if (exists($external_node->{'extra'}->{'manual_content'})) {
Texinfo::Convert::Text::set_options_code($self->{'convert_text_options'});
my $manual_name = Texinfo::Convert::Text::convert_to_text(
$external_node->{'extra'}->{'manual_content'},
$self->{'convert_text_options'});
Texinfo::Convert::Text::reset_options_code($self->{'convert_text_options'});
if ($self->get_conf('IGNORE_REF_TO_TOP_NODE_UP') and $target eq '') {
my $top_node_up = $self->get_conf('TOP_NODE_UP');
if (defined($top_node_up) and "($manual_name)" eq $top_node_up) {
return '';
}
}
my $manual_base = $manual_name;
# in 2023 there were manuals with .info. Warning added in 2024.
if ($manual_base =~ s/(\.info?)$//) {
$self->converter_line_warn(sprintf(__(
"do not set %s suffix in reference for manual `%s'"),
$1, $manual_name),
$source_command->{'source_info'});
}
$manual_base =~ s/^.*\///;
my $split_found;
my $htmlxref_href;
my $htmlxref_mode = $self->get_conf('HTMLXREF_MODE');
if (!defined($htmlxref_mode) or $htmlxref_mode ne 'none') {
if (exists($self->{'htmlxref'}->{$manual_base})) {
my $htmlxref_info = $self->{'htmlxref'}->{$manual_base};
my $document_split = $self->get_conf('SPLIT');
$document_split = 'mono' if (!$document_split);
foreach my $split_ordered (@{$htmlxref_entries{$document_split}}) {
if (exists($htmlxref_info->{$split_ordered})) {
$split_found = $split_ordered;
if ($htmlxref_info->{$split_ordered} ne '') {
$htmlxref_href
= $self->url_protect_url_text($htmlxref_info->{$split_ordered});
}
last;
}
}
}
if (defined($split_found)) {
if ($split_found eq 'mono') {
$is_target_split = 0;
} else {
$is_target_split = 1;
}
} else { # nothing specified for that manual, use default
if ($self->get_conf('CHECK_HTMLXREF')) {
if (defined($source_command) and $source_command->{'source_info'}) {
if (!_check_htmlxref_already_warned($self, $manual_name,
$source_command->{'source_info'})) {
$self->converter_line_warn(sprintf(__(
"no HTML cross-references entry found for `%s'"), $manual_name),
$source_command->{'source_info'});
}
} else {
if (!_check_htmlxref_already_warned($self, $manual_name, undef)) {
$self->converter_document_warn(sprintf(__(
"no HTML cross-references entry found for `%s'"), $manual_name),
);
cluck;
}
}
}
}
}
if ($is_target_split) {
if (defined($htmlxref_href)) {
$directory = $htmlxref_href;
} else {
if (defined($self->get_conf('EXTERNAL_DIR'))) {
$directory = $self->get_conf('EXTERNAL_DIR')."/$manual_base";
} elsif ($self->get_conf('SPLIT')) {
$directory = "../$manual_base";
}
my $output_format = $self->get_conf('TEXINFO_OUTPUT_FORMAT');
if (defined($output_format) and $output_format ne '') {
$directory .= '_'.$output_format;
}
$directory = $self->url_protect_file_text($directory);
}
$directory .= "/";
} else {# target not split
if (defined($htmlxref_href)) {
$file = $htmlxref_href;
} else {
if (defined($self->get_conf('EXTERNAL_DIR'))) {
$file = $self->get_conf('EXTERNAL_DIR')."/$manual_base";
} elsif ($self->get_conf('SPLIT')) {
$file = "../$manual_base";
} else {
$file = $manual_base;
}
$file .= $external_file_extension;
$file = $self->url_protect_file_text($file);
}
}
}
if ($is_target_split) {
my $file_name;
if (($target eq 'Top' or $target eq '')
and defined($self->get_conf('TOP_NODE_FILE_TARGET'))) {
$file_name = $self->get_conf('TOP_NODE_FILE_TARGET');
} else {
$file_name = $target_filebase . $external_file_extension;
}
if (defined($self->{'file_id_setting'}->{'external_target_split_name'})) {
($target, $directory, $file_name)
= &{$self->{'file_id_setting'}->{'external_target_split_name'}}($self,
$normalized, $external_node, $target,
$directory, $file_name);
$directory = '' if (!defined($directory));
$file_name = '' if (!defined($file_name));
$target = '' if (!defined($target));
}
my $result = $directory . $file_name;
if ($target ne '') {
$result .= '#' . $target;
}
return $result;
} else {
if ($target eq '') {
$target = 'Top';
}
if (defined($self->{'file_id_setting'}->{
'external_target_non_split_name'})) {
($target, $file)
= &{$self->{'file_id_setting'}->{'external_target_non_split_name'}}($self,
$normalized, $external_node, $target, $file);
$file = '' if (!defined($file));
$target = '' if (!defined($target));
}
my $result = $file;
if ($target ne '') {
$result .= '#' . $target;
}
return $result;
}
}
# Output a list of the nodes immediately below this one
sub _mini_toc($$) {
my ($self, $section_relations) = @_;
my $result = '';
my $entry_index = 0;
if (defined($section_relations)
and exists($section_relations->{'section_children'})
and scalar(@{$section_relations->{'section_children'}})) {
$result .= $self->html_attribute_class('ul', ['mini-toc']).">\n";
foreach my $section_relations
(@{$section_relations->{'section_children'}}) {
my $section = $section_relations->{'element'};
my $text = $self->command_text($section, 'text_nonumber');
# could happen with empty sectioning command
next if (!defined($text) or $text eq '');
$entry_index++;
my $accesskey = '';
$accesskey = " accesskey=\"$entry_index\""
if ($self->get_conf('USE_ACCESSKEY') and $entry_index < 10);
my $href = $self->command_href($section);
$result .= "
\n";
}
$result .= "\n";
}
return $result;
}
sub _default_format_contents($$;$$) {
my ($self, $cmdname, $command, $filename) = @_;
$filename = $self->current_filename() if (!defined($filename));
my $document = $self->get_info('document');
my $sections_list;
my $sectioning_root;
if (defined($document)) {
$sections_list = $document->sections_list();
$sectioning_root = $document->sectioning_root();
}
return ''
if (!defined($sections_list) or !scalar(@$sections_list)
# this should not happen with $sections_list as set from Structuring
# sectioning_structure, but could happen with another source.
# We consider that if sectioning_root is set as usual, all the
# fields are set consistently with what sectioning_structure would
# have set.
or !defined($sectioning_root));
my $is_contents;
$is_contents = 1 if ($cmdname eq 'contents');
my $min_root_level = $sectioning_root->{'section_children'}->[0]
->{'element'}->{'extra'}->{'section_level'};
my $max_root_level = $min_root_level;
foreach my $top_relations (@{$sectioning_root->{'section_children'}}) {
my $top_section = $top_relations->{'element'};
$min_root_level = $top_section->{'extra'}->{'section_level'}
if ($top_section->{'extra'}->{'section_level'} < $min_root_level);
$max_root_level = $top_section->{'extra'}->{'section_level'}
if ($top_section->{'extra'}->{'section_level'} > $max_root_level);
}
# chapter level elements are considered top-level here.
$max_root_level = 1 if ($max_root_level < 1);
#print STDERR "ROOT_LEVEL Max: $max_root_level, Min: $min_root_level\n";
my @toc_ul_classes;
push @toc_ul_classes, 'toc-numbered-mark'
if ($self->get_conf('NUMBER_SECTIONS'));
my $result = '';
if ($is_contents and !defined($self->get_conf('BEFORE_TOC_LINES'))
or (!$is_contents
and !defined($self->get_conf('BEFORE_SHORT_TOC_LINES')))) {
$result .= $self->html_attribute_class('div', [$cmdname]).">\n";
} elsif($is_contents) {
$result .= $self->get_conf('BEFORE_TOC_LINES');
} else {
$result .= $self->get_conf('BEFORE_SHORT_TOC_LINES');
}
my $has_toplevel_contents;
if (@{$sectioning_root->{'section_children'}} > 1) {
$result .= $self->html_attribute_class('ul', \@toc_ul_classes) .">\n";
$has_toplevel_contents = 1;
}
my $link_to_toc = (!$is_contents and $self->get_conf('SHORT_TOC_LINK_TO_TOC')
and ($self->get_conf('contents'))
and ($self->get_conf('CONTENTS_OUTPUT_LOCATION') ne 'inline'
or _has_contents_or_shortcontents($self)));
foreach my $top_relations (@{$sectioning_root->{'section_children'}}) {
my $section_relations = $top_relations;
SECTION:
while (defined($section_relations)) {
my $section = $section_relations->{'element'};
if ($section->{'cmdname'} ne 'top') {
my $text = $self->command_text($section);
my $href;
if ($link_to_toc) {
$href = $self->command_contents_href($section, 'contents', $filename);
} else {
$href = $self->command_href($section, $filename);
}
my $toc_id = $self->command_contents_target($section, $cmdname);
if ($text ne '') {
# no indenting for shortcontents
$result .= (' ' x
(2*($section->{'extra'}->{'section_level'} - $min_root_level)))
if ($is_contents);
$result .= "
";
}
# for shortcontents don't do child if child is not toplevel
if (exists($section_relations->{'section_children'})
and ($is_contents
or $section->{'extra'}->{'section_level'} < $max_root_level)) {
# no indenting for shortcontents
$result .= "\n"
. ' ' x (2*($section->{'extra'}->{'section_level'} - $min_root_level))
if ($is_contents);
$result .= $self->html_attribute_class('ul', \@toc_ul_classes) .">\n";
$section_relations = $section_relations->{'section_children'}->[0];
} elsif (exists($section_relations->{'section_directions'})
and exists($section_relations->{'section_directions'}->{'next'})
and $section->{'cmdname'} ne 'top') {
$result .= "
\n";
last if ($section_relations eq $top_relations);
$section_relations
= $section_relations->{'section_directions'}->{'next'};
} else {
#last if ($section eq $top_section);
if ($section_relations eq $top_relations) {
$result .= "\n" unless ($section->{'cmdname'} eq 'top');
last;
}
while (exists($section_relations->{'section_directions'})
and exists($section_relations->{'section_directions'}->{'up'})) {
$section_relations
= $section_relations->{'section_directions'}->{'up'};
$section = $section_relations->{'element'};
$result .= "\n"
. ' ' x (2*($section->{'extra'}->{'section_level'} - $min_root_level))
. "";
if ($section_relations eq $top_relations) {
$result .= "\n" if ($has_toplevel_contents);
last SECTION;
}
if (exists($section_relations->{'section_directions'})
and exists($section_relations->{'section_directions'}
->{'next'})) {
$result .= "\n";
$section_relations
= $section_relations->{'section_directions'}->{'next'};
last;
}
}
}
}
}
if (scalar(@{$sectioning_root->{'section_children'}}) > 1) {
$result .= "\n";
}
if ($is_contents and !defined($self->get_conf('AFTER_TOC_LINES'))
or (!$is_contents
and !defined($self->get_conf('AFTER_SHORT_TOC_LINES')))) {
$result .= "\n\n";
} elsif ($is_contents) {
$result .= $self->get_conf('AFTER_TOC_LINES');
} else {
$result .= $self->get_conf('AFTER_SHORT_TOC_LINES');
}
return $result;
}
sub _default_format_program_string($) {
my $self = shift;
if (defined($self->get_conf('PROGRAM'))
and $self->get_conf('PROGRAM') ne ''
and defined($self->get_conf('PACKAGE_URL'))) {
return $self->convert_tree(
$self->cdt('This document was generated on @emph{@today{}} using @uref{{program_homepage}, @emph{{program}}}.',
{ 'program_homepage' => Texinfo::TreeElement::new(
{'text' => $self->get_conf('PACKAGE_URL')}),
'program' => Texinfo::TreeElement::new(
{'text' => $self->get_conf('PROGRAM')}) }),
'Tr program string program');
} else {
return $self->convert_tree(
$self->cdt('This document was generated on @emph{@today{}}.'),
'Tr program string date');
}
}
sub _default_format_end_file($$$) {
my ($self, $filename, $output_unit) = @_;
my $result = '';
if ($self->get_conf('PROGRAM_NAME_IN_FOOTER')) {
$result .= "
\n ";
my $open = $self->html_attribute_class('span', ['program-in-footer']);
$result .= $open.'>' if ($open ne '');
my $program_string
= &{$self->formatting_function('format_program_string')}($self);
$result .= $program_string;
$result .= '' if ($open ne '');
$result .= "\n
";
}
$result .= "\n\n";
my $pre_body_close = $self->get_conf('PRE_BODY_CLOSE');
$result .= $pre_body_close if (defined($pre_body_close));
my $jslicenses = $self->get_info('jslicenses');
if (defined($jslicenses)
and ((exists($jslicenses->{'infojs'})
and scalar(keys %{$jslicenses->{'infojs'}}))
or (($self->get_file_information('mathjax', $filename)
or !$self->get_conf('SPLIT'))
and (exists($jslicenses->{'mathjax'})
and scalar(keys %{$jslicenses->{'mathjax'}}))))) {
my $js_setting = $self->get_conf('JS_WEBLABELS');
my $js_path = $self->get_conf('JS_WEBLABELS_FILE');
if (defined($js_setting) and defined($js_path)
and ($js_setting eq 'generate' or $js_setting eq 'reference')) {
$result .=
''
.$self->convert_tree($self->cdt('JavaScript license information'),
'Tr JS license header')
.'';
}
}
return "$result