.
# The mapping is based on style used in other formats.
'i' => 'i',
'slanted' => 'i',
'sansserif' => 'span',
'kbd' => 'kbd',
'key' => 'kbd',
'option' => 'samp',
'r' => 'span',
'samp' => 'samp',
'sc' => 'small',
'strong' => 'strong',
'sub' => 'sub',
'sup' => 'sup',
't' => 'code',
'var' => 'var',
'verb' => 'code', # other brace command
};
my %style_commands_formatting;
foreach my $formatting_context (keys(%style_commands_element)) {
$style_commands_formatting{$formatting_context} = {};
}
$style_commands_formatting{'string'} = {};
my %style_brace_types = map {$_ => 1} ('style_other', 'style_code', 'style_no_code');
# @all_style_commands is the union of style brace commands and commands
# in $style_commands_element{'normal'}, a few not being style brace commands.
# Using keys of a map generated hash does like uniq, it avoids duplicates.
# The first grep selects style brace commands, ie commands with %brace_commands
# type in %style_brace_types.
my @all_style_commands = keys %{{ map { $_ => 1 }
((grep {$style_brace_types{$brace_commands{$_}}} keys(%brace_commands)),
keys(%{$style_commands_element{'normal'}})) }};
foreach my $command (@all_style_commands) {
# default is no attribute.
if ($style_commands_element{'normal'}->{$command}) {
$style_commands_formatting{'normal'}->{$command}
= {'element' => $style_commands_element{'normal'}->{$command}};
$style_commands_formatting{'preformatted'}->{$command}
= {'element' => $style_commands_element{'normal'}->{$command}};
}
if ($style_commands_element{'preformatted'}->{$command}) {
$style_commands_formatting{'preformatted'}->{$command}
= {'element' => $style_commands_element{'preformatted'}->{$command}};
}
if ($quoted_style_commands{$command}) {
foreach my $context ('normal', 'string', 'preformatted') {
$style_commands_formatting{$context}->{$command} = {}
if (!$style_commands_formatting{$context}->{$command});
$style_commands_formatting{$context}->{$command}->{'quote'} = 1;
}
}
$default_commands_conversion{$command} = \&_convert_style_command;
}
$style_commands_formatting{'preformatted'}->{'sc'}->{'element'} = 'span';
# currently unused, could be re-used if there is a need to have attributes
# specified in %style_commands_element
sub _parse_attribute($)
{
my $element = shift;
return ('', '', '') if (!defined($element));
my ($class, $attributes) = ('', '');
if ($element =~ /^(\w+)(\s+.*)/)
{
$element = $1;
$attributes = $2;
if ($attributes =~ s/^\s+class=\"([^\"]+)\"//) {
$class = $1;
}
}
return ($element, $class, $attributes);
}
sub _convert_style_command($$$$)
{
my $self = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
my $text;
$text = $args->[0]->{'normal'} if ($args->[0]);
if (!defined($text)) {
# happens with bogus @-commands without argument, like @strong something
#cluck "text not defined in _convert_style_command";
return '';
}
my @classes;
# handle the effect of kbdinputstyle
if ($cmdname eq 'kbd' and $command->{'extra'}
and $command->{'extra'}->{'code'}) {
$cmdname = 'code';
push @classes, 'as-code-kbd';
}
unshift @classes, $cmdname;
my $attribute_hash = {};
if ($self->in_preformatted()) {
$attribute_hash = $self->{'style_commands_formatting'}->{'preformatted'};
} elsif (!$self->in_string()) {
$attribute_hash = $self->{'style_commands_formatting'}->{'normal'};
}
if (defined($attribute_hash->{$cmdname})) {
my $attribute_text = '';
my $style;
if (defined($attribute_hash->{$cmdname}->{'element'})) {
# the commented out code is useful only if there are attributes in
# style_commands_element
#my $class;
#($style, $class, $attribute_text)
# = _parse_attribute($attribute_hash->{$cmdname}->{'element'});
#if (defined($class) and $class ne '') {
# push @classes, $class;
#}
my $style = $attribute_hash->{$cmdname}->{'element'};
my $open = $self->html_attribute_class($style, \@classes);
if ($open ne '') {
$text = $open . '>' . $text . "$style>";
# $text = $open . "$attribute_text>" . $text . "$style>";
#} elsif ($attribute_text ne '') {
# $text = "<$style $attribute_text>". $text . "$style>";
}
}
if (defined($attribute_hash->{$cmdname}->{'quote'})) {
$text = $self->get_conf('OPEN_QUOTE_SYMBOL') . $text
. $self->get_conf('CLOSE_QUOTE_SYMBOL');
}
}
return $text;
}
sub _convert_w_command($$$$)
{
my $self = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
my $text;
$text = $args->[0]->{'normal'} if ($args->[0]);
if (!defined($text)) {
$text = '';
}
if ($self->in_string()) {
return $text;
} else {
return $text . '';
}
}
$default_commands_conversion{'w'} = \&_convert_w_command;
sub _convert_value_command($$$$)
{
my $self = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
return $self->convert_tree($self->gdt('@{No value for `{value}\'@}',
{'value' => $command->{'extra'}->{'flag'}}));
}
$default_commands_conversion{'value'} = \&_convert_value_command;
sub _convert_email_command($$$$)
{
my $self = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
my $mail_arg = shift @$args;
my $text_arg = shift @$args;
my $mail = '';
my $mail_string;
if (defined($mail_arg)) {
$mail = $mail_arg->{'url'};
$mail_string = $mail_arg->{'monospacestring'};
}
my $text = '';
if (defined($text_arg)) {
$text = $text_arg->{'normal'};
}
$text = $mail_string unless ($text ne '');
# match a non-space character, ascii and non-ascii spaces considered as spaces
return $text unless ($mail =~ /[^\v\h\s]/);
if ($self->in_string()) {
return "$mail_string ($text)";
} else {
return $self->html_attribute_class('a', [$cmdname])
.' href="'.$self->url_protect_url_text("mailto:$mail")."\">$text";
}
}
$default_commands_conversion{'email'} = \&_convert_email_command;
sub _convert_explained_command($$$$)
{
my $self = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
my $with_explanation;
my $explanation_result;
my $explanation_string;
my $normalized_type
= Texinfo::Convert::NodeNameNormalization::normalize_node(
{'contents' => $command->{'args'}->[0]->{'contents'}});
my $explained_commands
= $self->shared_conversion_state('explained_commands', {});
$explained_commands->{$cmdname} = {} if (!$explained_commands->{$cmdname});
my $element_explanation_contents
= $self->shared_conversion_state('element_explanation_contents', {});
if ($args->[1] and defined($args->[1]->{'string'})
and $args->[1]->{'string'} =~ /\S/) {
$with_explanation = 1;
$explanation_string = $args->[1]->{'string'};
# Convert the explanation of the acronym. Must do this before we save
# the explanation for the future, otherwise we get infinite recursion
# for recursively-defined acronyms.
$explanation_result = $self->convert_tree($args->[1]->{'tree'},
"convert $cmdname explanation");
$explained_commands->{$cmdname}->{$normalized_type} =
$command->{'args'}->[1]->{'contents'};
} elsif ($element_explanation_contents->{$command}) {
# if an acronym element is formatted more than once, this ensures that
# only the first explanation (including a lack of explanation) is reused.
# Note that this means that acronyms converted first on a sectioning
# command line for a direction text may not get the explanation
# from acronyms appearing later on in the document but before
# the sectioning command.
if (@{$element_explanation_contents->{$command}}) {
$explanation_string = $self->convert_tree_new_formatting_context(
{'type' => '_string',
'contents' => $element_explanation_contents->{$command}},
$cmdname, $cmdname);
}
} elsif ($explained_commands->{$cmdname}->{$normalized_type}) {
$explanation_string = $self->convert_tree_new_formatting_context(
{'type' => '_string',
'contents' => $explained_commands
->{$cmdname}->{$normalized_type}},
$cmdname, $cmdname);
$element_explanation_contents->{$command}
= $explained_commands->{$cmdname}->{$normalized_type};
} else {
# Avoid ever giving an explanation for this element, even if an
# explanation could appear later on, for instance if acronym is
# formatted early on a sectioning command line and the acronym is
# defined before the sectioning command in the document. This prevents
# infinite recursion for a recursively-defined acronym, when an
# @acronym within the explanation could end up referring to the
# containing @acronym.
$element_explanation_contents->{$command} = [];
}
my $result = $args->[0]->{'normal'};
if (!$self->in_string()) {
my $explanation = '';
$explanation = " title=\"$explanation_string\""
if (defined($explanation_string));
my $html_element = 'abbr';
$result = $self->html_attribute_class($html_element, [$cmdname])
."${explanation}>".$result."$html_element>";
}
if ($with_explanation) {
$result = $self->convert_tree($self->gdt('{explained_string} ({explanation})',
{'explained_string' => {'type' => '_converted',
'text' => $result},
'explanation' => {'type' => '_converted',
'text' => $explanation_result}}), "convert explained $cmdname");
}
return $result;
}
foreach my $explained_command (keys(%explained_commands)) {
$default_commands_conversion{$explained_command}
= \&_convert_explained_command;
}
sub _convert_anchor_command($$$$)
{
my $self = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
my $id = $self->command_id($command);
if (defined($id) and $id ne '' and !$self->in_multi_expanded()
and !$self->in_string()) {
return &{$self->formatting_function('format_separate_anchor')}($self,
$id, 'anchor');
}
return '';
}
$default_commands_conversion{'anchor'} = \&_convert_anchor_command;
sub _convert_footnote_command($$$$)
{
my $self = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
my $number_in_doc;
my $foot_num = $self->shared_conversion_state('footnote_number', 0);
${$foot_num}++;
if ($self->get_conf('NUMBER_FOOTNOTES')) {
$number_in_doc = $$foot_num;
} else {
$number_in_doc = $self->get_conf('NO_NUMBER_FOOTNOTE_SYMBOL');
}
return "($number_in_doc)" if ($self->in_string());
#print STDERR "FOOTNOTE $command\n";
my $footid = $self->command_id($command);
# happens for bogus footnotes
if (!defined($footid)) {
return '';
}
# ID for linking back to the main text from the footnote.
my $docid = $self->footnote_location_target($command);
my $multiple_expanded_footnote = 0;
my $multi_expanded_region = $self->in_multi_expanded();
if (defined($multi_expanded_region)) {
# to avoid duplicate names, use a prefix that cannot happen in anchors
my $target_prefix = "t_f";
$footid = $target_prefix.$multi_expanded_region.'_'.$footid.'_'.$$foot_num;
$docid = $target_prefix.$multi_expanded_region.'_'.$docid.'_'.$$foot_num;
} else {
my $footnote_id_numbers
= $self->shared_conversion_state('footnote_id_numbers', {});
if (!defined($footnote_id_numbers->{$footid})) {
$footnote_id_numbers->{$footid} = $$foot_num;
} else {
# This should rarely happen, except for @footnote in @copying and
# multiple @insertcopying...
# Here it is not checked that there is no clash with another anchor.
# However, unless there are more than 1000 footnotes this should not
# happen.
$footid .= '_'.$$foot_num;
$docid .= '_'.$$foot_num;
$multiple_expanded_footnote = 1;
}
}
my $footnote_href;
if ($self->get_conf('footnotestyle') eq 'end'
and (defined($multi_expanded_region)
or $multiple_expanded_footnote)) {
# if the footnote appears multiple times, command_href() will select
# one, but it may not be the one expanded at the location currently
# formatted (in general the first one, but it depends if it is in a
# tree element or not, for instance in @titlepage).
# With footnotestyle end, considering that the footnote is in the same file
# has a better change of being correct.
$footnote_href = "#$footid";
} else {
$footnote_href = $self->command_href($command, undef, undef, $footid);
}
$self->register_footnote($command, $footid, $docid, $number_in_doc,
$self->get_info('current_filename'), $multi_expanded_region);
my $footnote_number_text;
if ($self->in_preformatted()) {
$footnote_number_text = "($number_in_doc)";
} else {
$footnote_number_text = "$number_in_doc";
}
return $self->html_attribute_class('a', [$cmdname])
." id=\"$docid\" href=\"$footnote_href\">$footnote_number_text";
}
$default_commands_conversion{'footnote'} = \&_convert_footnote_command;
sub _convert_uref_command($$$$)
{
my $self = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
my @args = @$args;
my $url_arg = shift @args;
my $text_arg = shift @args;
my $replacement_arg = shift @args;
my ($url, $url_string, $text, $replacement);
if (defined($url_arg)) {
$url = $url_arg->{'url'};
$url_string = $url_arg->{'monospacestring'};
}
$text = $text_arg->{'normal'} if defined($text_arg);
$replacement = $replacement_arg->{'normal'} if defined($replacement_arg);
$text = $replacement if (defined($replacement) and $replacement ne '');
$text = $url_string if (!defined($text) or $text eq '');
return $text if (!defined($url) or $url eq '');
return "$text ($url_string)" if ($self->in_string());
return $self->html_attribute_class('a', [$cmdname])
.' href="'.$self->url_protect_url_text($url)."\">$text";
}
$default_commands_conversion{'uref'} = \&_convert_uref_command;
$default_commands_conversion{'url'} = \&_convert_uref_command;
sub _convert_image_command($$$$)
{
my $self = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
if (defined($args->[0]->{'monospacetext'})
and $args->[0]->{'monospacetext'} ne '') {
my $basefile_string = '';
$basefile_string = $args->[0]->{'monospacestring'}
if (defined($args->[0]->{'monospacestring'}));
return $basefile_string if ($self->in_string());
my ($image_file, $image_basefile, $image_extension, $image_path)
= $self->html_image_file_location_name($cmdname, $command, $args);
if (not defined($image_path)) {
$self->_noticed_line_warn(sprintf(
__("\@image file `%s' (for HTML) not found, using `%s'"),
$image_basefile, $image_file), $command->{'source_info'});
}
if (defined($self->get_conf('IMAGE_LINK_PREFIX'))) {
$image_file = $self->get_conf('IMAGE_LINK_PREFIX') . $image_file;
}
my $alt_string;
if (defined($args->[3]) and defined($args->[3]->{'string'})) {
$alt_string = $args->[3]->{'string'};
}
if (!defined($alt_string) or ($alt_string eq '')) {
$alt_string = $basefile_string;
}
return $self->close_html_lone_element(
$self->html_attribute_class('img', [$cmdname])
. ' src="'.$self->url_protect_file_text($image_file)."\" alt=\"$alt_string\"");
}
return '';
}
$default_commands_conversion{'image'} = \&_convert_image_command;
sub _convert_math_command($$$$)
{
my $self = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
my $arg = $args->[0]->{'normal'};
my $math_type = $self->get_conf('HTML_MATH');
if ($math_type and $math_type eq 'mathjax') {
$self->register_file_information('mathjax', 1);
return $self->html_attribute_class('em', [$cmdname, 'tex2jax_process'])
.">\\($arg\\)";
}
return $self->html_attribute_class('em', [$cmdname]).">$arg";
}
$default_commands_conversion{'math'} = \&_convert_math_command;
sub _accent_entities_html_accent($$$;$$$)
{
my $self = shift;
my $text = shift;
my $command = shift;
my $in_upper_case = shift;
my $use_numeric_entities = shift;
my $accent = $command->{'cmdname'};
if ($in_upper_case and $text =~ /^\w$/) {
$text = uc ($text);
}
# do not return a dotless i or j as such if it is further composed
# with an accented letter, return the letter as is
if ($accent eq 'dotless') {
if ($Texinfo::Convert::Unicode::unicode_accented_letters{$accent}
and exists($Texinfo::Convert::Unicode::unicode_accented_letters{$accent}->{$text})
and ($command->{'parent'}
and $command->{'parent'}->{'parent'}
and $command->{'parent'}->{'parent'}->{'cmdname'}
and $Texinfo::Convert::Unicode::unicode_accented_letters{$command->{'parent'}
->{'parent'}->{'cmdname'}})) {
return $text;
}
}
if ($use_numeric_entities) {
my $formatted_accent
= Texinfo::Convert::Converter::xml_numeric_entity_accent($accent, $text);
if (defined($formatted_accent)) {
return $formatted_accent;
}
} else {
my ($accent_command_entity, $accent_command_text_with_entities);
if ($self->{'accent_entities'}->{$accent}) {
($accent_command_entity, $accent_command_text_with_entities)
= @{$self->{'accent_entities'}->{$accent}};
}
return "&${text}$accent_command_entity;"
if ($accent_command_entity
and defined($accent_command_text_with_entities)
and ($text =~ /^[$accent_command_text_with_entities]$/));
my $formatted_accent
= Texinfo::Convert::Converter::xml_numeric_entity_accent($accent, $text);
if (defined($formatted_accent)) {
return $formatted_accent;
}
}
return $self->xml_accent($text, $command, $in_upper_case, $use_numeric_entities);
}
sub _accent_entities_numeric_entities_accent($$$;$)
{
my $self = shift;
my $text = shift;
my $command = shift;
my $in_upper_case = shift;
return _accent_entities_html_accent($self, $text, $command, $in_upper_case, 1);
}
sub _convert_accent_command($$$$)
{
my $self = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
my $format_accents;
if ($self->get_conf('USE_NUMERIC_ENTITY')) {
$format_accents = \&_accent_entities_numeric_entities_accent;
} else {
$format_accents = \&_accent_entities_html_accent;
}
return $self->convert_accents($command, $format_accents,
$self->in_upper_case());
}
foreach my $command (keys(%accent_commands)) {
$default_commands_conversion{$command} = \&_convert_accent_command;
}
sub _css_string_accent($$$;$)
{
my $self = shift;
my $text = shift;
my $command = shift;
my $in_upper_case = shift;
my $accent = $command->{'cmdname'};
if ($in_upper_case and $text =~ /^\p{Word}$/) {
$text = uc ($text);
}
if (exists($Texinfo::Convert::Unicode::unicode_accented_letters{$accent})
and exists($Texinfo::Convert::Unicode::unicode_accented_letters{$accent}->{$text})) {
return '\\' .
$Texinfo::Convert::Unicode::unicode_accented_letters{$accent}->{$text}. ' ';
}
if (exists($Texinfo::Convert::Unicode::unicode_diacritics{$accent})) {
my $diacritic = '\\'
.$Texinfo::Convert::Unicode::unicode_diacritics{$accent}. ' ';
if ($accent ne 'tieaccent') {
return $text . $diacritic;
} else {
# tieaccent diacritic is naturally and correctly composed
# between two characters
my $remaining_text = $text;
# we consider that letters are either characters or escaped characters
if ($remaining_text =~ s/^([\p{L}\d]|\\[a-zA-Z0-9]+ )([\p{L}\d]|\\[a-zA-Z0-9]+ )(.*)$/$3/) {
return $1.$diacritic.$2 . $remaining_text;
} else {
return $text . $diacritic;
}
}
}
# should never happen, there are diacritics for every accent command
return Texinfo::Convert::Text::ascii_accent($text, $command);
}
sub _css_string_convert_accent_command($$$$)
{
my $self = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
my $format_accents = \&_css_string_accent;
return $self->convert_accents($command, $format_accents, $self->in_upper_case());
}
foreach my $command (keys(%accent_commands)) {
$default_css_string_commands_conversion{$command} = \&_css_string_convert_accent_command;
}
# argument is formatted as code since indicateurl is in brace_code_commands
sub _convert_indicateurl_command($$$$)
{
my $self = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
my $text = $args->[0]->{'normal'};
if (!defined($text)) {
# happens with bogus @-commands without argument, like @strong something
return '';
}
if (!$self->in_string()) {
return $self->get_conf('OPEN_QUOTE_SYMBOL').
$self->html_attribute_class('code', [$cmdname]).'>'.$text
.''.$self->get_conf('CLOSE_QUOTE_SYMBOL');
} else {
return $self->get_conf('OPEN_QUOTE_SYMBOL').$text.
$self->get_conf('CLOSE_QUOTE_SYMBOL');
}
}
$default_commands_conversion{'indicateurl'} = \&_convert_indicateurl_command;
sub _convert_titlefont_command($$$$)
{
my $self = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
my $text = $args->[0]->{'normal'};
if (!defined($text)) {
# happens with bogus @-commands without argument, like @strong something
return '';
}
return &{$self->formatting_function('format_heading_text')}($self, $cmdname,
[$cmdname], $text, 0);
}
$default_commands_conversion{'titlefont'} = \&_convert_titlefont_command;
sub _convert_U_command($$$$)
{
my $self = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
my $arg;
$arg = $args->[0]->{'normal'} if ($args->[0]);
my $res;
if (defined($arg) and $arg ne '') {
# checks on the value already done in Parser, just output it here.
$res = "$arg;";
} else {
$res = '';
}
return $res;
}
$default_commands_conversion{'U'} = \&_convert_U_command;
sub _default_format_comment($$) {
my $self = shift;
my $text = shift;
return $self->xml_comment(' '.$text);
}
# Note: has an XS override
sub _default_format_protect_text {
my $self = shift;
my $text = shift;
my $result = $self->xml_protect_text($text);
$result =~ s/\f//g;
return $result;
}
sub _default_css_string_format_protect_text($$) {
my $self = shift;
my $text = shift;
$text =~ s/\\/\\\\/g;
$text =~ s/\'/\\'/g;
return $text;
}
# can be called on root commands, tree units, special elements
# and title elements. $cmdname can be undef for special elements.
sub _default_format_heading_text($$$$$;$$)
{
my $self = shift;
my $cmdname = shift;
my $classes = shift;
my $text = shift;
my $level = shift;
my $id = shift;
my $element = shift;
return '' if ($text !~ /\S/ and not defined($id));
# This should seldom happen.
if ($self->in_string()) {
$text .= "\n" unless (defined($cmdname) and $cmdname eq 'titlefont');
return $text;
}
if ($level < 1) {
$level = 1;
} elsif ($level > $self->get_conf('MAX_HEADER_LEVEL')) {
$level = $self->get_conf('MAX_HEADER_LEVEL');
}
my $id_str = '';
if (defined($id)) {
$id_str = " id=\"$id\"";
}
my $result = $self->html_attribute_class("h$level", $classes)
."${id_str}>$text";
# titlefont appears inline in text, so no end of line is
# added. The end of line should be added by the user if needed.
$result .= "\n" unless (defined($cmdname) and $cmdname eq 'titlefont');
$result .= $self->get_conf('DEFAULT_RULE') . "\n"
if (defined($cmdname) and $cmdname eq 'part'
and defined($self->get_conf('DEFAULT_RULE'))
and $self->get_conf('DEFAULT_RULE') ne '');
return $result;
}
sub _default_format_separate_anchor($$;$)
{
my $self = shift;
my $id = shift;
my $class = shift;
# html_attribute_class would not work with span, so if span is
# used, html_attribute_class should not be used
return $self->html_attribute_class('a', [$class])." id=\"$id\">";
}
# Associated to a button. Return text to use for a link in button bar.
# Depending on USE_NODE_DIRECTIONS and xrefautomaticsectiontitle
# use section or node for link direction and string.
sub _default_panel_button_dynamic_direction($$;$$$)
{
my $self = shift;
my $direction = shift;
my $source_command = shift;
my $omit_rel = shift;
my $use_first_element_in_file_directions = shift;
my $result = undef;
if ((defined($self->get_conf('USE_NODE_DIRECTIONS'))
and $self->get_conf('USE_NODE_DIRECTIONS'))
or (not defined($self->get_conf('USE_NODE_DIRECTIONS'))
and $self->get_conf('USE_NODES'))) {
$direction = 'Node'.$direction;
}
if ($use_first_element_in_file_directions) {
$direction = 'FirstInFile'.$direction;
}
my $href = $self->from_element_direction($direction, 'href',
undef, undef, $source_command);
my $node;
if ($self->get_conf('xrefautomaticsectiontitle') eq 'on') {
$node = $self->from_element_direction($direction, 'section');
}
if (!defined($node)) {
$node = $self->from_element_direction($direction, 'node');
}
my $hyperlink;
if (defined($href) and $href ne '' and defined($node) and $node =~ /\S/) {
my $hyperlink_attributes = $omit_rel ? ''
: $self->_direction_href_attributes($direction);
$hyperlink = "$node";
} elsif (defined($node) and $node =~ /\S/) {
$hyperlink = $node;
}
if (defined($hyperlink)) {
# i18n
$result = $self->get_conf('BUTTONS_TEXT')->{$direction}.": $hyperlink";
}
# 1 to communicate that a delimiter is needed for that button
return ($result, 1);
}
# Used for button bar at the foot of a node, with "rel" and "accesskey"
# attributes omitted.
sub _default_panel_button_dynamic_direction_node_footer($$$)
{
my $self = shift;
my $direction = shift;
my $source_command = shift;
return _default_panel_button_dynamic_direction($self, $direction,
$source_command, 1);
}
# used for button bar at the foot of a section or chapter with
# directions of first element in file used instead of the last
# element directions.
sub _default_panel_button_dynamic_direction_section_footer($$$) {
my $self = shift;
my $direction = shift;
my $source_command = shift;
return _default_panel_button_dynamic_direction($self, $direction,
$source_command, undef, 1);
}
# Only used if ICONS is set and the button is active.
sub _default_format_button_icon_img($$$;$)
{
my $self = shift;
my $button = shift;
my $icon = shift;
my $name = shift;
return '' if (!defined($icon));
$button = '' if (!defined ($button));
$name = '' if (!defined($name));
my $alt = '';
if ($name ne '') {
if ($button ne '') {
$alt = "$button: $name";
} else {
$alt = $name;
}
} else {
$alt = $button;
}
return $self->close_html_lone_element(
'get_conf('USE_ACCESSKEY')
and $self->get_conf('BUTTONS_ACCESSKEY')) {
my $accesskey = $self->get_conf('BUTTONS_ACCESSKEY')->{$direction};
if (defined($accesskey) and ($accesskey ne '')) {
$href_attributes = " accesskey=\"$accesskey\"";
}
}
if ($self->get_conf('USE_REL_REV') and $self->get_conf('BUTTONS_REL')) {
my $button_rel = $self->get_conf('BUTTONS_REL')->{$direction};
if (defined($button_rel) and ($button_rel ne '')) {
$href_attributes .= " rel=\"$button_rel\"";
}
}
return $href_attributes;
}
my %html_default_node_directions;
foreach my $node_directions ('NodeNext', 'NodePrev', 'NodeUp') {
$html_default_node_directions{$node_directions} = 1;
}
sub _default_format_button($$;$)
{
my $self = shift;
my $button = shift;
my $source_command = shift;
my ($active, $passive, $need_delimiter);
if (ref($button) eq 'CODE') {
($active, $need_delimiter) = &$button($self);
} elsif (ref($button) eq 'SCALAR') {
$active = "$$button" if defined($$button);
$need_delimiter = 1;
} elsif (ref($button) eq 'ARRAY' and scalar(@$button == 2)) {
my $text = $button->[1];
my $direction = $button->[0];
# $direction is simple text and $text is a reference
if (defined($direction) and ref($direction) eq ''
and defined($text) and (ref($text) eq 'SCALAR') and defined($$text)) {
# use given text
my $href = $self->from_element_direction($direction, 'href',
undef, undef, $source_command);
if ($href) {
my $anchor_attributes = $self->_direction_href_attributes($direction);
$active = "$$text";
} else {
$passive = $$text;
}
$need_delimiter = 1;
# $direction is simple text and $text is a reference on code
} elsif (defined($direction) and ref($direction) eq ''
and defined($text) and (ref($text) eq 'CODE')) {
($active, $need_delimiter) = &$text($self, $direction, $source_command);
# $direction is simple text and $text is also a simple text
} elsif (defined($direction) and ref($direction) eq ''
and defined($text) and ref($text) eq '') {
if ($text =~ s/^->\s*//) {
# this case is mostly for tests, to test the direction type $text
# with the direction $direction
$active = $self->from_element_direction($direction, $text,
undef, undef, $source_command);
} else {
my $href = $self->from_element_direction($direction, 'href',
undef, undef, $source_command);
my $text_formatted = $self->from_element_direction($direction, $text);
if ($href) {
my $anchor_attributes = $self->_direction_href_attributes($direction);
$active = "$text_formatted";
} else {
$passive = $text_formatted;
}
}
$need_delimiter = 1;
}
} elsif ($button eq ' ') {
# handle space button
if ($self->get_conf('ICONS') and $self->get_conf('ACTIVE_ICONS')
and defined($self->get_conf('ACTIVE_ICONS')->{$button})
and $self->get_conf('ACTIVE_ICONS')->{$button} ne '') {
my $button_name = $self->get_conf('BUTTONS_NAME')->{$button};
$active = &{$self->formatting_function('format_button_icon_img')}($self,
$button_name, $self->get_conf('ACTIVE_ICONS')->{' '});
} else {
$active = $self->get_conf('BUTTONS_TEXT')->{$button};
}
$need_delimiter = 0;
} else {
my $href = $self->from_element_direction($button, 'href',
undef, undef, $source_command);
if ($href) {
# button is active
my $btitle = '';
if ($self->get_conf('BUTTONS_GOTO')
and defined($self->get_conf('BUTTONS_GOTO')->{$button})) {
$btitle = ' title="' . $self->get_conf('BUTTONS_GOTO')->{$button} . '"';
}
if ($self->get_conf('USE_ACCESSKEY')
and $self->get_conf('BUTTONS_ACCESSKEY')) {
my $accesskey = $self->get_conf('BUTTONS_ACCESSKEY')->{$button};
if (defined($accesskey) and $accesskey ne '') {
$btitle .= " accesskey=\"$accesskey\"";
}
}
if ($self->get_conf('USE_REL_REV') and ($self->get_conf('BUTTONS_REL'))) {
my $button_rel = $self->get_conf('BUTTONS_REL')->{$button};
if (defined($button_rel) and $button_rel ne '') {
$btitle .= " rel=\"$button_rel\"";
}
}
my $use_icon;
if ($self->get_conf('ICONS') and $self->get_conf('ACTIVE_ICONS')
and $self->get_conf('BUTTONS_NAME')) {
my $active_icon = $self->get_conf('ACTIVE_ICONS')->{$button};
my $button_name = $self->get_conf('BUTTONS_NAME')->{$button};
if (defined($active_icon) and $active_icon ne ''
and defined($button_name)) {
# use icon
$active = "".
&{$self->formatting_function('format_button_icon_img')}($self,
$button_name, $active_icon,
$self->from_element_direction($button, 'string')) ."";
$use_icon = 1;
}
}
if (!$use_icon) {
# use text
$active = '[' . "".
$self->get_conf('BUTTONS_TEXT')->{$button}."" . ']';
}
} else {
# button is passive
my $use_icon;
if ($self->get_conf('ICONS') and $self->get_conf('PASSIVE_ICONS')
and $self->get_conf('BUTTONS_NAME')) {
my $passive_icon = $self->get_conf('PASSIVE_ICONS')->{$button};
my $button_name = $self->get_conf('BUTTONS_NAME')->{$button};
if ($passive_icon and $passive_icon ne '') {
$passive = &{$self->formatting_function('format_button_icon_img')}(
$self, $button_name, $passive_icon,
$self->from_element_direction($button, 'string'));
$use_icon = 1;
}
}
if (!$use_icon) {
$passive = '[' . $self->get_conf('BUTTONS_TEXT')->{$button} . ']';
}
}
$need_delimiter = 0;
}
# FIXME chose another option among those proposed in comments below?
if (not defined($need_delimiter)) {
# option 1: be forgiving if $need_delimiter is not set
# if ($html_default_node_directions{$button}) {
# $need_delimiter = 1;
# } else {
# $need_delimiter = 0;
# }
# option 2: be somewhat forgiving but show a backtrace
#cluck ("need_delimiter not defined");
# $need_delimiter = 0;
# option3: no pity
confess ("need_delimiter not defined");
}
return ($active, $passive, $need_delimiter);
}
# called for special elements and tree units
sub _default_format_navigation_panel($$$$;$)
{
my $self = shift;
my $buttons = shift;
my $cmdname = shift;
my $source_command = shift;
my $vertical = shift;
# if VERTICAL_HEAD_NAVIGATION, the buttons are in a vertical table which
# is itself in the first column of a table opened in header_navigation
#my $vertical = $self->get_conf('VERTICAL_HEAD_NAVIGATION');
my $result = '';
if ($self->get_conf('HEADER_IN_TABLE')) {
$result .= $self->html_attribute_class('table', ['nav-panel'])
.' cellpadding="1" cellspacing="1" border="0">'."\n";
$result .= "
\n";
}
my $first_button = 1;
foreach my $button (@$buttons) {
if ($self->get_conf('HEADER_IN_TABLE')) {
$result .= '
'."\n" if $vertical;
$result .= '
';
}
my $direction;
if (ref($button) eq 'ARRAY'
and defined($button->[0]) and ref($button->[0]) eq '') {
$direction = $button->[0];
} elsif (defined($button) and ref($button) eq '') {
$direction = $button;
}
my ($active, $passive, $need_delimiter)
# API info: using the API to allow for customization would be:
# = &{$self->formatting_function('format_button')}($self, $button,
# $source_command);
= &{$self->{'formatting_function'}->{'format_button'}}($self, $button,
$source_command);
if ($self->get_conf('HEADER_IN_TABLE')) {
if (defined($active)) {
$result .= $active;
} elsif (defined($passive)) {
$result .= $passive;
}
$result .= "
\n";
$result .= "
\n" if $vertical;
$first_button = 0 if ($first_button);
} elsif (defined($active)) {
# only active buttons are print out when not in table
if ($need_delimiter and !$first_button) {
$active = ', ' .$active;
}
$result .= $active;
$first_button = 0 if ($first_button);
}
}
if ($self->get_conf('HEADER_IN_TABLE')) {
$result .= "" unless $vertical;
$result .= "\n";
} else {
$result .= "
\n\n";
}
return $result;
}
sub _default_format_navigation_header($$$$)
{
my $self = shift;
my $buttons = shift;
my $cmdname = shift;
my $element = shift;
my $result = '';
if ($self->get_conf('VERTICAL_HEAD_NAVIGATION')) {
$result .= '
';
} elsif ($self->get_conf('SPLIT') eq 'node') {
$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 = shift;
my $cmdname = shift;
my $command = shift;
my $tree_unit = shift;
my $result = '';
print STDERR "FORMAT elt header $tree_unit (@{$tree_unit->{'contents'}}) ".
Texinfo::Structuring::root_or_external_element_cmd_texi($tree_unit) ."\n"
if ($self->get_conf('DEBUG'));
# Do the heading if the command is the first command in the element
if (($tree_unit->{'contents'}->[0] eq $command
or (!$tree_unit->{'contents'}->[0]->{'cmdname'}
and $tree_unit->{'contents'}->[1] eq $command))
# and there is more than one element
and ($tree_unit->{'structure'}
and ($tree_unit->{'structure'}->{'unit_next'}
or $tree_unit->{'structure'}->{'unit_prev'}))) {
my $is_top = $self->element_is_tree_unit_top($tree_unit);
my $first_in_page = (defined($tree_unit->{'structure'}->{'unit_filename'})
and $self->count_elements_in_filename('current',
$tree_unit->{'structure'}->{'unit_filename'}) == 1);
my $previous_is_top = 0;
$previous_is_top = 1 if ($tree_unit->{'structure'}->{'unit_prev'}
and $self->element_is_tree_unit_top($tree_unit->{'structure'}->{'unit_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 {
if ($first_in_page and !$self->get_conf('HEADERS')) {
if ($self->get_conf('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 ($self->get_conf('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 $self->get_conf('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);
}
}
}
return $result;
}
sub register_opened_section_level($$$)
{
my $self = shift;
my $level = shift;
my $close = shift;
while (@{$self->{'pending_closes'}} < $level) {
push(@{$self->{'pending_closes'}}, "");
}
push(@{$self->{'pending_closes'}}, $close);
}
sub close_registered_sections_level($$)
{
my $self = shift;
my $level = shift;
if (not defined($level)) {
cluck 'close_registered_sections_level $level not defined';
}
my @closed_elements;
my $result = '';
while (@{$self->{'pending_closes'}} > $level) {
my $close = pop @{$self->{'pending_closes'}};
push(@closed_elements, $close)
if ($close);
}
return @closed_elements;
}
sub _convert_heading_command($$$$$)
{
my $self = shift;
my $cmdname = shift;
my $element = shift;
my $args = shift;
my $content = shift;
my $result = '';
# No situation where this could happen
if ($self->in_string()) {
$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 $element "
.Texinfo::Convert::Texinfo::root_heading_command_to_texinfo($element)."\n"
if ($self->get_conf('DEBUG'));
my $tree_unit;
if ($Texinfo::Commands::root_commands{$element->{'cmdname'}}
and $element->{'structure'}->{'associated_unit'}) {
$tree_unit = $element->{'structure'}->{'associated_unit'};
}
my $element_header = '';
if ($tree_unit) {
$element_header = &{$self->formatting_function('format_element_header')}(
$self, $cmdname, $element, $tree_unit);
}
my $tables_of_contents = '';
my $structuring = $self->get_info('structuring');
if ($self->get_conf('CONTENTS_OUTPUT_LOCATION') eq 'after_top'
and $cmdname eq 'top'
and $structuring and $structuring->{'sectioning_root'}
and scalar(@{$structuring->{'sections_list'}}) > 1) {
foreach my $content_command_name ('contents', 'shortcontents') {
if ($self->get_conf($content_command_name)) {
my $contents_text
= $self->_contents_inline_element($content_command_name, undef);
if ($contents_text ne '') {
$tables_of_contents .= $contents_text;
}
}
}
}
my $mini_toc = '';
if ($tables_of_contents eq ''
and $self->get_conf('FORMAT_MENU') eq 'sectiontoc'
and $sectioning_heading_commands{$cmdname}) {
$mini_toc = _mini_toc($self, $element);
}
if ($self->get_conf('NO_TOP_NODE_OUTPUT')
and $Texinfo::Commands::root_commands{$cmdname}) {
my $in_skipped_node_top
= $self->shared_conversion_state('in_skipped_node_top', 0);
my $node_element;
if ($cmdname eq 'node') {
$node_element = $element;
} elsif ($cmdname eq 'part' and $element->{'extra'}
and $element->{'extra'}->{'part_following_node'}) {
$node_element = $element->{'extra'}->{'part_following_node'};
}
if ($node_element or $cmdname eq 'part') {
if ($node_element and $node_element->{'extra'}
and $node_element->{'extra'}->{'normalized'}
and $node_element->{'extra'}->{'normalized'} eq 'Top') {
$$in_skipped_node_top = 1;
} elsif ($$in_skipped_node_top == 1) {
$$in_skipped_node_top = -1;
}
}
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 .= $tables_of_contents;
$result .= $mini_toc;
return $result;
}
}
my @heading_classes;
my $level_corrected_cmdname = $cmdname;
if ($element->{'structure'}
and defined $element->{'structure'}->{'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) {
push @heading_classes,
"${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 ($cmdname eq 'node' and $element->{'extra'}->{'associated_section'}) {
$opening_section = $element->{'extra'}->{'associated_section'};
$level_corrected_opening_section_cmdname
= Texinfo::Structuring::section_level_adjusted_command_name($opening_section);
} elsif ($cmdname ne 'node'
# if there is an associated node, it is not a section opening
# the section was opened before when the node was encountered
and (not $element->{'extra'}
or not $element->{'extra'}->{'associated_node'})
# to avoid *heading* @-commands
and $Texinfo::Commands::root_commands{$cmdname}) {
$opening_section = $element;
$level_corrected_opening_section_cmdname = $level_corrected_cmdname;
}
# $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 ($cmdname eq 'node') {
# FIXME what to do if the $tree_unit extra does not contain any
# unit_command, but tree_unit is defined (it can contain only 'first_in_page')
if ((!$tree_unit # or !$tree_unit->{'extra'}
# or !$tree_unit->{'extra'}->{'unit_command'}
or ($tree_unit->{'extra'}->{'unit_command'}
and $tree_unit->{'extra'}->{'unit_command'} eq $element
and not $element->{'extra'}->{'associated_section'}))
and defined($element->{'extra'}->{'normalized'})) {
if ($element->{'extra'}->{'normalized'} eq 'Top') {
$heading_level = 0;
} else {
$heading_level = 3;
}
}
} elsif ($element->{'structure'}
and defined($element->{'structure'}->{'section_level'})) {
$heading_level = $element->{'structure'}->{'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 = $opening_section->{'structure'}->{'section_level'};
$result .= join('', $self->close_registered_sections_level($level));
$self->register_opened_section_level($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 $Texinfo::Commands::root_commands{$cmdname}
and $sectioning_heading_commands{$cmdname}) {
my $content_href = $self->command_contents_href($element, 'contents');
if ($content_href ne '') {
$heading = "$heading";
}
}
my $heading_class = $level_corrected_cmdname;
unshift @heading_classes, $heading_class;
if ($self->in_preformatted()) {
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);
}
} 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 .= $tables_of_contents;
$result .= $mini_toc;
return $result;
}
foreach my $command (keys(%sectioning_heading_commands), 'node') {
$default_commands_conversion{$command} = \&_convert_heading_command;
}
sub _convert_raw_command($$$$$)
{
my $self = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
my $content = shift;
if ($cmdname eq 'html') {
return $content;
}
$self->_noticed_line_warn(sprintf(__("raw format %s is not converted"),
$cmdname), $command->{'source_info'});
return &{$self->formatting_function('format_protect_text')}($self, $content);
}
foreach my $command (keys(%format_raw_commands)) {
$default_commands_conversion{$command} = \&_convert_raw_command;
}
sub _convert_inline_command($$$$)
{
my $self = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
my $format_arg = shift @$args;
my $format;
if (defined($format_arg)) {
$format = $format_arg->{'monospacetext'};
}
return '' if (!defined($format) or $format eq '');
my $arg_index = undef;
if ($inline_format_commands{$cmdname}) {
if ($cmdname eq 'inlinefmtifelse' and !$self->is_format_expanded($format)) {
$arg_index = 1;
} elsif ($self->is_format_expanded($format)) {
$arg_index = 0;
}
} elsif (defined($command->{'extra'}->{'expand_index'})) {
$arg_index = 0;
}
if (defined($arg_index) and $arg_index < scalar(@$args)) {
my $text_arg = $args->[$arg_index];
if ($text_arg) {
if ($text_arg->{'normal'}) {
return $text_arg->{'normal'};
} elsif ($text_arg->{'raw'}) {
return $text_arg->{'raw'};
}
}
}
return '';
}
foreach my $command (grep {$brace_commands{$_} eq 'inline'}
keys(%brace_commands)) {
$default_commands_conversion{$command} = \&_convert_inline_command;
}
sub _indent_with_table($$$;$)
{
my $self = shift;
my $cmdname = shift;
my $content = shift;
my $extra_classes = shift;
my @classes;
@classes = @$extra_classes if (defined($extra_classes));
unshift @classes, $cmdname;
return $self->html_attribute_class('table', \@classes)
.'>
'.$self->get_info('non_breaking_space').'
'.$content
."
\n";
}
sub _convert_preformatted_command($$$$$)
{
my $self = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
my $content = shift;
my @classes;
# this is mainly for classes as there are purprosely no classes
# for small*
my $main_cmdname;
if ($small_block_associated_command{$cmdname}) {
$main_cmdname = $small_block_associated_command{$cmdname};
push @classes, $cmdname;
} else {
$main_cmdname = $cmdname;
}
if ($cmdname eq 'example') {
if ($command->{'args'}) {
for my $example_arg (@{$command->{'args'}}) {
# 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 ($content ne '' and !$self->in_string()) {
if ($self->get_conf('COMPLEX_FORMAT_IN_TABLE')
and $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";
}
} else {
return $content;
}
}
foreach my $preformatted_command (keys(%preformatted_commands)) {
$default_commands_conversion{$preformatted_command}
= \&_convert_preformatted_command;
}
sub _convert_indented_command($$$$$)
{
my $self = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
my $content = shift;
my @classes;
my $main_cmdname;
if ($small_block_associated_command{$cmdname}) {
push @classes, $cmdname;
$main_cmdname = $small_block_associated_command{$cmdname};
} else {
$main_cmdname = $cmdname;
}
if ($content ne '' and !$self->in_string()) {
if ($self->get_conf('COMPLEX_FORMAT_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";
}
} else {
return $content;
}
}
$default_commands_conversion{'indentedblock'} = \&_convert_indented_command;
sub _convert_verbatim_command($$$$$)
{
my $self = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
my $content = shift;
if (!$self->in_string()) {
return $self->html_attribute_class('pre', [$cmdname]).'>'
.$content . '';
} else {
return $content;
}
}
$default_commands_conversion{'verbatim'} = \&_convert_verbatim_command;
sub _convert_displaymath_command($$$$$)
{
my $self = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
my $content = shift;
if ($self->in_string()) {
return $content;
}
my $result = '';
$result .= $self->html_attribute_class('div', [$cmdname]).'>';
if ($self->get_conf('HTML_MATH')
and $self->get_conf('HTML_MATH') eq 'mathjax') {
$self->register_file_information('mathjax', 1);
$result .= $self->html_attribute_class('em', ['tex2jax_process']).'>'
."\\[$content\\]".'';
} else {
$result .= $self->html_attribute_class('em').'>'."$content".'';
}
$result .= '';
return $result;
}
$default_commands_conversion{'displaymath'} = \&_convert_displaymath_command;
sub _convert_verbatiminclude_command($$$$)
{
my $self = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
my $verbatim_include_verbatim
= Texinfo::Convert::Utils::expand_verbatiminclude($self, $self, $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 = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
my $content = shift;
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 = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
if (defined($command->{'extra'}->{'misc_args'}->[0])) {
my $sp_nr = $command->{'extra'}->{'misc_args'}->[0];
if ($self->in_preformatted() or $self->in_string()) {
return "\n" x $sp_nr;
} else {
return ($self->get_info('line_break_element')."\n") x $sp_nr;
}
}
}
$default_commands_conversion{'sp'} = \&_convert_sp_command;
sub _convert_exdent_command($$$$)
{
my $self = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
my $arg = $self->get_pending_formatted_inline_content().$args->[0]->{'normal'};
if ($self->in_string()) {
return $arg ."\n";
}
# FIXME do something with CSS? Currently nothing is defined for exdent
my $preformatted = $self->in_preformatted();
if ($self->in_preformatted()) {
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 = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
if ($self->in_string()) {
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 = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
return '' if (!$args->[0] or !$command->{'extra'}->{'titlepage'});
if (!$self->in_string()) {
return $self->html_attribute_class('strong', [$cmdname])
.">$args->[0]->{'normal'}"
.$self->get_info('line_break_element')."\n";
} else {
return $args->[0]->{'normal'} . "\n";
}
}
$default_commands_conversion{'author'} = \&_convert_author_command;
sub _convert_title_command($$$$)
{
my $self = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
return '' if (!$args->[0]);
if (!$self->in_string()) {
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 = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
return '' if (!$args->[0]);
if (!$self->in_string()) {
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 = shift;
my $cmdname = shift;
my $command = shift;
my $global_commands = $self->get_info('global_commands');
if ($global_commands and $global_commands->{'copying'}) {
return $self->convert_tree({'contents'
=> $global_commands->{'copying'}->{'contents'}},
'convert insertcopying');
}
return '';
}
$default_commands_conversion{'insertcopying'}
= \&_convert_insertcopying_command;
sub _convert_listoffloats_command($$$$)
{
my $self = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
my $floats = $self->get_info('floats');
if (!$self->in_string()
and $command->{'extra'} and $command->{'extra'}->{'type'}
and defined($command->{'extra'}->{'type'}->{'normalized'})
and $floats
and $floats->{$command->{'extra'}->{'type'}->{'normalized'}}
and @{$floats->{$command->{'extra'}->{'type'}->{'normalized'}}}) {
my $listoffloats_name = $command->{'extra'}->{'type'}->{'normalized'};
my $result = $self->html_attribute_class('dl', [$cmdname]).">\n" ;
foreach my $float (@{$floats->{$listoffloats_name}}) {
my $float_href = $self->command_href($float);
next if (!$float_href);
$result .= '
';
my $float_text = $self->command_text($float);
if (defined($float_text) and $float_text ne '') {
if ($float_href) {
$result .= "$float_text";
} else {
$result .= $float_text;
}
}
$result .= '
';
my $caption;
my $caption_cmdname;
if ($float->{'extra'}->{'shortcaption'}) {
$caption = $float->{'extra'}->{'shortcaption'};
$caption_cmdname = 'shortcaption';
} elsif ($float->{'extra'}->{'caption'}) {
$caption = $float->{'extra'}->{'caption'};
$caption_cmdname = 'caption';
}
my $caption_text;
my @caption_classes;
if ($caption) {
$caption_text = $self->convert_tree_new_formatting_context(
$caption->{'args'}->[0], $cmdname, 'listoffloats');
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 _in_preformatted_in_menu($)
{
my $self = shift;
return 1 if ($self->get_conf('SIMPLE_MENU'));
my @pre_classes = $self->preformatted_classes_stack();
foreach my $pre_class (@pre_classes) {
return 1 if ($preformatted_commands{$pre_class});
}
return 0;
}
sub _convert_menu_command($$$$$)
{
my $self = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
my $content = shift;
return $content if ($cmdname eq 'detailmenu');
my $html_menu_entry_index
= $self->shared_conversion_state('html_menu_entry_index', 0);
$$html_menu_entry_index = 0;
if ($content !~ /\S/) {
return '';
}
# This can probably only happen with incorrect input,
# for instance menu in copying
# FIXME check?
if ($self->in_string()) {
return $content;
}
if ($self->get_conf('SIMPLE_MENU')) {
return $self->html_attribute_class('div', [$cmdname]).'>'
.$content ."\n";
}
my $begin_row = '';
my $end_row = '';
if ($self->_in_preformatted_in_menu()) {
$begin_row = '
';
$end_row = '
';
}
return $self->html_attribute_class('table', [$cmdname])
." border=\"0\" cellspacing=\"0\">${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 = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
my $content = shift;
my ($caption, $prepended)
= Texinfo::Convert::Converter::float_name_caption($self, $command);
my $caption_command_name;
if (defined($caption)) {
$caption_command_name = $caption->{'cmdname'};
}
if ($self->in_string()) {
my $prepended_text;
if ($prepended) {
$prepended_text = $self->convert_tree_new_formatting_context(
$prepended, 'float prepended');
} else {
$prepended_text = '';
}
my $caption_text = '';
if ($caption) {
$caption_text = $self->convert_tree_new_formatting_context(
{'contents' => $caption->{'args'}->[0]->{'contents'}},
'float caption');
}
return $prepended.$content.$caption_text;
}
my $id = $self->command_id($command);
my $id_str = '';;
if (defined($id) and $id ne '') {
$id_str = " id=\"$id\"";
}
my $prepended_text;
my $caption_text = '';
if ($prepended) {
# FIXME add a span with a class name for the prependend information
# if not empty?
$prepended_text = $self->convert_tree_new_formatting_context(
{'cmdname' => 'strong',
'args' => [{'type' => 'brace_command_arg',
'contents' => [$prepended]}]},
'float number type');
if ($caption) {
# register the converted prepended tree to be prepended to
# the first paragraph in caption formatting
$self->register_pending_formatted_inline_content($caption_command_name,
$prepended_text);
$caption_text = $self->convert_tree_new_formatting_context(
$caption->{'args'}->[0], 'float caption');
my $cancelled_prepended
= $self->cancel_pending_formatted_inline_content($caption_command_name);
$prepended_text = '' if (not defined($cancelled_prepended));
}
if ($prepended_text ne '') {
$prepended_text = '
'.$prepended_text.'
';
}
} else {
$caption_text = $self->convert_tree_new_formatting_context(
$caption->{'args'}->[0], 'float caption')
if (defined($caption));
}
my $float_type_number_caption = '';
if ($caption_text ne '') {
$float_type_number_caption
= $self->html_attribute_class('div', [$caption_command_name]). '>'
.$caption_text.'';
} elsif (defined($prepended) and $prepended_text ne '') {
$float_type_number_caption
= $self->html_attribute_class('div', ['type-number-float']). '>'
. $prepended_text .'';
}
return $self->html_attribute_class('div', [$cmdname]). "${id_str}>\n".$content.
$float_type_number_caption . '';
}
$default_commands_conversion{'float'} = \&_convert_float_command;
sub _convert_quotation_command($$$$$)
{
my $self = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
my $content = shift;
$self->cancel_pending_formatted_inline_content($cmdname);
my @classes;
my $main_cmdname;
if ($small_block_associated_command{$cmdname}) {
push @classes, $cmdname;
$main_cmdname = $small_block_associated_command{$cmdname};
} else {
$main_cmdname = $cmdname;
}
unshift @classes, $main_cmdname;
my $attribution = '';
if ($command->{'extra'} and $command->{'extra'}->{'authors'}) {
# FIXME 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 (@{$command->{'extra'}->{'authors'}}) {
my $centered_author = $self->gdt("\@center --- \@emph{{author}}\n",
{'author' => $author->{'args'}->[0]->{'contents'}});
$centered_author->{'parent'} = $command;
$attribution .= $self->convert_tree($centered_author, 'convert quotation author');
}
}
if (!$self->in_string()) {
return $self->html_attribute_class('blockquote', \@classes).">\n"
. $content . "\n" . $attribution;
} else {
return $content.$attribution;
}
}
$default_commands_conversion{'quotation'} = \&_convert_quotation_command;
sub _convert_cartouche_command($$$$$)
{
my $self = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
my $content = shift;
if ($content =~ /\S/ and !$self->in_string()) {
return $self->html_attribute_class('table', [$cmdname])
." border=\"1\">
\n". $content ."
\n";
}
return $content;
}
$default_commands_conversion{'cartouche'} = \&_convert_cartouche_command;
sub _convert_itemize_command($$$$$)
{
my $self = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
my $content = shift;
if ($self->in_string()) {
return $content;
}
my $command_as_argument_name;
my $mark_class_name;
if (defined($command->{'extra'})
and defined($command->{'extra'}->{'command_as_argument'})) {
my $command_as_argument = $command->{'extra'}->{'command_as_argument'};
if ($command_as_argument->{'cmdname'} eq 'click'
and $command_as_argument->{'extra'}->{'clickstyle'}) {
$command_as_argument_name = $command_as_argument->{'extra'}->{'clickstyle'};
} else {
$command_as_argument_name = $command_as_argument->{'cmdname'};
}
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_info('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($command->{'args'}->[0],
'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 = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
my $content = shift;
if ($self->in_string()) {
return $content;
}
if ($content eq '') {
return '';
}
my $type_attribute = '';
my $start_attribute = '';
my $specification = $command->{'extra'}->{'enumerate_specification'};
if (defined $specification) {
my ($start, $type);
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 = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
my $content = shift;
if ($self->in_string()) {
return $content;
}
if ($content =~ /\S/) {
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 = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
my $content = shift;
if ($self->in_string()) {
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 = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
my $content = shift;
if ($self->in_string()) {
return $content;
}
if ($command->{'parent'}->{'cmdname'}
and $command->{'parent'}->{'cmdname'} eq 'itemize') {
if ($content =~ /\S/) {
return '
';
} else {
return '';
}
} elsif ($command->{'parent'}->{'type'}
and $command->{'parent'}->{'type'} eq 'table_term') {
if ($args->[0]) {
my $table_item_tree = $self->table_item_content_tree($command,
[$args->[0]->{'tree'}]);
my $result = $self->convert_tree($table_item_tree, 'convert table_item_tree');
if ($self->in_preformatted()) {
my @pre_classes = $self->preformatted_classes_stack();
foreach my $pre_class (@pre_classes) {
if ($preformatted_code_commands{$pre_class}) {
$result = $self->html_attribute_class('code',
['table-term-preformatted-code']).'>'
. $result . '';
last;
}
}
}
my $index_id = $self->command_id($command);
my $anchor;
my $anchor_span_open = '';
my $anchor_span_close = '';
if (defined($index_id)) {
$anchor = $self->_get_copiable_anchor($index_id);
$index_id = " id='$index_id'";
if ($anchor ne '') {
$anchor_span_open = '';
$anchor_span_close = '';
}
} else {
$anchor = '';
$index_id = '';
}
return "
$anchor_span_open$result$anchor$anchor_span_close
\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 = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
my $content = shift;
my $cell_nr = $command->{'extra'}->{'cell_number'};
my $row = $command->{'parent'};
my $row_cmdname = $row->{'contents'}->[0]->{'cmdname'};
my $multitable = $row->{'parent'}->{'parent'};
my $fractions = '';
my $cf = $multitable->{'extra'}->{'columnfractions'};
if ($cf) {
if (exists($cf->{'extra'}->{'misc_args'}->[$cell_nr-1])) {
my $fraction = sprintf('%d',
100*$cf->{'extra'}->{'misc_args'}->[$cell_nr-1]);
$fractions = " width=\"$fraction%\"";
}
}
$content =~ s/^\s*//;
$content =~ s/\s*$//;
if ($self->in_string()) {
return $content;
}
if ($row_cmdname eq 'headitem') {
return "
" . $content . '
';
} else {
return "
" . $content . '
';
}
}
$default_commands_conversion{'tab'} = \&_convert_tab_command;
sub _convert_xref_commands($$$$)
{
my $self = shift;
my $cmdname = shift;
my $root = shift;
my $args = shift;
my $tree;
my $name;
if ($cmdname ne 'inforef'
and $args->[2]
and defined($args->[2]->{'normal'}) and $args->[2]->{'normal'} ne '') {
$name = $args->[2]->{'normal'};
} elsif ($args->[1]
and defined($args->[1]->{'normal'}) and $args->[1]->{'normal'} ne '') {
$name = $args->[1]->{'normal'}
}
if ($cmdname eq 'inforef') {
$args->[3] = $args->[2];
$args->[2] = undef;
}
my $file_arg_tree;
my $file = '';
if ($args->[3]
and defined($args->[3]->{'monospacetext'})
and $args->[3]->{'monospacetext'} ne '') {
$file_arg_tree = $args->[3]->{'tree'};
$file = $args->[3]->{'monospacetext'};
}
my $book = '';
$book = $args->[4]->{'normal'}
if ($args->[4] and defined($args->[4]->{'normal'}));
# internal reference
if ($cmdname ne 'inforef' and $book eq '' and $file eq ''
and $root->{'extra'}
and $root->{'extra'}->{'node_argument'}
and defined($root->{'extra'}->{'node_argument'}->{'normalized'})
and !$root->{'extra'}->{'node_argument'}->{'manual_content'}
and $self->label_command($root->{'extra'}->{'node_argument'}->{'normalized'})) {
my $node
= $self->label_command($root->{'extra'}->{'node_argument'}->{'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 $command = $self->command_root_element_command($node);
$command = $node if (!$node->{'extra'}->{'associated_section'}
or $node->{'extra'}->{'associated_section'} ne $command);
my $href = $self->command_href($command, undef, $root);
if (!defined($name)) {
if ($self->get_conf('xrefautomaticsectiontitle') eq 'on'
and $node->{'extra'}->{'associated_section'}) {
$command = $node->{'extra'}->{'associated_section'};
$name = $self->command_text($command, 'text_nonumber');
} elsif ($node->{'cmdname'} eq 'float') {
if (!$self->get_conf('XREF_USE_FLOAT_LABEL')) {
$name = $self->command_text($command);
}
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 !$self->in_preformatted())) {
$name = $self->command_text($command, 'text_nonumber');
#die "$command $command->{'normalized'}" if (!defined($name));
} elsif (defined($args->[0]->{'monospace'})) {
$name = $args->[0]->{'monospace'};
} else {
$name = '';
}
}
my $reference = $name;
$reference = $self->html_attribute_class('a', [$cmdname])
." href=\"$href\">$name" if ($href ne ''
and !$self->in_string());
# maybe use {'extra'}->{'node_argument'}?
my $is_section = ($command->{'cmdname'} ne 'node'
and $command->{'cmdname'} ne 'anchor'
and $command->{'cmdname'} ne 'float');
if ($cmdname eq 'pxref') {
$tree = $self->gdt('see {reference_name}',
{ 'reference_name' => {'type' => '_converted', 'text' => $reference} });
} elsif ($cmdname eq 'xref') {
$tree = $self->gdt('See {reference_name}',
{ 'reference_name' => {'type' => '_converted', 'text' => $reference} });
} elsif ($cmdname eq 'ref') {
$tree = $self->gdt('{reference_name}',
{ 'reference_name' => {'type' => '_converted', 'text' => $reference} });
}
} else {
# external reference
# We setup a node_entry based on the parsed node entry instead of
# simply using the parsed node entry to be able to use the $file argument
my $node_entry = {};
if ($root->{'extra'} and $root->{'extra'}->{'node_argument'}) {
$node_entry->{'node_content'}
= $root->{'extra'}->{'node_argument'}->{'node_content'}
if ($root->{'extra'}->{'node_argument'}->{'node_content'});
$node_entry->{'normalized'} = $root->{'extra'}->{'node_argument'}->{'normalized'}
if (exists($root->{'extra'}->{'node_argument'}->{'normalized'}));
}
# file argument takes precedence over the file in the node (file)node entry
if (defined($file_arg_tree) and $file ne '') {
$node_entry->{'manual_content'} = $file_arg_tree->{'contents'};
} elsif ($root->{'extra'}
and $root->{'extra'}->{'node_argument'}
and $root->{'extra'}->{'node_argument'}->{'manual_content'}) {
$node_entry->{'manual_content'}
= $root->{'extra'}->{'node_argument'}->{'manual_content'};
my $file_with_node_tree = {'type' => '_code',
'contents' => [@{$node_entry->{'manual_content'}}]};
$file = $self->convert_tree($file_with_node_tree, 'node file in ref');
}
my $href = $self->command_href($node_entry, undef, $root);
if ($book eq '') {
if (!defined($name)) {
my $node_name = $self->command_text($node_entry);
$name = $node_name;
} elsif ($file ne '') {
$name = "($file)$name";
}
} elsif (!defined($name) and $node_entry->{'node_content'}) {
my $node_no_file_tree = {'type' => '_code',
'contents' => [@{$node_entry->{'node_content'}}]};
my $node_name = $self->convert_tree($node_no_file_tree, 'node in ref');
if (defined($node_name) and $node_name ne 'Top') {
$name = $node_name;
}
}
# not exactly sure when it happens. Something like @ref{(file),,,Manual}?
$name = $args->[0]->{'monospace'}
if (!defined($name)
# FIXME could it really be Top?
and $args->[0]->{'monospace'} ne 'Top');
$name = '' if (!defined($name));
my $reference = $name;
my $book_reference = '';
if (!$self->in_string() and $href ne '') {
# attribute to distiguish links to Texinfo manuals from other links
# and to provide manual name of target
my $manual_name_attribute = '';
if ($file) {
if (not $self->get_conf('NO_CUSTOM_HTML_ATTRIBUTE')) {
$manual_name_attribute = "data-manual=\"".
&{$self->formatting_function('format_protect_text')}($self, $file)."\" ";
}
}
if ($name ne '') {
$reference = "$name";
} elsif ($book ne '') {
$book_reference = "$book";
}
}
if ($cmdname eq 'pxref') {
if (($book ne '') and ($href ne '') and ($reference ne '')) {
$tree = $self->gdt('see {reference} in @cite{{book}}',
{ 'reference' => {'type' => '_converted', 'text' => $reference},
'book' => {'type' => '_converted', 'text' => $book }});
} elsif ($book_reference ne '') {
$tree = $self->gdt('see @cite{{book_reference}}',
{ 'book_reference' => {'type' => '_converted',
'text' => $book_reference }});
} elsif (($book ne '') and ($reference ne '')) {
$tree = $self->gdt('see `{section}\' in @cite{{book}}',
{ 'section' => {'type' => '_converted', 'text' => $reference},
'book' => {'type' => '_converted', 'text' => $book }});
} elsif ($book ne '') { # should seldom or even never happen
$tree = $self->gdt('see @cite{{book}}',
{'book' => {'type' => '_converted', 'text' => $book }});
} elsif ($href ne '') {
$tree = $self->gdt('see {reference}',
{ 'reference' => {'type' => '_converted', 'text' => $reference} });
} elsif ($reference ne '') {
$tree = $self->gdt('see `{section}\'', {
'section' => {'type' => '_converted', 'text' => $reference} });
}
} elsif ($cmdname eq 'xref' or $cmdname eq 'inforef') {
if (($book ne '') and ($href ne '') and ($reference ne '')) {
$tree = $self->gdt('See {reference} in @cite{{book}}',
{ 'reference' => {'type' => '_converted', 'text' => $reference},
'book' => {'type' => '_converted', 'text' => $book }});
} elsif ($book_reference ne '') {
$tree = $self->gdt('See @cite{{book_reference}}',
{ 'book_reference' => {'type' => '_converted',
'text' => $book_reference }});
} elsif (($book ne '') and ($reference ne '')) {
$tree = $self->gdt('See `{section}\' in @cite{{book}}',
{ 'section' => {'type' => '_converted', 'text' => $reference},
'book' => {'type' => '_converted', 'text' => $book }});
} elsif ($book ne '') { # should seldom or even never happen
$tree = $self->gdt('See @cite{{book}}',
{'book' => {'type' => '_converted', 'text' => $book }});
} elsif ($href ne '') {
$tree = $self->gdt('See {reference}',
{ 'reference' => {'type' => '_converted', 'text' => $reference} });
} elsif ($reference ne '') {
$tree = $self->gdt('See `{section}\'', {
'section' => {'type' => '_converted', 'text' => $reference} });
}
} else { # @ref
if (($book ne '') and ($href ne '') and ($reference ne '')) {
$tree = $self->gdt('{reference} in @cite{{book}}',
{ 'reference' => {'type' => '_converted', 'text' => $reference},
'book' => {'type' => '_converted', 'text' => $book }});
} elsif ($book_reference ne '') {
$tree = $self->gdt('@cite{{book_reference}}',
{ 'book_reference' => {'type' => '_converted',
'text' => $book_reference }});
} elsif (($book ne '') and ($reference ne '')) {
$tree = $self->gdt('`{section}\' in @cite{{book}}',
{ 'section' => {'type' => '_converted', 'text' => $reference},
'book' => {'type' => '_converted', 'text' => $book }});
} elsif ($book ne '') { # should seldom or even never happen
$tree = $self->gdt('@cite{{book}}',
{'book' => {'type' => '_converted', 'text' => $book }});
} elsif ($href ne '') {
$tree = $self->gdt('{reference}',
{ 'reference' => {'type' => '_converted', 'text' => $reference} });
} elsif ($reference ne '') {
$tree = $self->gdt('`{section}\'', {
'section' => {'type' => '_converted', 'text' => $reference} });
}
}
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 $command(keys(%ref_commands)) {
$default_commands_conversion{$command} = \&_convert_xref_commands;
}
# note that $cmdname is always cindex, in particular to make
# customization possible, the actual @-command could be any index entry
# @-command.
sub _convert_index_command($$$$)
{
my $self = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
my $index_id = $self->command_id($command);
if (defined($index_id) and $index_id ne ''
and !$self->in_multi_expanded()
and !$self->in_string()) {
my $result = &{$self->formatting_function('format_separate_anchor')}($self,
$index_id, 'index-entry-id');
$result .= "\n" unless ($self->in_preformatted());
return $result;
}
return '';
}
$default_commands_conversion{'cindex'} = \&_convert_index_command;
sub _convert_printindex_command($$$$)
{
my $self = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
my $index_name;
if ($command->{'extra'} and $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_info('index_entries_by_letter');
if (!defined($index_entries_by_letter)
or !$index_entries_by_letter->{$index_name}
or !@{$index_entries_by_letter->{$index_name}}) {
return '';
}
#foreach my $letter_entry (@{$index_entries_by_letter->{$index_name}}) {
# print STDERR "IIIIIII $letter_entry->{'letter'}\n";
# foreach my $index_entry (@{$letter_entry->{'entries'}}) {
# print STDERR " ".join('|', keys(%$index_entry))."||| $index_entry->{'key'}\n";
# }
#}
return '' if ($self->in_string());
my %letter_id;
my %letter_is_symbol;
# First collect the links that are used in entries and in letter summaries
my $symbol_idx = 0;
foreach my $letter_entry (@{$index_entries_by_letter->{$index_name}}) {
my $letter = $letter_entry->{'letter'};
my $index_element_id = $self->from_element_direction('This', 'target');
if (!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 $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 {
$identifier = $index_element_id . "_${index_name}_letter-${letter}";
}
$letter_id{$letter} = $identifier;
}
$self->_new_document_context($cmdname);
# Next do the entries to determine the letters that are not empty
my @letter_entries;
my $result_index_entries = '';
my $formatted_index_entries
= $self->shared_conversion_state('formatted_index_entries', {});
foreach my $letter_entry (@{$index_entries_by_letter->{$index_name}}) {
my $letter = $letter_entry->{'letter'};
my $entries_text = '';
my $entry_nr = -1;
foreach my $index_entry_ref (@{$letter_entry->{'entries'}}) {
# FIXME format instead of ignoring
next if ($index_entry_ref->{'entry_element'}->{'extra'}
and ($index_entry_ref->{'entry_element'}->{'extra'}->{'seeentry'}
or $index_entry_ref->{'entry_element'}->{'extra'}->{'seealso'}));
$entry_nr++;
# to avoid double error messages set ignore_notice if an entry was
# already formatted once, for example if there are multiple printindex.
my $already_formatted;
if (!$formatted_index_entries->{$index_entry_ref}) {
$formatted_index_entries->{$index_entry_ref} = 1;
} else {
$formatted_index_entries->{$index_entry_ref}++;
}
my $subentries_tree = $self->comma_index_subentries_tree($index_entry_ref);
my @entry_contents = @{$index_entry_ref->{'entry_content'}};
push @entry_contents, @{$subentries_tree->{'contents'}}
if (defined($subentries_tree));
my $entry_tree = {'contents' => \@entry_contents};
$entry_tree->{'type'} = '_code' if ($index_entry_ref->{'in_code'});
my $entry;
if ($formatted_index_entries->{$index_entry_ref} > 1) {
$entry = $self->convert_tree_new_formatting_context($entry_tree,
"index $index_name l $letter index entry $entry_nr",
"index formatted $formatted_index_entries->{$index_entry_ref}")
} else {
$entry = $self->convert_tree($entry_tree,
"index $index_name l $letter index entry $entry_nr");
}
next if ($entry !~ /\S/);
next if ($self->get_conf('NO_TOP_NODE_OUTPUT')
and defined($index_entry_ref->{'entry_node'})
and $index_entry_ref->{'entry_node'}->{'extra'}
and $index_entry_ref->{'entry_node'}->{'extra'}->{'normalized'}
and $index_entry_ref->{'entry_node'}->{'extra'}->{'normalized'} eq 'Top');
$entry = '' .$entry .'' if ($index_entry_ref->{'in_code'});
my $entry_href = $self->command_href($index_entry_ref->{'entry_element'});
my $associated_command;
if ($self->get_conf('NODE_NAME_IN_INDEX')) {
$associated_command = $index_entry_ref->{'entry_node'};
if (!defined($associated_command)) {
$associated_command
= $self->command_node($index_entry_ref->{'entry_element'});
}
}
if (!$associated_command) {
$associated_command
= $self->command_root_element_command($index_entry_ref->{'entry_element'});
if (!$associated_command) {
# Use Top if not associated command found
$associated_command
= $self->tree_unit_element_command(
$self->global_direction_element('Top'));
}
}
my ($associated_command_href, $associated_command_text);
if ($associated_command) {
$associated_command_href = $self->command_href($associated_command);
$associated_command_text = $self->command_text($associated_command);
}
$entries_text .= '
\n";
$result .= $summary_header;
}
# now format the index entries
$result
.= $self->html_attribute_class('table', ["$index_name-entries-$cmdname"])
." border=\"0\">\n" . '
\n";
$result .= $summary_footer
}
return $result . "\n";
}
$default_commands_conversion{'printindex'} = \&_convert_printindex_command;
sub _contents_inline_element($$$)
{
my $self = shift;
my $cmdname = shift;
my $command = shift;
print STDERR "CONTENTS_INLINE $cmdname\n" if ($self->get_conf('DEBUG'));
my $content = &{$self->formatting_function('format_contents')}($self,
$cmdname, $command);
if ($content) {
my ($special_element_variety, $special_element, $class_base,
$special_element_direction)
= $self->command_name_special_element_information($cmdname);
# FIXME is element- the best prefix?
my $result = $self->html_attribute_class('div', ["element-${class_base}"]);
my $heading;
if ($special_element) {
my $id = $self->command_id($special_element);
if ($id ne '') {
$result .= " id=\"$id\"";
}
$heading = $self->command_text($special_element);
} else {
# happens when called as convert() and not output()
#cluck "$cmdname special element not defined";
$heading = $self->convert_tree($self->get_conf('SPECIAL_ELEMENTS_HEADING')
->{$special_element_variety},
"convert $cmdname special heading");
}
$result .= ">\n";
$result .= &{$self->formatting_function('format_heading_text')}($self,
$cmdname, [$class_base.'-heading'], $heading,
$self->get_conf('CHAPTER_HEADER_LEVEL'))."\n";
$result .= $content . "\n";
return $result;
}
return '';
}
sub _convert_informative_command($$$)
{
my $self = shift;
my $cmdname = shift;
my $command = shift;
return '' if ($self->in_string());
Texinfo::Common::set_informative_command_value($self, $command);
if ($cmdname eq 'documentlanguage') {
$self->_translate_names();
}
return '';
}
foreach my $informative_command (@informative_global_commands) {
$default_commands_conversion{$informative_command}
= \&_convert_informative_command;
}
sub _convert_contents_command($$$)
{
my $self = shift;
my $cmdname = shift;
my $command = shift;
return '' if ($self->in_string());
$cmdname = 'shortcontents' if ($cmdname eq 'summarycontents');
Texinfo::Common::set_informative_command_value($self, $command);
my $structuring = $self->get_info('structuring');
if ($self->get_conf('CONTENTS_OUTPUT_LOCATION') eq 'inline'
and ($cmdname eq 'contents' or $cmdname eq 'shortcontents')
and $self->get_conf($cmdname)
and $structuring and $structuring->{'sectioning_root'}
and scalar(@{$structuring->{'sections_list'}}) > 1) {
return $self->_contents_inline_element($cmdname, $command);
}
return '';
}
foreach my $contents_comand (@contents_commands) {
$default_commands_conversion{$contents_comand} = \&_convert_contents_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}};
}
sub _open_quotation_command($$$)
{
my $self = shift;
my $cmdname = shift;
my $command = shift;
my $formatted_quotation_arg_to_prepend;
if ($command->{'args'} and $command->{'args'}->[0]
and $command->{'args'}->[0]->{'contents'}
and @{$command->{'args'}->[0]->{'contents'}}) {
$formatted_quotation_arg_to_prepend
= $self->convert_tree($self->gdt('@b{{quotation_arg}:} ',
{'quotation_arg' => $command->{'args'}->[0]->{'contents'}}),
"open $cmdname prepended arg");
}
$self->register_pending_formatted_inline_content($cmdname,
$formatted_quotation_arg_to_prepend);
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 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;
sub default_type_conversion($$)
{
my $self = shift;
my $type = shift;
return $default_types_conversion{$type};
}
sub type_conversion($$)
{
my $self = shift;
my $type = shift;
return $self->{'types_conversion'}->{$type};
}
my %default_types_open;
sub default_type_open($$)
{
my $self = shift;
my $type = shift;
return $default_types_open{$type};
}
# Ignored commands
foreach my $type ('ignorable_spaces_after_command', 'preamble_before_beginning',
'preamble_before_setfilename',
'spaces_at_end',
'spaces_before_paragraph',
'spaces_after_close_brace') {
$default_types_conversion{$type} = undef;
}
sub _convert_paragraph_type($$$$)
{
my $self = shift;
my $type = shift;
my $element = shift;
my $content = shift;
$content = $self->get_associated_formatted_inline_content($element).$content;
if ($self->paragraph_number() == 1) {
my $in_format = $self->top_block_command();
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') {
return $content;
}
}
}
return $content if ($self->in_string());
if ($content =~ /\S/) {
my $align = $self->in_align();
if ($align and $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 = shift;
my $type = shift;
my $element = shift;
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) {
# FIXME maybe add or $pre_class eq 'menu' to override
# 'menu' with 'menu-comment'?
$pre_class = $class unless ($pre_class
and $preformatted_code_commands{$pre_class}
and !($preformatted_code_commands{$class}
or $class eq 'menu'));
}
return $pre_class.'-preformatted';
}
sub _convert_preformatted_type($$$$)
{
my $self = shift;
my $type = shift;
my $element = shift;
my $content = shift;
if (!defined($content)) {
cluck "content undef in _convert_preformatted_type "
.Texinfo::Common::debug_print_element($element);
}
$content = $self->get_associated_formatted_inline_content($element).$content;
return '' if ($content eq '');
my $pre_class = $self->_preformatted_class();
if ($self->top_block_command() eq 'multitable') {
$content =~ s/^\s*//;
$content =~ s/\s*$//;
}
# 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 ($element->{'parent'}->{'type'}
and $element->{'parent'}->{'type'} eq 'menu_entry_description') {
if (!$self->_in_preformatted_in_menu()) {
# If not in preformatted block command (nor in SIMPLE_MENU),
# 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';
}
}
if ($self->in_string()) {
return $content;
}
$content =~ s/^\n/\n\n/; # a newline immediately after a
is ignored.
my $result = $self->html_attribute_class('pre', [$pre_class]).'>'
. $content . '
';
# this may happen with lines without textual content
# between a def* and def*x.
if ($element->{'parent'}->{'cmdname'}
and $element->{'parent'}->{'cmdname'} =~ /^def/) {
$result = '
'.$result.'
';
}
return $result;
}
$default_types_conversion{'preformatted'} = \&_convert_preformatted_type;
sub _convert_bracketed_type($$$$) {
my $self = shift;
my $type = shift;
my $element = shift;
my $content = shift;
return '{'.$content.'}';
}
$default_types_conversion{'bracketed'} = \&_convert_bracketed_type;
sub _convert_definfoenclose_type($$$$) {
my $self = shift;
my $type = shift;
my $element = shift;
my $content = shift;
# FIXME add a span to mark the original command as a class?
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 = shift;
my $type = shift;
my $element = shift;
my $text = shift;
my $context = $self->{'document_context'}->[-1];
# API info: in_verbatim() API code conforming would be:
#if ($self->in_verbatim()) {
if ($context->{'verbatim'}) { # inline these calls for speed
# API info: using the API to allow for customization would be:
#return &{$self->formatting_function('format_protect_text')}($self, $text);
return $self->_default_format_protect_text($text);
}
return $text if $context->{'raw'};
# API info: in_raw() API code conforming would be:
#return $text if ($self->in_raw());
my $formatting_context = $context->{'formatting_context'}->[-1];
$text = uc($text) if $formatting_context->{'upper_case'};
# API info: in_upper_case() API code conforming would be:
#$text = uc($text) if ($self->in_upper_case());
# 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: get_conf() API code conforming would be:
#if ($self->get_conf('ENABLE_ENCODING')
# and $self->get_conf('OUTPUT_ENCODING_NAME')
# and $self->get_conf('OUTPUT_ENCODING_NAME') eq 'utf-8') {
if ($self->{'conf'}->{'ENABLE_ENCODING'}
and $self->{'conf'}->{'OUTPUT_ENCODING_NAME'}
and $self->{'conf'}->{'OUTPUT_ENCODING_NAME'} eq 'utf-8') {
$text = Texinfo::Convert::Unicode::unicode_text($text,
(in_code($self) or in_math($self)));
# API info: in_code() API code conforming and
# API info: in_math() API code conforming would be:
#} elsif (!$self->in_code() and !$self->in_math()) {
} elsif (!$context->{'monospace'}->[-1] and !$context->{'math'}) {
# API info: get_conf() API code conforming would be:
#if ($self->get_conf('USE_NUMERIC_ENTITY')) {
if ($self->{'conf'}->{'USE_NUMERIC_ENTITY'}) {
$text = $self->xml_format_text_with_numeric_entities($text);
# API info: get_conf() API code conforming would be:
#} elsif ($self->get_conf('USE_ISO')) {
} elsif ($self->{'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($self));
# API info: in_non_breakable_space() API code conforming would be:
#if ($self->in_non_breakable_space()) {
if ($formatting_context->{'space_protected'}) {
if ($text =~ /(\S*[_-]\S*)/) {
my $open = $self->html_attribute_class('span', ['w-nolinebreak-text']);
if ($open ne '') {
$open .= '>';
# Protect spaces in the html leading attribute in case we are in 'w'
$open =~ s/ /\x{1F}/g;
# Special span to avoid breaking at _-
$text =~ s/(\S*[_-]\S*)/${open}$1<\/span>/g;
}
}
$text .= $self->get_info('non_breaking_space') if (chomp($text));
# 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 = shift;
my $type = shift;
my $element = shift;
my $text = shift;
$text = uc($text) if ($self->in_upper_case());
# need to hide \ otherwise it is protected in protect_text
if (!$self->in_code() and !$self->in_math()) {
$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_row_type($$$$) {
my $self = shift;
my $type = shift;
my $element = shift;
my $content = shift;
return $content if ($self->in_string());
if ($content =~ /\S/) {
my $result = '
' . $content . '
';
if ($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 = shift;
my $type = shift;
my $element = shift;
my $content = shift;
return $content if ($self->in_string());
if ($content =~ /\S/) {
return '' . $content . '' . "\n";
} else {
return '';
}
}
$default_types_conversion{'multitable_head'} = \&_convert_multitable_head_type;
sub _convert_multitable_body_type($$$$) {
my $self = shift;
my $type = shift;
my $element = shift;
my $content = shift;
return $content if ($self->in_string());
if ($content =~ /\S/) {
return '' . $content . '' . "\n";
} else {
return '';
}
}
$default_types_conversion{'multitable_body'} = \&_convert_multitable_body_type;
sub _convert_menu_entry_type($$$)
{
my $self = shift;
my $type = shift;
my $element = shift;
my $href = '';
my $rel = '';
my $section;
my $node_entry = $element->{'extra'}->{'menu_entry_node'};
# external node
my $external_node;
if ($node_entry->{'manual_content'}) {
$href = $self->command_href($node_entry, undef, $element);
$external_node = 1;
} else {
my $node = $self->label_command($node_entry->{'normalized'});
if ($node) {
# if !NODE_NAME_IN_MENU, we pick the associated section, except if
# the node is the element command
if ($node->{'extra'}
and $node->{'extra'}->{'associated_section'}
and !$self->get_conf('NODE_NAME_IN_MENU')
and !($self->command_root_element_command($node) eq $node)) {
$section = $node->{'extra'}->{'associated_section'};
$href = $self->command_href($section, undef, $element);
} else {
$href = $self->command_href($node, undef, $element);
}
if ($node->{'extra'}->{'isindex'}) {
# Mark the target as an index. See
# http://microformats.org/wiki/existing-rel-values#HTML5_link_type_extensions
$rel = ' rel="index"';
}
}
}
my $html_menu_entry_index
= $self->shared_conversion_state('html_menu_entry_index', 0);
${$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 = $self->in_string();
if ($self->_in_preformatted_in_menu() or $in_string) {
my $result_name_node = '';
my $i = 0;
my @args = @{$element->{'args'}};
while (@args) {
last if ($args[0]->{'type'}
and $args[0]->{'type'} eq 'menu_entry_description');
my $arg = shift @args;
if ($arg->{'type'} and $arg->{'type'} eq 'menu_entry_node') {
my $name = $self->convert_tree(
{'type' => '_code', 'contents' => $arg->{'contents'}},
"menu_arg menu_entry_node preformatted [$i]");
if ($href ne '' and !$in_string) {
$result_name_node .= "".$name."";
} else {
$result_name_node .= $name;
}
} elsif ($arg->{'type'} and $arg->{'type'} eq 'menu_entry_leading_text') {
my $text = $arg->{'text'};
$text =~ s/\*/$MENU_SYMBOL/;
$result_name_node .= $text;
} else {
$result_name_node .= $self->convert_tree($arg, "menu_arg preformatted [$i]");
}
$i++;
}
my $description = '';
foreach my $arg (@args) {
$description .= $self->convert_tree($arg, "menu_arg preformatted [$i]");
$i++;
}
if (!$self->get_conf('SIMPLE_MENU') and not $in_string) {
my $pre_class = $self->_preformatted_class();
$result_name_node = $self->html_attribute_class('pre', [$pre_class]).'>'
. $result_name_node . '';
}
return $result_name_node . $description;
}
my $name;
my $name_no_number;
if ($section) {
$name = $self->command_text($section);
$name_no_number = $self->command_text($section, 'text_nonumber');
if ($href ne '' and $name ne '') {
$name = "".$name."";
}
}
if (!defined($name) or $name eq '') {
if ($element->{'extra'}->{'menu_entry_name'}) {
$name = $self->convert_tree($element->{'extra'}->{'menu_entry_name'},
'convert menu_entry_name');
}
if (!defined($name) or $name eq '') {
if ($node_entry->{'manual_content'}) {
$name = $self->command_text($node_entry);
} else {
$name = $self->convert_tree({'type' => '_code',
'contents' => $node_entry->{'node_content'}},
'menu_arg name');
}
}
$name =~ s/^\s*//;
$name_no_number = $name;
if ($href ne '') {
$name = "".$name."";
}
$name = "$MENU_SYMBOL ".$name;
}
my $description = '';
if ($element->{'extra'}->{'menu_entry_description'}) {
$description = $self->convert_tree($element->{'extra'}->{'menu_entry_description'},
'menu_arg description');
if ($self->get_conf('AVOID_MENU_REDUNDANCY')) {
$description = '' if (_simplify_text_for_comparison($name_no_number)
eq _simplify_text_for_comparison($description));
}
}
my $non_breaking_space = $self->get_info('non_breaking_space');
return '
';
}
}
$default_types_conversion{'menu_comment'} = \&_convert_menu_comment_type;
sub _convert_before_item_type($$$$)
{
my $self = shift;
my $type = shift;
my $element = shift;
my $content = shift;
return '' if ($content !~ /\S/);
return $content if ($self->in_string());
my $top_block_command = $self->top_block_command();
if ($top_block_command eq 'itemize' or $top_block_command eq 'enumerate') {
return '
'. $content .'
';
} elsif ($top_block_command eq 'table' or $top_block_command eq 'vtable'
or $top_block_command eq 'ftable') {
return '
'."\n";
}
}
$default_types_conversion{'before_item'} = \&_convert_before_item_type;
sub _convert_def_line_type($$$$)
{
my $self = shift;
my $type = shift;
my $element = shift;
my $content = shift;
if ($self->in_string()) {
# should probably never happen
return &{$self->formatting_function('format_protect_text')}($self,
Texinfo::Convert::Text::convert_to_text(
$element, Texinfo::Convert::Text::copy_options_for_convert_text($self, 1)));
}
my $index_label = '';
my $index_id = $self->command_id($element);
if (defined($index_id) and $index_id ne '' and !$self->in_multi_expanded()) {
$index_label = " id=\"$index_id\"";
}
my $arguments
= Texinfo::Convert::Utils::definition_arguments_content($element);
my @classes = ();
my $command_name;
if ($Texinfo::Common::def_aliases{$element->{'extra'}->{'def_command'}}) {
$command_name = $Texinfo::Common::def_aliases{$element->{'extra'}->{'def_command'}};
} else {
$command_name = $element->{'extra'}->{'def_command'};
}
my $original_command_name;
if ($Texinfo::Common::def_aliases{$element->{'extra'}->{'original_def_cmdname'}}) {
my $original_def_cmdname = $element->{'extra'}->{'original_def_cmdname'};
$original_command_name = $Texinfo::Common::def_aliases{$original_def_cmdname};
push @classes, "$original_def_cmdname-alias-$original_command_name";
} else {
$original_command_name = $element->{'extra'}->{'original_def_cmdname'};
}
if ($command_name ne $original_command_name) {
push @classes, "def-cmd-$command_name";
}
unshift @classes, $original_command_name;
my $result_type = '';
if ($element->{'extra'}
and $element->{'extra'}->{'def_parsed_hash'}
and $element->{'extra'}->{'def_parsed_hash'}->{'type'}) {
my $type_text = $self->_convert({'type' => '_code',
'contents' => [$element->{'extra'}->{'def_parsed_hash'}->{'type'}]});
if ($type_text ne '') {
$result_type = $self->html_attribute_class('code', ['def-type']).'>'.
$type_text .'';
}
if ($self->get_conf('deftypefnnewline') eq 'on'
and ($command_name eq 'deftypefn' or $command_name eq 'deftypeop')) {
$result_type .= $self->get_info('line_break_element');
}
}
my $result_name = '';
if ($element->{'extra'} and $element->{'extra'}->{'def_parsed_hash'}
and defined($element->{'extra'}->{'def_parsed_hash'}->{'name'})) {
my $name_content = $element->{'extra'}->{'def_parsed_hash'}->{'name'};
$result_name = $self->html_attribute_class('strong', ['def-name']).'>'.
$self->_convert({'type' => '_code', 'contents' => [$name_content]})
.'';
}
my $def_space = ' ';
if ($element->{'extra'}->{'omit_def_name_space'}) {
$def_space = '';
}
my $result_arguments = '';
if ($arguments) {
# arguments not only metasyntactic variables
# (deftypefn, deftypevr, deftypeop, deftypecv)
if ($Texinfo::Common::def_no_var_arg_commands{$command_name}) {
my $arguments_formatted = $self->_convert({'type' => '_code',
'contents' => $arguments});
$result_arguments = $self->html_attribute_class('code',
['def-code-arguments']).'>'
. $arguments_formatted.''
if ($arguments_formatted =~ /\S/);
} else {
# only metasyntactic variable arguments (deffn, defvr, deftp, defop, defcv)
push @{$self->{'document_context'}->[-1]->{'monospace'}}, 0;
my $arguments_formatted = $self->_convert({'contents' => $arguments});
pop @{$self->{'document_context'}->[-1]->{'monospace'}};
if ($arguments_formatted =~ /\S/) {
$result_arguments = $self->html_attribute_class('var',
['def-var-arguments']).'>'
. $arguments_formatted .'';
}
}
}
my $def_call = '';
$def_call .= $result_type . ' ' if ($result_type ne '');
$def_call .= $result_name;
$def_call .= $def_space . $result_arguments if ($result_arguments ne '');
if (!$self->get_conf('DEF_TABLE')) {
my $category;
if ($element->{'extra'} and $element->{'extra'}->{'def_parsed_hash'}
and defined($element->{'extra'}->{'def_parsed_hash'}->{'category'})) {
$category = $element->{'extra'}->{'def_parsed_hash'}->{'category'};
}
my $category_result = '';
my $category_tree;
if (defined($category) and $category ne '') {
if ($element->{'extra'}->{'def_parsed_hash'}->{'class'}) {
if ($command_name eq 'deftypeop'
and $element->{'extra'}->{'def_parsed_hash'}->{'type'}
and $self->get_conf('deftypefnnewline') eq 'on') {
$category_tree = $self->gdt('{category} on @code{{class}}:@* ',
{'category' => $category,
'class' => $element->{'extra'}->{'def_parsed_hash'}->{'class'}});
} elsif ($command_name eq 'defop' or $command_name eq 'deftypeop') {
$category_tree = $self->gdt('{category} on @code{{class}}: ',
{'category' => $category,
'class' => $element->{'extra'}->{'def_parsed_hash'}->{'class'}});
} elsif ($command_name eq 'defcv' or $command_name eq 'deftypecv') {
$category_tree = $self->gdt('{category} of @code{{class}}: ',
{'category' => $category,
'class' => $element->{'extra'}->{'def_parsed_hash'}->{'class'}});
}
} elsif ($element->{'extra'}->{'def_parsed_hash'}->{'type'}
and ($command_name eq 'deftypefn' or $command_name eq 'deftypeop')
and $self->get_conf('deftypefnnewline') eq 'on') {
# FIXME 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.
$category_tree = $self->gdt('{category}:@* ', {'category' => $category});
} else {
$category_tree = $self->gdt('{category}: ', {'category' => $category});
}
$category_result = $self->convert_tree($category_tree);
}
if ($category_result ne '') {
my $open = $self->html_attribute_class('span', ['category-def']);
if ($open ne '') {
$category_result = $open.'>'.$category_result.'';
}
}
my $anchor_span_open = '';
my $anchor_span_close = '';
my $anchor = $self->_get_copiable_anchor($index_id);
if ($anchor ne '') {
$anchor_span_open = '';
$anchor_span_close = '';
}
return $self->html_attribute_class('dt', \@classes)
. "$index_label>" . $category_result . $anchor_span_open
. $def_call
. "$anchor$anchor_span_close\n";
} else {
my $category_result = '';
my $definition_category_tree
= Texinfo::Convert::Utils::definition_category_tree($self, $element);
$category_result
= $self->convert_tree({'contents' => [$definition_category_tree]})
if (defined($definition_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";
}
}
sub _get_copiable_anchor {
my ($self, $id) = @_;
my $result = '';
if ($id and $self->get_conf('COPIABLE_LINKS')) {
my $paragraph_symbol = $self->get_info('paragraph_symbol');
$result = $self->html_attribute_class('a', ['copiable-link'])
." href='#$id'> $paragraph_symbol";
}
return $result;
}
$default_types_conversion{'def_line'} = \&_convert_def_line_type;
sub _convert_def_item_type($$$$)
{
my $self = shift;
my $type = shift;
my $element = shift;
my $content = shift;
return $content if ($self->in_string());
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;
sub _convert_def_command($$$$$) {
my $self = shift;
my $cmdname = shift;
my $command = shift;
my $args = shift;
my $content = shift;
return $content if ($self->in_string());
my @classes;
my $command_name;
if ($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";
if (!$self->get_conf('DEF_TABLE')) {
return $self->html_attribute_class('dl', \@classes).">\n"
. $content ."\n";
} else {
return $self->html_attribute_class('table', \@classes)." width=\"100%\">\n"
. $content . "\n";
}
}
foreach my $command (keys(%def_commands)) {
$default_commands_conversion{$command} = \&_convert_def_command;
}
sub _convert_table_item_type($$$$)
{
my $self = shift;
my $type = shift;
my $element = shift;
my $content = shift;
return $content if ($self->in_string());
if ($content =~ /\S/) {
return '
' . $content . '
'."\n";
}
}
$default_types_conversion{'table_item'} = \&_convert_table_item_type;
$default_types_conversion{'inter_item'} = \&_convert_table_item_type;
sub _contents_shortcontents_in_title($)
{
my $self = shift;
my $result = '';
my $structuring = $self->get_info('structuring');
if ($structuring and $structuring->{'sectioning_root'}
and scalar(@{$structuring->{'sections_list'}}) > 1
and $self->get_conf('CONTENTS_OUTPUT_LOCATION') eq 'after_title') {
foreach my $cmdname ('contents', 'shortcontents') {
if ($self->get_conf($cmdname)) {
my $contents_text = $self->_contents_inline_element($cmdname, undef);
if ($contents_text ne '') {
$result .= $contents_text . $self->get_conf('DEFAULT_RULE')."\n";
}
}
}
}
return $result;
}
# Convert @titlepage. Falls back to simpletitle.
sub _default_format_titlepage($)
{
my $self = shift;
my $titlepage_text;
my $global_commands = $self->get_info('global_commands');
if ($global_commands->{'titlepage'}) {
$titlepage_text = $self->convert_tree({'contents'
=> $global_commands->{'titlepage'}->{'contents'}},
'convert titlepage');
} else {
my $simpletitle_tree = $self->get_info('simpletitle_tree');
if ($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 = '';
$result .= $titlepage_text.$self->get_conf('DEFAULT_RULE')."\n"
if (defined($titlepage_text));
$result .= $self->_contents_shortcontents_in_title();
return $result;
}
sub _default_format_title_titlepage($)
{
my $self = shift;
my $result = '';
if ($self->get_conf('SHOW_TITLE')) {
if ($self->get_conf('USE_TITLEPAGE_FOR_TITLE')) {
$result .= &{$self->formatting_function('format_titlepage')}($self);
} else {
my $simpletitle_tree = $self->get_info('simpletitle_tree');
if ($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 .= $self->_contents_shortcontents_in_title();
}
}
return $result;
}
# Function for converting special elements
sub _convert_special_element_type($$$$)
{
my $self = shift;
my $type = shift;
my $element = shift;
my $content = shift;
if ($self->in_string()) {
return '';
}
my $result = '';
my $special_element_variety = $element->{'extra'}->{'special_element_variety'};
$result .= join('', $self->close_registered_sections_level(0));
my $special_element_body
.= &{$self->special_element_body_formatting($special_element_variety)}($self,
$special_element_variety, $element);
# This may happen with footnotes in regions that are not expanded,
# like @copying or @titlepage
if ($special_element_body eq '') {
return '';
}
my $id = $self->command_id($element);
my $class_base
= $self->get_conf('SPECIAL_ELEMENTS_CLASS')->{$special_element_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 $self->count_elements_in_filename('current',
$element->{'structure'}->{'unit_filename'}) == 1) {
$result .= &{$self->formatting_function('format_navigation_header')}($self,
$self->get_conf('MISC_BUTTONS'), undef, $element);
}
my $heading = $self->command_text($element);
my $level = $self->get_conf('CHAPTER_HEADER_LEVEL');
if ($special_element_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_element_body . '';
$result .= &{$self->formatting_function('format_element_footer')}($self, $type,
$element, $content);
return $result;
}
$default_types_conversion{'special_element'} = \&_convert_special_element_type;
# Function for converting the top-level elements in the conversion corresponding to
# a section or a node. The node and associated section appear together in
# the tree unit top-level element. $ELEMENT was created in this module (in
# _prepare_conversion_tree_units), with type 'unit' (it's not a tree element created
# by the parser). $CONTENT is the contents of the node/section, already converted.
sub _convert_tree_unit_type($$$$)
{
my $self = shift;
my $type = shift;
my $element = shift;
my $content = shift;
if ($self->in_string()) {
if (defined($content)) {
return $content;
} else {
return '';
}
}
my $result = '';
my $tree_unit = $element;
if (not $tree_unit->{'structure'}
or not $tree_unit->{'structure'}->{'unit_prev'}) {
$result .= $self->get_info('title_titlepage');
if (not $tree_unit->{'structure'}
or not $tree_unit->{'structure'}->{'unit_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
$result .= join('', $self->close_registered_sections_level(0));
return $result;
}
}
$result .= $content;
my $command;
if ($element->{'extra'} and $element->{'extra'}->{'unit_command'}) {
$command = $element->{'extra'}->{'unit_command'};
}
$result .= &{$self->formatting_function('format_element_footer')}($self, $type,
$element, $content, $command);
return $result;
}
$default_types_conversion{'unit'} = \&_convert_tree_unit_type;
# for tree unit elements and special elements
sub _default_format_element_footer($$$$;$)
{
my $self = shift;
my $type = shift;
my $element = shift;
my $content = shift;
my $command = shift;
my $result = '';
my $is_top = $self->element_is_tree_unit_top($element);
my $next_is_top = ($element->{'structure'}->{'unit_next'}
and $self->element_is_tree_unit_top($element->{'structure'}->{'unit_next'}));
my $next_is_special = (defined($element->{'structure'}->{'unit_next'})
and defined($element->{'structure'}->{'unit_next'}->{'type'})
and $element->{'structure'}->{'unit_next'}->{'type'} eq 'special_element');
my $end_page = (!$element->{'structure'}->{'unit_next'}
or (defined($element->{'structure'}->{'unit_filename'})
and $element->{'structure'}->{'unit_filename'}
ne $element->{'structure'}->{'unit_next'}->{'structure'}->{'unit_filename'}
and $self->count_elements_in_filename('remaining',
$element->{'structure'}->{'unit_filename'}) == 1));
my $is_special = (defined($element->{'type'})
and $element->{'type'} eq 'special_element');
if (($end_page or $next_is_top or $next_is_special or $is_top)
and $self->get_conf('VERTICAL_HEAD_NAVIGATION')
and ($self->get_conf('SPLIT') ne 'node'
or $self->get_conf('HEADERS') or $is_special or $is_top)) {
$result .= "
"."\n";
}
my $rule = '';
my $buttons;
if ($end_page) {
$result .= join('', $self->close_registered_sections_level(0));
# setup buttons for navigation footer
if (($is_top or $is_special)
and ($self->get_conf('SPLIT') or !$self->get_conf('MONOLITHIC'))
and (($self->get_conf('HEADERS')
or ($self->get_conf('SPLIT') and $self->get_conf('SPLIT') ne 'node')))) {
if ($is_top) {
$buttons = $self->get_conf('TOP_BUTTONS');
} else {
$buttons = $self->get_conf('MISC_BUTTONS');
}
} elsif ($self->get_conf('SPLIT') eq 'section') {
$buttons = $self->get_conf('SECTION_FOOTER_BUTTONS');
} elsif ($self->get_conf('SPLIT') eq 'chapter') {
$buttons = $self->get_conf('CHAPTER_FOOTER_BUTTONS');
} elsif ($self->get_conf('SPLIT') eq 'node') {
if ($self->get_conf('HEADERS')) {
my $no_footer_word_count;
if ($self->get_conf('WORDS_IN_PAGE')) {
# FIXME it seems that NO-BREAK SPACE and NEXT LINE (NEL) may
# not be in \h and \v in some case, but not sure which case it is
my @cnt = split(/\P{Word}*[\h\v]+\P{Word}*/, $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);
}
}
}
# FIXME 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 ((!$element->{'structure'}->{'unit_next'}
or (defined($element->{'structure'}->{'unit_filename'})
and $element->{'structure'}->{'unit_filename'}
ne $element->{'structure'}->{'unit_next'}->{'structure'}->{'unit_filename'}))
and $self->get_conf('footnotestyle') eq 'end') {
$result .= &{$self->formatting_function('format_footnotes_segment')}($self);
}
if (!$buttons or $is_top or $is_special
or ($end_page and ($self->get_conf('SPLIT') eq 'chapter'
or $self->get_conf('SPLIT') eq 'section'))
or ($self->get_conf('SPLIT') eq 'node' and $self->get_conf('HEADERS'))) {
$rule = $self->get_conf('DEFAULT_RULE');
}
if (!$end_page and ($is_top or $next_is_top or ($next_is_special
and !$is_special))) {
$rule = $self->get_conf('BIG_RULE');
}
if ($buttons or !$end_page or $self->get_conf('PROGRAM_NAME_IN_FOOTER')) {
$result .= "$rule\n" if ($rule);
}
if ($buttons) {
my $cmdname;
$cmdname = $command->{'cmdname'} if ($command and $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 = shift;
my $context = shift;
my $document_global_context = shift;
push @{$self->{'document_context'}},
{'context' => $context,
'formatting_context' => [{'context_name' => $context}],
'composition_context' => [''],
'formats' => [],
'monospace' => [0],
'document_global_context' => $document_global_context,
'block_commands' => [],
};
if (defined($document_global_context)) {
$self->{'document_global_context'}++;
}
}
sub _pop_document_context($)
{
my $self = shift;
my $context = pop @{$self->{'document_context'}};
if (defined($context->{'document_global_context'})) {
$self->{'document_global_context'}--;
}
}
# can be set through Texinfo::Config::texinfo_register_file_id_setting_function
my %customizable_file_id_setting_references;
foreach my $customized_reference ('label_target_name', 'node_file_name',
'sectioning_command_target_name', 'tree_unit_file_name',
'special_element_target_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_frame_files' => \&_default_format_frame_files,
'format_footnotes_segment' => \&_default_format_footnotes_segment,
'format_footnotes_sequence' => \&_default_format_footnotes_sequence,
'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,
);
# not up for customization
%default_css_string_formatting_references = (
'format_protect_text' => \&_default_css_string_format_protect_text,
);
%defaults_format_special_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,
);
sub _reset_unset_no_arg_commands_formatting_context($$$$;$)
{
my $self = shift;
my $cmdname = shift;
my $reset_context = shift;
my $ref_context = shift;
my $translate = shift;
# should never happen as unset is set at configuration
if (!defined ($self->{'no_arg_commands_formatting'}->{$reset_context}->{$cmdname})) {
$self->{'no_arg_commands_formatting'}->{$reset_context}->{$cmdname}->{'unset'} = 1;
}
my $no_arg_command_context
= $self->{'no_arg_commands_formatting'}->{$reset_context}->{$cmdname};
if (defined($ref_context)) {
if ($no_arg_command_context->{'unset'}) {
foreach my $key (keys(%{$self->{'no_arg_commands_formatting'}->{$ref_context}->{$cmdname}})) {
# both 'translated' and (possibly translated) 'text' are
# reused
$no_arg_command_context->{$key}
= $self->{'no_arg_commands_formatting'}->{$ref_context}->{$cmdname}->{$key}
}
}
}
if ($translate
and $no_arg_command_context->{'tree'}
and not defined($no_arg_command_context->{'translated'})) {
my $translated_tree
= $no_arg_command_context->{'tree'};
my $translation_result;
if ($reset_context eq 'normal') {
$translation_result
= $self->convert_tree($translated_tree, "no arg $cmdname translated");
} elsif ($reset_context eq 'preformatted') {
# there does not seems to be anything simpler...
my $preformatted_command_name = 'example';
$self->_new_document_context();
push @{$self->{'document_context'}->[-1]->{'composition_context'}},
$preformatted_command_name;
# should not be needed for at commands no brace translation strings
push @{$self->{'document_context'}->[-1]->{'preformatted_classes'}},
$pre_class_commands{$preformatted_command_name};
$translation_result
= $self->convert_tree($translated_tree, "no arg $cmdname translated");
# only pop the main context
$self->_pop_document_context();
} elsif ($reset_context eq 'string') {
$translation_result = $self->convert_tree_new_formatting_context({'type' => '_string',
'contents' => [$translated_tree]},
'translated_string', "string no arg $cmdname translated");
} elsif ($reset_context eq 'css_string') {
$translation_result = $self->html_convert_css_string($translated_tree);
}
$no_arg_command_context->{'text'}
= $translation_result;
}
}
sub _complete_no_arg_commands_formatting($$;$)
{
my $self = shift;
my $cmdname = shift;
my $translate = shift;
_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);
}
sub _set_non_breaking_space($$)
{
my $self = shift;
my $non_breaking_space = shift;
$self->{'non_breaking_space'} = $non_breaking_space;
}
# transform to
sub _xhtml_re_close_lone_element($)
{
my $element = shift;
$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 = shift;
my $files = shift;
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->document_warn($self,
sprintf(__("could not open html refs config file %s: %s"),
$htmlxref_file_name, $!));
next;
}
my $line_nr = 0;
my %variables;
while (my $hline = ) {
my $line = $hline;
$line_nr++;
next if $hline =~ /^\s*#/;
#$hline =~ s/[#]\s.*//;
$hline =~ s/^\s*//;
next if $hline =~ /^\s*$/;
chomp ($hline);
if ($hline =~ s/^\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 "$split_or_mono $Texi2HTML::Config::htmlxref_entries{$split_or_mono} $line_nr\n";
if (!defined($split_or_mono)) {
$self->line_warn($self, __("missing type"),
{'file_name' => $fname, 'line_nr' => $line_nr});
next;
} elsif (!defined($htmlxref_entries{$split_or_mono})) {
$self->line_warn($self, sprintf(__("unrecognized type: %s"),
$split_or_mono),
{'file_name' => $fname, 'line_nr' => $line_nr});
next;
}
my $href = shift @htmlxref;
next if ($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');
}
$htmlxref->{$manual} = {} if (!$htmlxref->{$manual});
$htmlxref->{$manual}->{$split_or_mono} = $href;
}
if (!close (HTMLXREF)) {
$self->document_warn($self, sprintf(__(
"error on closing html refs config file %s: %s"),
$file, $!));
}
}
return $htmlxref;
}
sub _load_htmlxref_files {
my ($self) = @_;
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) {
$self->{'htmlxref_files'} = [$encoded_htmlxref_file_name];
} else {
$self->document_warn($self,
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 = File::Spec->catdir($curdir, '.texinfo');
if (defined($self->{'parser_info'})
and defined($self->{'parser_info'}->{'input_directory'})) {
my $input_directory = $self->{'parser_info'}->{'input_directory'};
if ($input_directory ne '.' and $input_directory ne '') {
unshift @htmlxref_dirs, $input_directory;
}
}
} elsif ($self->{'language_config_dirs'}
and @{$self->{'language_config_dirs'}}) {
@htmlxref_dirs = @{$self->{'language_config_dirs'}};
}
unshift @htmlxref_dirs, '.';
my @texinfo_htmlxref_files;
# 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;
}
} elsif (defined($self->get_conf('HTMLXREF_FILE'))) {
$htmlxref_file_name = $self->get_conf('HTMLXREF_FILE');
}
if (defined($htmlxref_file_name)) {
my ($encoded_htmlxref_file_name, $htmlxref_file_encoding)
= $self->encoded_output_file_name($htmlxref_file_name);
@texinfo_htmlxref_files
= Texinfo::Common::locate_init_file($encoded_htmlxref_file_name,
\@htmlxref_dirs, 1);
}
$self->{'htmlxref_files'} = \@texinfo_htmlxref_files;
}
$self->{'htmlxref'} = {};
if (scalar(@{$self->{'htmlxref_files'}})) {
$self->{'htmlxref'} = _parse_htmlxref_files($self,
$self->{'htmlxref_files'});
}
}
# converter state
#
# output_init_conf
#
# API exists
# shared_conversion_state
#
# API exists
# current_filename
# 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
# index_entries
# index_entries_by_letter
# jslicenses
#
# 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
# no_arg_commands_formatting
# style_commands_formatting
# 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
# 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
# special_elements_targets
# special_elements_directions
# global_target_elements_directions
#
# API exists
# elements_in_file_count # the number of tree unit elements in file
# file_counters # begin at elements_in_file_count decrease
# # each time the tree unit element is closed
#
# API exists
# document_global_context_css
# file_css
#
# API exists
# files_information
#
# tree_units
# out_filepaths
# current_root_element
# seen_ids
# ignore_notice
# options_latex_math
# htmlxref_files
# htmlxref
# check_htmlxref_already_warned
#
# from Converter
# labels
#
# explained_commands # not defined in the converter per se but in an
# # @-command conversion function and only used there
# element_explanation_contents # same as above
my %special_characters = (
'paragraph_symbol' => ['¶', '00B6'],
'left_quote' => ['‘', '2018'],
'right_quote' => ['’', '2019'],
'bullet' => ['•', '2022'],
'non_breaking_space' => [undef, '00A0'],
);
sub converter_initialize($)
{
my $self = shift;
%{$self->{'css_element_class_styles'}} = %css_element_class_styles;
_load_htmlxref_files($self);
# duplicate such as not to modify the defaults
my $conf_default_no_arg_commands_formatting_normal
= Storable::dclone($default_no_arg_commands_formatting{'normal'});
my %special_characters_set;
my $output_encoding = $self->get_conf('OUTPUT_ENCODING_NAME');
foreach my $special_character (keys(%special_characters)) {
my ($default_entity, $unicode_point) = @{$special_characters{$special_character}};
if ($self->get_conf('ENABLE_ENCODING')
and Texinfo::Convert::Unicode::unicode_point_decoded_in_encoding(
$output_encoding, $unicode_point)) {
$special_characters_set{$special_character} = chr(hex($unicode_point));
} elsif ($self->get_conf('USE_NUMERIC_ENTITY')) {
$special_characters_set{$special_character} = ''.hex($unicode_point).';';
} else {
$special_characters_set{$special_character} = $default_entity;
}
}
if (defined($special_characters_set{'non_breaking_space'})) {
my $non_breaking_space = $special_characters_set{'non_breaking_space'};
$self->_set_non_breaking_space($non_breaking_space);
foreach my $space_command (' ', "\t", "\n") {
$conf_default_no_arg_commands_formatting_normal->{$space_command}->{'text'}
= $self->{'non_breaking_space'};
}
$conf_default_no_arg_commands_formatting_normal->{'tie'}->{'text'}
= $self->substitute_html_non_breaking_space(
$default_no_arg_commands_formatting{'normal'}->{'tie'}->{'text'});
} else {
$self->_set_non_breaking_space($xml_named_entity_nbsp);
}
$self->{'paragraph_symbol'} = $special_characters_set{'paragraph_symbol'};
if (not defined($self->get_conf('OPEN_QUOTE_SYMBOL'))) {
$self->set_conf('OPEN_QUOTE_SYMBOL', $special_characters_set{'left_quote'});
}
if (not defined($self->get_conf('CLOSE_QUOTE_SYMBOL'))) {
$self->set_conf('CLOSE_QUOTE_SYMBOL', $special_characters_set{'right_quote'});
}
if (not defined($self->get_conf('MENU_SYMBOL'))) {
$self->set_conf('MENU_SYMBOL', $special_characters_set{'bullet'});
}
if ($self->get_conf('USE_NUMERIC_ENTITY')) {
foreach my $command (keys(%Texinfo::Convert::Unicode::unicode_entities)) {
$conf_default_no_arg_commands_formatting_normal->{$command}->{'text'}
= $Texinfo::Convert::Unicode::unicode_entities{$command};
}
}
if ($self->get_conf('USE_XML_SYNTAX')) {
foreach my $customization_variable ('BIG_RULE', 'DEFAULT_RULE') {
my $variable_value = $self->get_conf($customization_variable);
if (defined($variable_value)) {
my $closed_lone_element = _xhtml_re_close_lone_element($variable_value);
if ($closed_lone_element ne $variable_value) {
$self->force_conf($customization_variable, $closed_lone_element);
}
}
}
$self->{'line_break_element'} = ' ';
} else {
$self->{'line_break_element'} = ' ';
}
$conf_default_no_arg_commands_formatting_normal->{'*'}->{'text'}
= $self->{'line_break_element'};
$self->{'types_conversion'} = {};
my $customized_types_conversion = Texinfo::Config::GNUT_get_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();
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_type_formatting
= Texinfo::Config::GNUT_get_types_formatting_info();
foreach my $type (keys(%$customized_type_formatting)) {
# Used in cvs.init.
$self->{'code_types'}->{$type}
= $customized_type_formatting->{$type}->{'code'};
$self->{'pre_class_types'}->{$type}
= $customized_type_formatting->{$type}->{'pre_class'};
}
$self->{'commands_conversion'} = {};
# FIXME put value in a category in Texinfo::Common?
my $customized_commands_conversion
= Texinfo::Config::GNUT_get_commands_conversion();
foreach my $command (keys(%line_commands), keys(%brace_commands),
keys (%block_commands), keys(%nobrace_commands)) {
if (exists($customized_commands_conversion->{$command})) {
$self->{'commands_conversion'}->{$command}
= $customized_commands_conversion->{$command};
} else {
if ($self->get_conf('FORMAT_MENU') ne 'menu'
and ($command eq 'menu' or $command eq 'detailmenu')) {
$self->{'commands_conversion'}->{$command} = undef;
} elsif ($format_raw_commands{$command}
and !$self->{'expanded_formats_hash'}->{$command}) {
} elsif (exists($default_commands_conversion{$command})) {
$self->{'commands_conversion'}->{$command}
= $default_commands_conversion{$command};
}
}
}
$self->{'commands_open'} = {};
my $customized_commands_open
= Texinfo::Config::GNUT_get_commands_open();
foreach my $command (keys(%line_commands), keys(%brace_commands),
keys (%block_commands), keys(%nobrace_commands)) {
if (exists($customized_commands_open->{$command})) {
$self->{'commands_open'}->{$command}
= $customized_commands_open->{$command};
} elsif (exists($default_commands_open{$command})) {
$self->{'commands_open'}->{$command}
= $default_commands_open{$command};
}
}
$self->{'no_arg_commands_formatting'} = {};
foreach my $context ('normal', 'preformatted', 'string', 'css_string') {
$self->{'no_arg_commands_formatting'}->{$context} = {};
foreach my $command (keys(%{$default_no_arg_commands_formatting{'normal'}})) {
my $no_arg_command_customized_formatting
= Texinfo::Config::GNUT_get_no_arg_command_formatting($command, $context);
if (defined($no_arg_command_customized_formatting)) {
$self->{'no_arg_commands_formatting'}->{$context}->{$command}
= $no_arg_command_customized_formatting;
} else {
my $context_default_default_no_arg_commands_formatting
= $default_no_arg_commands_formatting{$context};
if ($context eq 'normal') {
$context_default_default_no_arg_commands_formatting
= $conf_default_no_arg_commands_formatting_normal;
}
if (defined($context_default_default_no_arg_commands_formatting->{$command})) {
if ($self->get_conf('ENABLE_ENCODING')
and Texinfo::Convert::Unicode::brace_no_arg_command(
$command, $self->get_conf('OUTPUT_ENCODING_NAME'))) {
$self->{'no_arg_commands_formatting'}->{$context}->{$command}
= { 'text' => Texinfo::Convert::Unicode::brace_no_arg_command(
$command, $self->get_conf('OUTPUT_ENCODING_NAME'))};
# reset CSS for itemize command arguments
if ($context eq 'css_string'
and exists($brace_commands{$command})
and $command ne 'bullet' and $command ne 'w'
and not $special_list_mark_css_string_no_arg_command{$command}) {
my $css_string
= $self->{'no_arg_commands_formatting'}->{$context}->{$command}->{'text'};
$css_string = '"'.$css_string.'"';
$self->{'css_element_class_styles'}->{"ul.mark-$command"}
= "list-style-type: $css_string";
}
} else {
$self->{'no_arg_commands_formatting'}->{$context}->{$command}
= $context_default_default_no_arg_commands_formatting->{$command};
}
} else {
$self->{'no_arg_commands_formatting'}->{$context}->{$command}
= {'unset' => 1};
}
}
}
}
# set sane defaults in case there is none and the default formatting
# function is used
foreach my $command (keys(%{$default_no_arg_commands_formatting{'normal'}})) {
if ($self->{'commands_conversion'}->{$command}
and $self->{'commands_conversion'}->{$command}
eq $default_commands_conversion{$command}) {
$self->_complete_no_arg_commands_formatting($command);
}
}
$self->{'style_commands_formatting'} = {};
foreach my $context (keys(%style_commands_formatting)) {
$self->{'style_commands_formatting'}->{$context} = {};
foreach my $command (keys(%{$style_commands_formatting{$context}})) {
my $style_commands_formatting_info
= Texinfo::Config::GNUT_get_style_command_formatting($command, $context);
if (defined($style_commands_formatting_info)) {
$self->{'style_commands_formatting'}->{$context}->{$command}
= $style_commands_formatting_info;
} elsif (exists($style_commands_formatting{$context}->{$command})) {
$self->{'style_commands_formatting'}->{$context}->{$command}
= $style_commands_formatting{$context}->{$command};
}
}
}
$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)
= Texinfo::Config::GNUT_get_accent_command_formatting($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'}]);
$self->{'file_id_setting'} = {};
my $customized_file_id_setting_references
= Texinfo::Config::GNUT_get_file_id_setting_references();
# first check the validity of the names
foreach my $customized_file_id_setting_ref
(sort(keys(%{$customized_file_id_setting_references}))) {
if (!$customizable_file_id_setting_references{$customized_file_id_setting_ref}) {
$self->document_warn($self,
sprintf(__("Unknown file and id setting function: %s"),
$customized_file_id_setting_ref));
} else {
$self->{'file_id_setting'}->{$customized_file_id_setting_ref}
= $customized_file_id_setting_references->{$customized_file_id_setting_ref};
}
}
my $customized_formatting_references = Texinfo::Config::GNUT_get_formatting_references();
# first check that all the customized_formatting_references
# are in default_formatting_references
foreach my $customized_formatting_reference
(sort(keys(%{$customized_formatting_references}))) {
if (!$default_formatting_references{$customized_formatting_reference}) {
$self->document_warn($self, sprintf(__("Unknown formatting function: %s"),
$customized_formatting_reference));
}
}
$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_element_body
= Texinfo::Config::GNUT_get_formatting_special_element_body_references();
$self->{'special_element_body'} = {};
foreach my $special_element_variety (keys(%defaults_format_special_body_contents)) {
$self->{'special_element_body'}->{$special_element_variety}
= $defaults_format_special_body_contents{$special_element_variety};
}
foreach my $special_element_variety (keys(%$customized_special_element_body)) {
$self->{'special_element_body'}->{$special_element_variety}
= $customized_special_element_body->{$special_element_variety};
}
$self->{'document_context'} = [];
$self->{'multiple_pass'} = [];
$self->{'pending_closes'} = [];
$self->_new_document_context('_toplevel_context');
if ($self->get_conf('SPLIT') and $self->get_conf('SPLIT') ne 'chapter'
and $self->get_conf('SPLIT') ne 'section'
and $self->get_conf('SPLIT') ne 'node') {
$self->force_conf('SPLIT', 'node');
}
return $self;
}
# the entry point for _convert
sub convert_tree($$;$)
{
my $self = shift;
my $tree = shift;
my $explanation = shift;
# 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 $self->_convert($tree, $explanation);
}
# FIXME document as part of the API. Make it a mandatory called function?
# a format_* function?
# 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 = shift;
my $input_string = shift;
# 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);
}
# FIXME document as part of the API. Make it a mandatory called function?
# a format_* function?
# 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 = shift;
my $input_string = shift;
# 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 = shift;
my $filename = shift;
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(
') {
$line_nr++;
if ($line_nr == 1) {
# the rule is to assume utf-8. There could also be a BOM, and
# the Content-Type: HTTP header but it is not relevant here.
# https://developer.mozilla.org/en-US/docs/Web/CSS/@charset
my $charset = 'utf-8';
my $charset_line;
# should always be the first line
if ($line =~ /^\@charset *"([^"]+)" *; *$/) {
$charset = $1;
$charset_line = 1;
}
my $Encode_encoding_object = find_encoding($charset);
if (defined($Encode_encoding_object)) {
my $input_perl_encoding = $Encode_encoding_object->name();
if ($input_perl_encoding eq 'utf-8') {
binmode($fh, ":utf8");
} else {
binmode($fh, ":encoding($input_perl_encoding)");
}
}
next if ($charset_line);
}
#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->line_warn($self, __("string not closed in css file"),
{'file_name' => $file, 'line_nr' => $line_nr}) if ($in_string);
$self->line_warn($self, __("--css-include ended in comment"),
{'file_name' => $file, 'line_nr' => $line_nr}) if ($in_comment);
$self->line_warn($self, __("\@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;
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 $file (@$css_files) {
my $css_file_fh;
my $css_file;
if ($file eq '-') {
$css_file_fh = \*STDIN;
$css_file = '-';
} else {
$css_file = $self->Texinfo::Common::locate_include_file($file);
unless (defined($css_file)) {
my $input_file_name = $file;
my $encoding = $self->get_conf('COMMAND_LINE_ENCODING');
if (defined($encoding)) {
$input_file_name = decode($encoding, $input_file_name);
}
$self->document_warn($self, sprintf(
__("CSS file %s not found"), $input_file_name));
next;
}
unless (open (CSSFILE, $css_file)) {
my $css_file_name = $css_file;
my $encoding = $self->get_conf('COMMAND_LINE_ENCODING');
if (defined($encoding)) {
$css_file_name = decode($encoding, $css_file_name);
}
$self->document_warn($self, 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)
= $self->_process_css_file($css_file_fh, $css_file);
if (!close($css_file_fh)) {
my $css_file_name = $css_file;
my $encoding = $self->get_conf('COMMAND_LINE_ENCODING');
if (defined($encoding)) {
$css_file_name = decode($encoding, $css_file_name);
}
$self->document_warn($self,
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";
}
}
}
$self->{'css_import_lines'} = \@css_import_lines;
$self->{'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. Argument is the 'extra' value on
# an element hash, or something that looks like it. Labels are typically
# associated to @node, @anchor or @float.
sub _normalized_label_id_file($$)
{
my $self = shift;
my $label_info = shift;
my $target;
my $normalized;
if ($label_info->{'normalized'}) {
$normalized = $label_info->{'normalized'};
} elsif ($label_info->{'node_content'}) {
$normalized = Texinfo::Convert::NodeNameNormalization::normalize_node(
{ 'contents' => $label_info->{'node_content'} });
}
if (defined($normalized)) {
$target = _normalized_to_id($normalized);
} else {
$target = '';
}
# to find out the Top node, one could check $label_info->{'normalized'}
if (defined($self->{'file_id_setting'}->{'label_target_name'})) {
$target = &{$self->{'file_id_setting'}->{'label_target_name'}}($self,
$label_info, $target);
}
my $filename = $self->node_information_filename($label_info);
return ($filename, $target);
}
sub _new_sectioning_command_target($$)
{
my $self = shift;
my $command = shift;
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'
and defined($self->{'special_elements_targets'}->{'Top'})) {
$target_base = $self->{'special_elements_targets'}->{'Top'};
}
my $nr=1;
my $target = $target_base;
if ($target ne '') {
while ($self->{'seen_ids'}->{$target}) {
$target = $target_base.'-'.$nr;
$nr++;
# Avoid integer overflow
die if ($nr == 0);
}
}
# These are undefined if the $target is set to ''.
my $target_contents;
my $target_shortcontents;
if ($sectioning_heading_commands{$command->{'cmdname'}}) {
if ($target ne '') {
my $target_base_contents = $target;
$target_base_contents =~ s/^g_t//;
$target_contents = 'toc-'.$target_base_contents;
my $toc_nr = $nr -1;
while ($self->{'seen_ids'}->{$target_contents}) {
$target_contents = 'toc-'.$target_base_contents.'-'.$toc_nr;
$toc_nr++;
# Avoid integer overflow
die if ($toc_nr == 0);
}
$target_shortcontents = 'stoc-'.$target_base_contents;
my $target_base_shortcontents = $target_base;
$target_base_shortcontents =~ s/^g_t//;
my $stoc_nr = $nr -1;
while ($self->{'seen_ids'}->{$target_shortcontents}) {
$target_shortcontents = 'stoc-'.$target_base_shortcontents
.'-'.$stoc_nr;
$stoc_nr++;
# Avoid integer overflow
die if ($stoc_nr == 0);
}
}
}
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,
};
$self->{'seen_ids'}->{$target} = 1;
if (defined($target_contents)) {
$self->{'targets'}->{$command}->{'contents_target'} = $target_contents;
} else {
$self->{'targets'}->{$command}->{'contents_target'} = '';
}
if (defined($target_shortcontents)) {
$self->{'targets'}->{$command}->{'shortcontents_target'}
= $target_shortcontents;
} else {
$self->{'targets'}->{$command}->{'shortcontents_target'} = '';
}
return $self->{'targets'}->{$command};
}
# This set with two different codes
# * the target information, id and normalized filename of 'labels',
# ie everything that may be the target of a ref, like @node, @float, @anchor...
# * The target information of sectioning elements by going through tree units
# @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 $tree_units = shift;
my $no_unidecode;
$no_unidecode = 1 if (defined($self->get_conf('USE_UNIDECODE'))
and !$self->get_conf('USE_UNIDECODE'));
my $extension = '';
$extension = '.'.$self->get_conf('EXTENSION')
if (defined($self->get_conf('EXTENSION'))
and $self->get_conf('EXTENSION') ne '');
if ($self->{'labels'}) {
foreach my $label_element (values(%{$self->{'labels'}})) {
my ($node_filename, $target)
= $self->_normalized_label_id_file($label_element->{'extra'});
$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, $label_element, $node_filename);
if (defined($user_node_filename)) {
$node_filename = $user_node_filename;
} elsif ($self->get_conf('VERBOSE')) {
$self->document_warn($self, 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($label_element) \@$label_element->{'cmdname'} $target, $node_filename\n";
}
$self->{'targets'}->{$label_element} = {'target' => $target,
'node_filename' => $node_filename};
$self->{'seen_ids'}->{$target} = 1;
}
}
if ($tree_units) {
foreach my $tree_unit (@$tree_units) {
foreach my $root_element(@{$tree_unit->{'contents'}}) {
# this happens for types which would precede the root commands.
# The target may already be set for the top node tree unit.
next if (!defined($root_element->{'cmdname'})
or $self->{'targets'}->{$root_element});
if ($sectioning_heading_commands{$root_element->{'cmdname'}}) {
$self->_new_sectioning_command_target($root_element);
}
}
}
}
}
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 = shift;
my $command = shift;
my $find_container = shift;
# can be used to debug/understand what is going on
#my $debug = 1;
my $current = $command;
#print STDERR "START ".Texinfo::Common::debug_print_element_short($current)."\n" if ($debug);
my ($root_element, $root_command);
while (1) {
if ($current->{'type'}) {
if ($current->{'type'} eq 'unit' or $current->{'type'} eq 'special_element') {
#print STDERR "ROOT ELEMENT $current->{'type'}\n" if ($debug);
return ($current, $root_command);
}
}
if ($current->{'cmdname'}) {
if ($root_commands{$current->{'cmdname'}}) {
$root_command = $current;
#print STDERR "CMD ROOT $current->{'cmdname'}\n" if ($debug);
return ($root_element, $root_command) if defined($root_element);
} elsif ($block_commands{$current->{'cmdname'}}
and $block_commands{$current->{'cmdname'}} eq 'region') {
if ($current->{'cmdname'} eq 'copying'
and $self->{'global_commands'}
and $self->{'global_commands'}->{'insertcopying'}) {
foreach my $insertcopying(@{$self->{'global_commands'}->{'insertcopying'}}) {
#print STDERR "INSERTCOPYING\n" if ($debug);
my ($root_element, $root_command)
= $self->_html_get_tree_root_element($insertcopying, $find_container);
return ($root_element, $root_command)
if (defined($root_element) or defined($root_command));
}
} elsif ($current->{'cmdname'} eq 'titlepage'
and $self->get_conf('USE_TITLEPAGE_FOR_TITLE')
and $self->get_conf('SHOW_TITLE')
and $self->{'tree_units'}->[0]) {
#print STDERR "FOR titlepage tree_units [0]\n" if ($debug);
return ($self->{'tree_units'}->[0],
$self->{'tree_units'}->[0]->{'extra'}->{'unit_command'});
}
die "Problem $root_element, $root_command" if (defined($root_element)
or defined($root_command));
return (undef, undef);
} elsif ($find_container) {
# @footnote and possibly @*contents when a separate element is set
my ($special_element_variety, $special_element, $class_base,
$special_element_direction)
= $self->command_name_special_element_information($current->{'cmdname'});
if ($special_element) {
#print STDERR "SPECIAL $current->{'cmdname'}: $special_element_variety ($special_element_direction)\n" if ($debug);
return ($special_element);
}
}
}
if ($current->{'structure'}
and $current->{'structure'}->{'associated_unit'}) {
#print STDERR "ASSOCIATED_UNIT ".Texinfo::Common::debug_print_element_short($current->{'structure'}->{'associated_unit'})."\n" if ($debug);
$current = $current->{'structure'}->{'associated_unit'};
} elsif ($current->{'parent'}) {
#print STDERR "PARENT ".Texinfo::Common::debug_print_element_short($current->{'parent'})."\n" if ($debug);
$current = $current->{'parent'};
} else {
#print STDERR "UNKNOWN ROOT ".Texinfo::Common::debug_print_element_short($current)."\n" if ($debug);
return (undef, $root_command);
}
}
}
sub _html_set_pages_files($$$$$$$$)
{
my $self = shift;
my $tree_units = shift;
my $special_elements = shift;
my $output_file = shift;
my $destination_directory = shift;
my $output_filename = shift;
my $document_name = shift;
# Ensure that the document has pages
return undef if (!defined($tree_units) or !@$tree_units);
$self->initialize_tree_units_files();
my $extension = '';
$extension = '.'.$self->get_conf('EXTENSION')
if (defined($self->get_conf('EXTENSION'))
and $self->get_conf('EXTENSION') ne '');
my %filenames_paths;
my %unit_file_name_paths;
if (!$self->get_conf('SPLIT')) {
$filenames_paths{$output_filename} = $output_file;
foreach my $tree_unit (@$tree_units) {
$unit_file_name_paths{$tree_unit} = $output_filename;
}
} else {
my $node_top;
$node_top = $self->{'labels'}->{'Top'} if ($self->{'labels'});
my $top_node_filename = $self->top_node_filename($document_name);
my $node_top_tree_unit;
# first determine the top node file name.
if ($node_top and defined($top_node_filename)) {
my ($node_top_tree_unit) = $self->_html_get_tree_root_element($node_top);
die "BUG: No element for top node" if (!defined($node_top_tree_unit));
$filenames_paths{$top_node_filename} = undef;
$unit_file_name_paths{$node_top_tree_unit} = $top_node_filename;
}
my $file_nr = 0;
my $previous_page;
foreach my $tree_unit (@$tree_units) {
# For Top node.
next if (exists($unit_file_name_paths{$tree_unit}));
my $file_tree_unit = $tree_unit->{'extra'}->{'first_in_page'};
if (!$file_tree_unit) {
cluck ("No first_in_page for $tree_unit\n");
}
if (not exists($unit_file_name_paths{$file_tree_unit})) {
foreach my $root_command (@{$file_tree_unit->{'contents'}}) {
if ($root_command->{'cmdname'}
and $root_command->{'cmdname'} eq 'node') {
my $node_filename;
# double node are not normalized, they are handled here
if (!defined($root_command->{'extra'}->{'normalized'})
or !defined($self->{'labels'}->{$root_command->{'extra'}->{'normalized'}})) {
$node_filename = 'unknown_node';
$node_filename .= $extension;
} else {
# Nodes with {'extra'}->{'normalized'} should always be in
# 'labels', and thus in targets. It is a bug otherwise.
$node_filename
= $self->{'targets'}->{$root_command}->{'node_filename'};
}
$filenames_paths{$node_filename} = undef;
$unit_file_name_paths{$file_tree_unit} = $node_filename;
last;
}
}
if (not defined($unit_file_name_paths{$file_tree_unit})) {
# use section to do the file name if there is no node
my $command = $self->tree_unit_element_command($file_tree_unit);
if ($command) {
if ($command->{'cmdname'} eq 'top' and !$node_top
and defined($top_node_filename)) {
$filenames_paths{$top_node_filename} = undef;
$unit_file_name_paths{$file_tree_unit} = $top_node_filename;
} else {
my $section_filename
= $self->{'targets'}->{$command}->{'section_filename'};
$filenames_paths{$section_filename} = undef;
$unit_file_name_paths{$file_tree_unit} = $section_filename;
}
} else {
# when everything else has failed
if ($file_nr == 0 and !$node_top
and defined($top_node_filename)) {
$filenames_paths{$top_node_filename} = undef;
$unit_file_name_paths{$file_tree_unit} = $top_node_filename;
} else {
my $filename = $document_name . "_$file_nr";
$filename .= $extension;
$filenames_paths{$filename} = undef;
$unit_file_name_paths{$file_tree_unit} = $filename;
}
$file_nr++;
}
}
}
if (not exists($unit_file_name_paths{$tree_unit})) {
$unit_file_name_paths{$tree_unit}
= $unit_file_name_paths{$file_tree_unit}
}
}
}
foreach my $tree_unit (@$tree_units) {
my $filename = $unit_file_name_paths{$tree_unit};
my $filepath = $filenames_paths{$filename};
if (defined($self->{'file_id_setting'}->{'tree_unit_file_name'})) {
# NOTE the information that it is associated with @top or @node Top
# may be determined with $self->element_is_tree_unit_top($tree_unit);
my ($user_filename, $user_filepath)
= &{$self->{'file_id_setting'}->{'tree_unit_file_name'}}(
$self, $tree_unit, $filename, $filepath);
if (defined($user_filename)) {
$filename = $user_filename;
$filenames_paths{$filename} = $user_filepath;
}
}
$self->set_tree_unit_file($tree_unit, $filename);
my $tree_unit_filename = $tree_unit->{'structure'}->{'unit_filename'};
$self->{'file_counters'}->{$tree_unit_filename} = 0
if (!exists($self->{'file_counters'}->{$tree_unit_filename}));
$self->{'file_counters'}->{$tree_unit_filename}++;
print STDERR "Page $tree_unit "
.Texinfo::Structuring::root_or_external_element_cmd_texi($tree_unit)
.": $tree_unit_filename($self->{'file_counters'}->{$tree_unit_filename})\n"
if ($self->get_conf('DEBUG'));
}
if ($special_elements) {
my $previous_tree_unit = $tree_units->[-1];
foreach my $special_element (@$special_elements) {
my $filename
= $self->{'targets'}->{$special_element}->{'special_element_filename'};
if (defined($filename)) {
$filenames_paths{$filename} = undef;
$self->set_tree_unit_file($special_element, $filename);
$self->{'file_counters'}->{$filename} = 0
if (!exists($self->{'file_counters'}->{$filename}));
$self->{'file_counters'}->{$filename}++;
print STDERR "Special page $special_element: "
."$filename($self->{'file_counters'}->{$filename})\n"
if ($self->get_conf('DEBUG'));
}
$special_element->{'structure'}->{'unit_prev'} = $previous_tree_unit;
$previous_tree_unit->{'structure'}->{'unit_next'} = $special_element;
$previous_tree_unit = $special_element;
}
}
foreach my $filename (keys(%filenames_paths)) {
$self->set_file_path($filename, $destination_directory,
$filenames_paths{$filename});
}
}
# $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 do some conversion setup that is to be done in both convert() and output().
sub _prepare_conversion_tree_units($$$$)
{
my $self = shift;
my $root = shift;
my $destination_directory = shift;
my $document_name = shift;
my $tree_units;
if ($self->get_conf('USE_NODES')) {
$tree_units = Texinfo::Structuring::split_by_node($root);
} else {
$tree_units = Texinfo::Structuring::split_by_section($root);
}
$self->{'tree_units'} = $tree_units
if (defined($tree_units));
# This may be done as soon as tree units are available.
$self->_prepare_tree_units_global_targets($tree_units);
# the presence of contents elements in the document is used in diverse
# places, set it once for all here
my @contents_elements_options
= grep {Texinfo::Common::valid_customization_option($_)}
keys(%contents_command_special_element_variety);
$self->set_global_document_commands('last', \@contents_elements_options);
# configuration used to determine if a special element is to be done
# (in addition to contents)
my @conf_for_special_elements = ('footnotestyle');
$self->set_global_document_commands('last', \@conf_for_special_elements);
# Do that before the other elements, to be sure that special page ids
# are registered before elements id are.
# NOTE if the last value of footnotestyle is separate, all the footnotes
# formatted text are set to the special element set in _prepare_special_elements
# 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.
my $special_elements
= $self->_prepare_special_elements($tree_units, $destination_directory,
$document_name);
# reset to the default
$self->set_global_document_commands('before', \@conf_for_special_elements);
#if ($tree_units) {
# foreach my $element(@{$tree_units}) {
# print STDERR "ELEMENT $element->{'type'}: $element\n";
# }
#}
$self->_set_root_commands_targets_node_files($tree_units);
# setup untranslated strings
$self->_translate_names();
return ($tree_units, $special_elements);
}
sub _prepare_special_elements($$$$)
{
my $self = shift;
my $tree_units = shift;
my $destination_directory = shift;
my $document_name = shift;
my %do_special;
if ($self->{'structuring'} and $self->{'structuring'}->{'sectioning_root'}
and scalar(@{$self->{'structuring'}->{'sections_list'}}) > 1) {
foreach my $cmdname ('contents', 'shortcontents') {
my $special_element_variety
= $contents_command_special_element_variety{$cmdname};
if ($self->get_conf($cmdname)) {
if ($self->get_conf('CONTENTS_OUTPUT_LOCATION')
eq 'separate_element') {
$do_special{$special_element_variety} = 1;
}
}
}
}
if ($self->{'global_commands'}->{'footnote'}
and $self->get_conf('footnotestyle') eq 'separate'
and $tree_units and scalar(@$tree_units) > 1) {
$do_special{'footnotes'} = 1;
}
if ((!defined($self->get_conf('DO_ABOUT'))
and $tree_units and scalar(@$tree_units) > 1
and ($self->get_conf('SPLIT') or $self->get_conf('HEADERS')))
or ($self->get_conf('DO_ABOUT'))) {
$do_special{'about'} = 1;
}
my $extension = '';
$extension = $self->get_conf('EXTENSION')
if (defined($self->get_conf('EXTENSION')));
my $special_elements = [];
foreach my $special_element_variety (@{$self->{'special_elements_order'}}) {
next unless ($do_special{$special_element_variety});
my $element = {'type' => 'special_element',
'extra' => {'special_element_variety'
=> $special_element_variety,},
'structure' => {'directions' => {}}};
$element->{'structure'}->{'directions'}->{'This'} = $element;
my $special_element_direction
= $self->get_conf('SPECIAL_ELEMENTS_DIRECTIONS')->{$special_element_variety};
$self->{'special_elements_directions'}->{$special_element_direction}
= $element;
push @$special_elements, $element;
my $target = $self->{'special_elements_targets'}->{$special_element_variety};
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)) {
$default_filename = $document_name.
$self->{'special_elements_file_string'}->{$special_element_variety};
$default_filename .= '.'.$extension if (defined($extension));
} else {
$default_filename = undef;
}
my $filename;
if (defined($self->{'file_id_setting'}->{'special_element_target_file_name'})) {
($target, $filename)
= &{$self->{'file_id_setting'}->{'special_element_target_file_name'}}(
$self,
$element,
$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 $element $special_element_variety: target $target,\n".
" filename $fileout\n";
}
$self->{'targets'}->{$element} = {'target' => $target,
'special_element_filename' => $filename,
};
$self->{'seen_ids'}->{$target} = 1;
}
if ($self->get_conf('FRAMES')) {
$self->{'frame_pages_filenames'} = {};
foreach my $special_element_variety (keys(%{$self->{'frame_pages_file_string'}})) {
my $default_filename;
$default_filename = $document_name.
$self->{'frame_pages_file_string'}->{$special_element_variety};
$default_filename .= '.'.$extension if (defined($extension));
my $element = {'type' => 'special_element',
'extra' => {'special_element_variety'
=> $special_element_variety, }};
# only the filename is used
my ($target, $filename);
if (defined($self->{'file_id_setting'}->{'special_element_target_file_name'})) {
($target, $filename)
= &{$self->{'file_id_setting'}->{'special_element_target_file_name'}}(
$self,
$element,
$target,
$default_filename);
}
$filename = $default_filename if (!defined($filename));
$self->{'frame_pages_filenames'}->{$special_element_variety} = $filename;
}
}
return $special_elements;
}
sub _prepare_contents_elements($)
{
my $self = shift;
if ($self->{'structuring'} and $self->{'structuring'}->{'sectioning_root'}
and scalar(@{$self->{'structuring'}->{'sections_list'}}) > 1) {
foreach my $cmdname ('contents', 'shortcontents') {
my $special_element_variety
= $contents_command_special_element_variety{$cmdname};
if ($self->get_conf($cmdname)) {
my $default_filename;
if ($self->get_conf('CONTENTS_OUTPUT_LOCATION') eq 'after_title') {
if ($self->{'tree_units'} and $self->{'tree_units'}->[0]->{'structure'}
and exists($self->{'tree_units'}->[0]->{'structure'}->{'unit_filename'})) {
$default_filename
= $self->{'tree_units'}->[0]->{'structure'}->{'unit_filename'};
}
} elsif ($self->get_conf('CONTENTS_OUTPUT_LOCATION') eq 'after_top') {
my $section_top = undef;
if ($self->{'global_commands'} and $self->{'global_commands'}->{'top'}) {
$section_top = $self->{'global_commands'}->{'top'};
$default_filename = $self->command_filename($section_top);
}
} elsif ($self->get_conf('CONTENTS_OUTPUT_LOCATION') eq 'inline') {
if ($self->{'global_commands'}
and $self->{'global_commands'}->{$cmdname}) {
foreach my $command(@{$self->{'global_commands'}->{$cmdname}}) {
my ($root_element, $root_command)
= $self->_html_get_tree_root_element($command);
if (defined($root_element) and $root_element->{'structure'}
and exists($root_element->{'structure'}->{'unit_filename'})) {
$default_filename
= $root_element->{'structure'}->{'unit_filename'};
last;
}
}
} else {
next;
}
} else { # in this case, there should already be a special element
# if needed, done together with the other special elements.
next;
}
my $contents_element = {'type' => 'special_element',
'extra' => {'special_element_variety'
=> $special_element_variety}};
my $special_element_direction
= $self->get_conf('SPECIAL_ELEMENTS_DIRECTIONS')->{$special_element_variety};
$self->{'special_elements_directions'}->{$special_element_direction}
= $contents_element;
my $target = $self->{'special_elements_targets'}->{$special_element_variety};
my $filename;
if (defined($self->{'file_id_setting'}->{'special_element_target_file_name'})) {
($target, $filename)
= &{$self->{'file_id_setting'}->{'special_element_target_file_name'}}(
$self,
$contents_element,
$target,
$default_filename);
}
$filename = $default_filename if (!defined($filename));
if ($self->get_conf('DEBUG')) {
my $str_filename = $filename;
$str_filename = 'UNDEF' if (not defined($str_filename));
print STDERR "Add content $contents_element $special_element_variety: target $target,\n".
" filename $str_filename\n";
}
$self->{'targets'}->{$contents_element} = {'target' => $target,
'special_element_filename' => $filename,
'filename' => $filename,
};
}
}
}
}
# Associate tree units with the global targets, First, Last, Top, Index.
sub _prepare_tree_units_global_targets($$)
{
my $self = shift;
my $tree_units = shift;
$self->{'global_target_elements_directions'} = {};
$self->{'global_target_elements_directions'}->{'First'} = $tree_units->[0];
$self->{'global_target_elements_directions'}->{'Last'} = $tree_units->[-1];
# It is always the first printindex, even if it is not output (for example
# it is in @copying and @titlepage, which are certainly wrong constructs).
if ($self->{'global_commands'} and $self->{'global_commands'}->{'printindex'}) {
# Here root_element can only be a tree unit, or maybe undef if there
# are no tree unit at all
my ($root_element, $root_command)
= $self->_html_get_tree_root_element($self->{'global_commands'}->{'printindex'}->[0]);
if (defined($root_element)) {
if ($root_command and $root_command->{'cmdname'} eq 'node'
and $root_command->{'extra'}->{'associated_section'}) {
$root_command = $root_command->{'extra'}->{'associated_section'};
}
# find the first level 1 sectioning element to associate the printindex with
if ($root_command and $root_command->{'cmdname'} ne 'node') {
while ($root_command->{'structure'}->{'section_level'} > 1
and $root_command->{'structure'}->{'section_up'}
and $root_command->{'structure'}->{'section_up'}
->{'structure'}->{'associated_unit'}) {
$root_command = $root_command->{'structure'}->{'section_up'};
$root_element = $root_command->{'structure'}->{'associated_unit'};
}
}
$self->{'global_target_elements_directions'}->{'Index'} = $root_element;
}
}
my $node_top;
$node_top = $self->{'labels'}->{'Top'} if ($self->{'labels'});
my $section_top;
$section_top = $self->{'global_commands'}->{'top'} if ($self->{'global_commands'});
if ($section_top) {
$self->{'global_target_elements_directions'}->{'Top'} = $section_top->{'structure'}->{'associated_unit'};
} elsif ($node_top) {
my $tree_unit_top = $node_top->{'structure'}->{'associated_unit'};
if (!$tree_unit_top) {
die "No parent for node_top: ".Texinfo::Common::debug_print_element($node_top);
}
$self->{'global_target_elements_directions'}->{'Top'} = $tree_unit_top;
} else {
$self->{'global_target_elements_directions'}->{'Top'} = $tree_units->[0];
}
if ($self->get_conf('DEBUG')) {
print STDERR "GLOBAL DIRECTIONS:\n";
foreach my $global_direction (@global_directions) {
if (defined($self->global_direction_element($global_direction))) {
my $global_element = $self->global_direction_element($global_direction);
print STDERR "$global_direction($global_element): ".
Texinfo::Structuring::root_or_external_element_cmd_texi($global_element)."\n";
}
}
}
}
sub _prepare_index_entries($)
{
my $self = shift;
my $index_names = $self->{'indices_information'};
if ($index_names) {
my $no_unidecode;
$no_unidecode = 1 if (defined($self->get_conf('USE_UNIDECODE'))
and !$self->get_conf('USE_UNIDECODE'));
my $merged_index_entries
= Texinfo::Structuring::merge_indices($index_names);
my $index_entries_sort_strings;
($self->{'index_entries_by_letter'}, $index_entries_sort_strings)
= Texinfo::Structuring::sort_indices($self,
$self, $merged_index_entries, 'by_letter');
$self->{'index_entries'} = $merged_index_entries;
foreach my $index_name (sort(keys(%$index_names))) {
foreach my $index_entry (@{$index_names->{$index_name}->{'index_entries'}}) {
# does not refer to the document
next if ($index_entry->{'entry_element'}->{'extra'}
and ($index_entry->{'entry_element'}->{'extra'}->{'seeentry'}
or $index_entry->{'entry_element'}->{'extra'}->{'seealso'}));
my $region = '';
$region = "$index_entry->{'entry_region'}->{'cmdname'}-"
if (defined($index_entry->{'entry_region'}));
my @contents = @{$index_entry->{'content_normalized'}};
my $trimmed_contents
= Texinfo::Common::trim_spaces_comment_from_content(\@contents);
my $normalized_index =
Texinfo::Convert::NodeNameNormalization::transliterate_texinfo(
{'contents' => \@contents}, $no_unidecode);
my $target_base = "index-" . $region .$normalized_index;
my $nr=1;
my $target = $target_base;
while ($self->{'seen_ids'}->{$target}) {
$target = $target_base.'-'.$nr;
$nr++;
# Avoid integer overflow
die if ($nr == 0);
}
$self->{'seen_ids'}->{$target} = 1;
$self->{'targets'}->{$index_entry->{'entry_element'}}
= {'target' => $target, };
}
}
}
}
sub _prepare_footnotes($)
{
my $self = shift;
my $footid_base = 'FOOT';
my $docid_base = 'DOCF';
$self->{'pending_footnotes'} = [];
if ($self->{'global_commands'}->{'footnote'}) {
my $footnote_nr = 0;
foreach my $footnote (@{$self->{'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 ($self->{'seen_ids'}->{$docid} or $self->{'seen_ids'}->{$footid}) {
$nr++;
$footid = $footid_base.$nr;
$docid = $docid_base.$nr;
# Avoid integer overflow
die if ($nr == 0);
}
$self->{'seen_ids'}->{$footid} = 1;
$self->{'seen_ids'}->{$docid} = 1;
$self->{'targets'}->{$footnote} = { 'target' => $footid };
$self->{'special_targets'}->{'footnote_location'}->{$footnote}
= { 'target' => $docid };
print STDERR "Enter footnote $footnote: target $footid, nr $footnote_nr\n"
.Texinfo::Convert::Texinfo::convert_to_texinfo($footnote)."\n"
if ($self->get_conf('DEBUG'));
}
}
}
# TODO this encapsulates some information.
# The encapsulation and API should be more consistent for
# the overall module.
sub _htmlxref($$)
{
my $self = shift;
my $file = shift;
return $self->{'htmlxref'}->{$file};
}
sub _external_node_href($$$;$)
{
my $self = shift;
my $external_node = shift;
my $filename = shift;
# for messages only
my $source_command = shift;
#print STDERR "external_node: ".join('|', keys(%$external_node))."\n";
my ($target_filebase, $target)
= $self->_normalized_label_id_file($external_node);
my $xml_target = _normalized_to_id($target);
my $default_target_split = $self->get_conf('EXTERNAL_CROSSREF_SPLIT');
my $external_file_extension = '';
my $external_extension = $self->get_conf('EXTERNAL_CROSSREF_EXTENSION');
$external_extension = $self->get_conf('EXTENSION')
if (not defined($external_extension));
$external_file_extension = '.' . $external_extension
if (defined($external_extension) and $external_extension ne '');
my $target_split;
my $file;
if ($external_node->{'manual_content'}) {
my $manual_name = Texinfo::Convert::Text::convert_to_text(
{'contents' => $external_node->{'manual_content'}},
{ 'code' => 1,
Texinfo::Convert::Text::copy_options_for_convert_text($self, 1)});
if ($self->get_conf('IGNORE_REF_TO_TOP_NODE_UP') and $xml_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;
$manual_base =~ s/\.info*$//;
$manual_base =~ s/^.*\///;
my $document_split = $self->get_conf('SPLIT');
$document_split = 'mono' if (!$document_split);
my $split_found;
my $href;
my $htmlxref_info = $self->_htmlxref($manual_base);
if ($htmlxref_info) {
foreach my $split_ordered (@{$htmlxref_entries{$document_split}}) {
if (defined($htmlxref_info->{$split_ordered})) {
$split_found = $split_ordered;
$href = $self->url_protect_url_text($htmlxref_info->{$split_ordered});
last;
}
}
}
if (defined($split_found)) {
$target_split = 1 unless ($split_found eq 'mono');
} else { # nothing specified for that manual, use default
$target_split = $default_target_split;
if ($self->get_conf('CHECK_HTMLXREF')) {
if (defined($source_command) and $source_command->{'source_info'}) {
my $node_manual_key = $source_command.'-'.$manual_name;
if (!$self->{'check_htmlxref_already_warned'}->{$node_manual_key}) {
$self->line_warn($self, sprintf(__(
"no htmlxref.cnf entry found for `%s'"), $manual_name),
$source_command->{'source_info'});
$self->{'check_htmlxref_already_warned'}->{$node_manual_key} = 1;
}
} else {
if (!$self->{'check_htmlxref_already_warned'}->{'UNDEF-'.$manual_name}) {
$self->document_warn($self, sprintf(__(
"no htmlxref.cnf entry found for `%s'"), $manual_name),
);
$self->{'check_htmlxref_already_warned'}->{'UNDEF-'.$manual_name} = 1;
cluck;
}
}
}
}
if ($target_split) {
if (defined($href)) {
$file = $href;
} else {
my $manual_dir = $manual_base;
if (defined($self->{'output_format'}) and $self->{'output_format'} ne '') {
$manual_dir .= '_'.$self->{'output_format'};
}
if (defined($self->get_conf('EXTERNAL_DIR'))) {
$file = $self->get_conf('EXTERNAL_DIR')."/$manual_dir";
} elsif ($self->get_conf('SPLIT')) {
$file = "../$manual_dir";
}
$file = $self->url_protect_file_text($file);
}
$file .= "/";
} else {# target not split
if (defined($href)) {
$file = $href;
} else {
my $manual_file_name = $manual_base . $external_file_extension;
if (defined($self->get_conf('EXTERNAL_DIR'))) {
$file = $self->get_conf('EXTERNAL_DIR')."/$manual_file_name";
} elsif ($self->get_conf('SPLIT')) {
$file = "../$manual_file_name";
} else {
$file = $manual_file_name;
}
$file = $self->url_protect_file_text($file);
}
}
} else {
$file = '';
$target_split = $default_target_split;
}
if ($target eq '') {
if ($target_split) {
if (defined($self->get_conf('TOP_NODE_FILE_TARGET'))) {
return $file . $self->get_conf('TOP_NODE_FILE_TARGET');
} else {
return $file;
}
} else {
return $file . '#Top';
}
}
if (! $target_split) {
return $file . '#' . $xml_target;
} else {
my $file_name;
if ($target eq 'Top' 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;
}
return $file . $file_name . '#' . $xml_target;
}
}
# Output a list of the nodes immediately below this one
sub _mini_toc
{
my ($self, $command) = @_;
my $result = '';
my $entry_index = 0;
if ($command->{'structure'}
and $command->{'structure'}->{'section_childs'}
and @{$command->{'structure'}->{'section_childs'}}) {
$result .= $self->html_attribute_class('ul', ['mini-toc']).">\n";
foreach my $section (@{$command->{'structure'}->{'section_childs'}}) {
my $tree = $self->command_text($section, 'tree_nonumber');
my $text = $self->convert_tree($tree, "mini_toc \@$section->{'cmdname'}");
$entry_index++;
my $accesskey = '';
$accesskey = " accesskey=\"$entry_index\""
if ($self->get_conf('USE_ACCESSKEY') and $entry_index < 10);
my $href = $self->command_href($section);
if ($text ne '') {
if ($href ne '') {
my $href_attribute = '';
if ($href ne '') {
$href_attribute = " href=\"$href\"";
}
$result .= "
\n";
}
}
$result .= "\n";
}
return $result;
}
sub _default_format_contents($$;$$)
{
my $self = shift;
my $cmdname = shift;
my $command = shift;
my $filename = shift;
$filename = $self->get_info('current_filename') if (!defined($filename));
my $structuring = $self->get_info('structuring');
return ''
if (!$structuring or !$structuring->{'sectioning_root'});
my $section_root = $structuring->{'sectioning_root'};
my $contents;
$contents = 1 if ($cmdname eq 'contents');
my $min_root_level = $section_root->{'structure'}->{'section_childs'}->[0]
->{'structure'}->{'section_level'};
my $max_root_level = $section_root->{'structure'}->{'section_childs'}->[0]
->{'structure'}->{'section_level'};
foreach my $top_section (@{$section_root->{'structure'}->{'section_childs'}}) {
$min_root_level = $top_section->{'structure'}->{'section_level'}
if ($top_section->{'structure'}->{'section_level'} < $min_root_level);
$max_root_level = $top_section->{'structure'}->{'section_level'}
if ($top_section->{'structure'}->{'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 ($contents and !defined($self->get_conf('BEFORE_TOC_LINES'))
or (!$contents and !defined($self->get_conf('BEFORE_SHORT_TOC_LINES')))) {
$result .= $self->html_attribute_class('div', [$cmdname]).">\n";
} elsif($contents) {
$result .= $self->get_conf('BEFORE_TOC_LINES');
} else {
$result .= $self->get_conf('BEFORE_SHORT_TOC_LINES');
}
my $toplevel_contents;
if (@{$section_root->{'structure'}->{'section_childs'}} > 1) {
$result .= $self->html_attribute_class('ul', \@toc_ul_classes) .">\n";
$toplevel_contents = 1;
}
my $link_to_toc = (!$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 $self->_has_contents_or_shortcontents()));
foreach my $top_section (@{$section_root->{'structure'}->{'section_childs'}}) {
my $section = $top_section;
SECTION:
while ($section) {
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->{'structure'}->{'section_level'} - $min_root_level)))
if ($contents);
if ($toc_id ne '' or $href ne '') {
my $toc_name_attribute = '';
if ($toc_id ne '') {
$toc_name_attribute = " id=\"$toc_id\"";
}
my $href_attribute = '';
if ($href ne '') {
$href_attribute = " href=\"$href\"";
}
my $rel = '';
if ($section->{'extra'}
and $section->{'extra'}->{'associated_node'}
and $section->{'extra'}->{'associated_node'}->{'extra'}->{'isindex'}) {
$rel = ' rel="index"';
}
$result .= "
$text";
}
}
} elsif ($section->{'structure'}->{'section_childs'}
and @{$section->{'structure'}->{'section_childs'}}
and $toplevel_contents) {
$result .= "
";
}
# for shortcontents don't do child if child is not toplevel
if ($section->{'structure'}->{'section_childs'}
and ($contents or $section->{'structure'}->{'section_level'} < $max_root_level)) {
# no indenting for shortcontents
$result .= "\n". ' ' x (2*($section->{'structure'}->{'section_level'} - $min_root_level))
if ($contents);
$result .= $self->html_attribute_class('ul', \@toc_ul_classes) .">\n";
$section = $section->{'structure'}->{'section_childs'}->[0];
} elsif ($section->{'structure'}->{'section_next'}
and $section->{'cmdname'} ne 'top') {
$result .= "
\n";
last if ($section eq $top_section);
$section = $section->{'structure'}->{'section_next'};
} else {
#last if ($section eq $top_section);
if ($section eq $top_section) {
$result .= "\n" unless ($section->{'cmdname'} eq 'top');
last;
}
while ($section->{'structure'}->{'section_up'}) {
$section = $section->{'structure'}->{'section_up'};
$result .= "\n". ' ' x (2*($section->{'structure'}->{'section_level'} - $min_root_level))
. "";
if ($section eq $top_section) {
$result .= "\n" if ($toplevel_contents);
last SECTION;
}
if ($section->{'structure'}->{'section_next'}) {
$result .= "\n";
$section = $section->{'structure'}->{'section_next'};
last;
}
}
}
}
}
if (@{$section_root->{'structure'}->{'section_childs'}} > 1) {
$result .= "\n";
}
if ($contents and !defined($self->get_conf('AFTER_TOC_LINES'))
or (!$contents and !defined($self->get_conf('AFTER_SHORT_TOC_LINES')))) {
$result .= "\n\n";
} elsif($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->gdt('This document was generated on @emph{@today{}} using @uref{{program_homepage}, @emph{{program}}}.',
{ 'program_homepage' => $self->get_conf('PACKAGE_URL'),
'program' => $self->get_conf('PROGRAM') }));
} else {
return $self->convert_tree(
$self->gdt('This document was generated on @emph{@today{}}.'));
}
}
sub _default_format_end_file($$)
{
my $self = shift;
my $filename = shift;
my $program_text = '';
if ($self->get_conf('PROGRAM_NAME_IN_FOOTER')) {
my $program_string
= &{$self->formatting_function('format_program_string')}($self);
my $open = $self->html_attribute_class('span', ['program-in-footer']);
if ($open ne '') {
$program_string = $open.'>'.$program_string.'';
}
$program_text .= "
$program_string
";
}
my $pre_body_close = $self->get_conf('PRE_BODY_CLOSE');
$pre_body_close = '' if (!defined($pre_body_close));
my $jslicenses = $self->get_info('jslicenses');
if ($jslicenses
and (($jslicenses->{'infojs'}
and scalar(keys %{$jslicenses->{'infojs'}}))
or (($self->get_file_information('mathjax', $filename)
or !$self->get_conf('SPLIT'))
and ($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')) {
$pre_body_close .=
''
.$self->convert_tree($self->gdt('JavaScript license information'))
.'';
}
}
return "${program_text}
$pre_body_close