ff4ff35918
Red Bear OS is a full fork. All sources must be available from git clone with zero network access. Removed gitignore rules that excluded fetched source trees under recipes/*/source/, local/recipes/kde/*/source/, local/recipes/qt/*/source/, and vendor source trees. Build artifacts (target/, build/, source.tar, *.o, *.so) remain excluded. 127291 files added — kernel, relibc, base, bootloader, pkgar, all KDE/Qt frameworks, mesa, wayland, DRM drivers, and every other recipe source.
11946 lines
435 KiB
Perl
11946 lines
435 KiB
Perl
# HTML.pm: output tree as HTML.
|
|
#
|
|
# Copyright 2011-2022 Free Software Foundation, Inc.
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 3 of the License,
|
|
# or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
#
|
|
# The documentation of the HTML customization API which is both
|
|
# used and implemented in the current file is in the customization_api
|
|
# Texinfo manual.
|
|
#
|
|
# Formatting and conversion functions that can be replaced by user-defined
|
|
# functions should only use documented functions to pass information
|
|
# and formatted content, such that users can overrides them independently
|
|
# without risking unwanted results. Also in formatting functions, the state of
|
|
# the converter should only be accessed through functions, such as in_math,
|
|
# in_preformatted, preformatted_classes_stack and similar functions.
|
|
#
|
|
# Original author: Patrice Dumas <pertusus@free.fr>
|
|
|
|
package Texinfo::Convert::HTML;
|
|
|
|
use 5.00405;
|
|
|
|
# See 'The "Unicode Bug"' under 'perlunicode' man page. This means
|
|
# that regular expressions will treat characters 128-255 in a Perl string
|
|
# the same regardless of whether the string is using a UTF-8 encoding.
|
|
# For older Perls, you can use utf8::upgrade on the strings, where the
|
|
# difference matters.
|
|
use if $] >= 5.012, feature => 'unicode_strings';
|
|
|
|
use if $] >= 5.014, re => '/a'; # ASCII-only character classes in regexes
|
|
|
|
use strict;
|
|
|
|
# To check if there is no erroneous autovivification
|
|
#no autovivification qw(fetch delete exists store strict);
|
|
|
|
use Carp qw(cluck confess);
|
|
|
|
use File::Copy qw(copy);
|
|
|
|
use Storable;
|
|
|
|
use Encode qw(find_encoding decode encode);
|
|
|
|
use Texinfo::Commands;
|
|
use Texinfo::Common;
|
|
use Texinfo::Config;
|
|
use Texinfo::Convert::Unicode;
|
|
use Texinfo::Convert::Texinfo;
|
|
use Texinfo::Convert::Utils;
|
|
use Texinfo::Convert::Text;
|
|
use Texinfo::Convert::NodeNameNormalization;
|
|
use Texinfo::Structuring;
|
|
use Texinfo::Convert::Converter;
|
|
|
|
# used to convert Texinfo to LaTeX math in @math and @displaymath
|
|
# for further conversion by softwares that only convert LaTeX.
|
|
# NOTE mathjax does not implement some constructs output by the
|
|
# Texinfo::Convert::LaTeX converter. Examples in 2022:
|
|
# \mathord{\text{}} \textsl{} \copyright{} \mathsterling{}
|
|
use Texinfo::Convert::LaTeX;
|
|
|
|
|
|
require Exporter;
|
|
use vars qw($VERSION @ISA);
|
|
@ISA = qw(Texinfo::Convert::Converter);
|
|
|
|
$VERSION = '7.0dev';
|
|
|
|
our $module_loaded = 0;
|
|
sub import {
|
|
if (!$module_loaded) {
|
|
Texinfo::XSLoader::override(
|
|
"Texinfo::Convert::HTML::_default_format_protect_text",
|
|
"Texinfo::MiscXS::default_format_protect_text");
|
|
Texinfo::XSLoader::override(
|
|
"Texinfo::Convert::HTML::_entity_text",
|
|
"Texinfo::MiscXS::entity_text");
|
|
$module_loaded = 1;
|
|
}
|
|
# The usual import method
|
|
goto &Exporter::import;
|
|
}
|
|
|
|
|
|
|
|
my %nobrace_commands = %Texinfo::Commands::nobrace_commands;
|
|
my %line_commands = %Texinfo::Commands::line_commands;
|
|
my %nobrace_symbol_text = %Texinfo::Common::nobrace_symbol_text;
|
|
my %accent_commands = %Texinfo::Commands::accent_commands;
|
|
my %sectioning_heading_commands = %Texinfo::Commands::sectioning_heading_commands;
|
|
my %def_commands = %Texinfo::Commands::def_commands;
|
|
my %ref_commands = %Texinfo::Commands::ref_commands;
|
|
my %brace_commands = %Texinfo::Commands::brace_commands;
|
|
my %block_commands = %Texinfo::Commands::block_commands;
|
|
my %root_commands = %Texinfo::Commands::root_commands;
|
|
my %preformatted_commands = %Texinfo::Commands::preformatted_commands;
|
|
my %math_commands = %Texinfo::Commands::math_commands;
|
|
my %preformatted_code_commands = %Texinfo::Commands::preformatted_code_commands;
|
|
my %letter_no_arg_commands = %Texinfo::Commands::letter_no_arg_commands;
|
|
|
|
my %formatted_line_commands = %Texinfo::Commands::formatted_line_commands;
|
|
my %formatted_nobrace_commands = %Texinfo::Commands::formatted_nobrace_commands;
|
|
my %formattable_line_commands = %Texinfo::Commands::formattable_line_commands;
|
|
my %explained_commands = %Texinfo::Commands::explained_commands;
|
|
my %inline_format_commands = %Texinfo::Commands::inline_format_commands;
|
|
my %brace_code_commands = %Texinfo::Commands::brace_code_commands;
|
|
my %default_index_commands = %Texinfo::Commands::default_index_commands;
|
|
my %small_block_associated_command = %Texinfo::Common::small_block_associated_command;
|
|
|
|
foreach my $def_command (keys(%def_commands)) {
|
|
$formatted_line_commands{$def_command} = 1 if ($line_commands{$def_command});
|
|
}
|
|
|
|
# FIXME remove raw commands?
|
|
my %format_context_commands = (%block_commands, %root_commands);
|
|
|
|
foreach my $misc_context_command('tab', 'item', 'itemx', 'headitem') {
|
|
$format_context_commands{$misc_context_command} = 1;
|
|
}
|
|
|
|
my %HTML_align_commands;
|
|
foreach my $align_command('raggedright', 'flushleft', 'flushright', 'center') {
|
|
$HTML_align_commands{$align_command} = 1;
|
|
}
|
|
|
|
my %composition_context_commands = (%preformatted_commands, %root_commands,
|
|
%HTML_align_commands);
|
|
$composition_context_commands{'float'} = 1;
|
|
my %format_raw_commands;
|
|
foreach my $block_command (keys(%block_commands)) {
|
|
$composition_context_commands{$block_command} = 1
|
|
if ($block_commands{$block_command} eq 'menu');
|
|
$format_raw_commands{$block_command} = 1
|
|
if ($block_commands{$block_command} eq 'format_raw');
|
|
}
|
|
|
|
# FIXME allow customization? (also in DocBook)
|
|
my %upper_case_commands = ( 'sc' => 1 );
|
|
|
|
|
|
# API for html formatting
|
|
|
|
sub _collect_css_element_class($$)
|
|
{
|
|
my $self = shift;
|
|
my $element_class = shift;
|
|
|
|
#if (not defined($self->{'current_filename'})) {
|
|
# cluck "CFND";
|
|
#}
|
|
if (defined($self->{'css_element_class_styles'}->{$element_class})) {
|
|
if ($self->{'document_global_context'}) {
|
|
$self->{'document_global_context_css'}->{$element_class} = 1;
|
|
} elsif (defined($self->{'current_filename'})) {
|
|
$self->{'file_css'}->{$self->{'current_filename'}} = {}
|
|
if (!$self->{'file_css'}->{$self->{'current_filename'}});
|
|
$self->{'file_css'}->{$self->{'current_filename'}}->{$element_class} = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
# $classes should be an array reference or undef
|
|
sub html_attribute_class($$;$)
|
|
{
|
|
my $self = shift;
|
|
my $element = shift;
|
|
my $classes = shift;
|
|
|
|
if (defined($classes) and ref($classes) ne 'ARRAY') {
|
|
confess("html_attribute_class: $classes not an array ref (for $element)");
|
|
}
|
|
if (!defined($classes) or scalar(@$classes) == 0
|
|
# API info: get_conf() API code conforming would be:
|
|
# or $self->get_conf('NO_CSS')) {
|
|
or $self->{'conf'}->{'NO_CSS'}) {
|
|
if ($element eq 'span') {
|
|
return '';
|
|
} else {
|
|
return "<$element";
|
|
}
|
|
}
|
|
|
|
my $style = '';
|
|
|
|
# API info: get_conf() API code conforming would be:
|
|
# if ($self->get_conf('INLINE_CSS_STYLE')) {
|
|
if ($self->{'conf'}->{'INLINE_CSS_STYLE'}) {
|
|
my @styles = ();
|
|
foreach my $style_class (@$classes) {
|
|
if (not defined($style_class)) {
|
|
confess ("class not defined (for $element)");
|
|
}
|
|
if (defined($self->{'css_element_class_styles'}
|
|
->{"$element.$style_class"})) {
|
|
push @styles,
|
|
$self->{'css_element_class_styles'}->{"$element.$style_class"};
|
|
}
|
|
}
|
|
if (scalar(@styles) > 0) {
|
|
$style = ' style="'.join(';', @styles).'"';
|
|
}
|
|
} else {
|
|
foreach my $style_class (@$classes) {
|
|
if (not defined($style_class)) {
|
|
confess ("class not defined (for $element)");
|
|
}
|
|
$self->_collect_css_element_class("$element.$style_class");
|
|
}
|
|
}
|
|
my $class_str = join(' ', map {_protect_class_name($self, $_)} @$classes);
|
|
return "<$element class=\"$class_str\"$style";
|
|
}
|
|
|
|
# for rules that cannot be collected during document output since they
|
|
# are not associated with a class attribute element setting
|
|
my %css_rules_not_collected = (
|
|
);
|
|
|
|
# returns an array of CSS element.class seen in the $FILENAME
|
|
sub html_get_css_elements_classes($;$)
|
|
{
|
|
my $self = shift;
|
|
my $filename = shift;
|
|
|
|
my %css_elements_classes = %css_rules_not_collected;
|
|
if ($self->{'document_global_context_css'}) {
|
|
%css_elements_classes = ( %css_elements_classes,
|
|
%{$self->{'document_global_context_css'}} );
|
|
}
|
|
|
|
if (defined($filename) and $self->{'file_css'}
|
|
and $self->{'file_css'}->{$filename}) {
|
|
%css_elements_classes = ( %css_elements_classes,
|
|
%{$self->{'file_css'}->{$filename}} );
|
|
}
|
|
|
|
if ($css_elements_classes{'a.copiable-link'}) {
|
|
$css_elements_classes{'span:hover a.copiable-link'} = 1;
|
|
}
|
|
|
|
return sort(keys(%css_elements_classes));
|
|
}
|
|
|
|
sub close_html_lone_element($$) {
|
|
my $self = shift;
|
|
my $html_element = shift;
|
|
if ($self->get_conf('USE_XML_SYNTAX')) {
|
|
return $html_element . '/>';
|
|
}
|
|
return $html_element .'>';
|
|
}
|
|
|
|
my $xml_numeric_entity_nbsp = '&#'.hex('00A0').';';
|
|
my $xml_named_entity_nbsp = ' ';
|
|
|
|
my $html_default_entity_nbsp = $xml_named_entity_nbsp;
|
|
|
|
sub substitute_html_non_breaking_space($$)
|
|
{
|
|
my $self = shift;
|
|
my $text = shift;
|
|
|
|
# do not use get_info() as it may not be set yet
|
|
my $non_breaking_space = $self->{'non_breaking_space'};
|
|
# using \Q \E on the substitution leads to spurious \
|
|
$text =~ s/\Q$html_default_entity_nbsp\E/$non_breaking_space/g;
|
|
return $text;
|
|
}
|
|
|
|
my @image_files_extensions = ('.png', '.jpg', '.jpeg', '.gif');
|
|
|
|
# this allows init files to get the location of the image files
|
|
# which cannot be determined from the result, as the file
|
|
# location is not used in the element output.
|
|
# FIXME use filenametext or url? url is always UTF-8 encoded
|
|
# to fit with percent encoding, filenametext uses the output
|
|
# encoding. As a file name, filenametext could make sense,
|
|
# although the underlying character obtained with utf-8 may also
|
|
# make sense. It is also used as the path part of a url.
|
|
# In practice, the user should check that the output encoding
|
|
# and the commands used in file names match, so url or
|
|
# filenametext should be the same.
|
|
sub html_image_file_location_name($$$$)
|
|
{
|
|
my $self = shift;
|
|
my $cmdname = shift;
|
|
my $command = shift;
|
|
my $args = shift;
|
|
|
|
my @extensions = @image_files_extensions;
|
|
|
|
my $image_file;
|
|
my $image_basefile;
|
|
my $image_extension;
|
|
# this variable is bytes encoded in the filesystem encoding
|
|
my ($image_path, $image_path_encoding);
|
|
if (defined($args->[0]->{'filenametext'})
|
|
and $args->[0]->{'filenametext'} ne '') {
|
|
$image_basefile = $args->[0]->{'filenametext'};
|
|
my $extension;
|
|
if (defined($args->[4]) and defined($args->[4]->{'filenametext'})) {
|
|
$extension = $args->[4]->{'filenametext'};
|
|
unshift @extensions, ("$extension", ".$extension");
|
|
}
|
|
foreach my $extension (@extensions) {
|
|
my ($file_name, $file_name_encoding)
|
|
= $self->encoded_input_file_name($image_basefile.$extension);
|
|
my $located_image_path
|
|
= $self->Texinfo::Common::locate_include_file($file_name);
|
|
if (defined($located_image_path) and $located_image_path ne '') {
|
|
$image_path = $located_image_path;
|
|
$image_path_encoding = $file_name_encoding;
|
|
# use the @-command argument and not the file found using the
|
|
# include paths. It is considered that the files in include paths
|
|
# will be moved by the caller anyway.
|
|
# If the file path found was to be used it should be decoded to perl
|
|
# codepoints too.
|
|
$image_file = $image_basefile.$extension;
|
|
$image_extension = $extension;
|
|
last;
|
|
}
|
|
}
|
|
if (!defined($image_file) or $image_file eq '') {
|
|
if (defined($extension) and $extension ne '') {
|
|
$image_file = $image_basefile.$extension;
|
|
$image_extension = $extension;
|
|
} else {
|
|
$image_file = "$image_basefile.jpg";
|
|
$image_extension = 'jpg';
|
|
}
|
|
}
|
|
}
|
|
return ($image_file, $image_basefile, $image_extension, $image_path,
|
|
$image_path_encoding);
|
|
}
|
|
|
|
sub css_add_info($$$;$)
|
|
{
|
|
my $self = shift;
|
|
my $spec = shift;
|
|
my $css_info = shift;
|
|
my $css_style = shift;
|
|
|
|
if ($spec eq 'rules') {
|
|
push @{$self->{'css_rule_lines'}}, $css_info;
|
|
} elsif ($spec eq 'imports') {
|
|
push @{$self->{'css_import_lines'}}, $css_info;
|
|
} else {
|
|
$self->{'css_element_class_styles'}->{$css_info} = $css_style;
|
|
}
|
|
}
|
|
|
|
sub css_get_info($$;$) {
|
|
my $self = shift;
|
|
my $spec = shift;
|
|
my $css_info = shift;
|
|
|
|
if ($spec eq 'rules') {
|
|
if (defined($self->{'css_rule_lines'})) {
|
|
return @{$self->{'css_rule_lines'}};
|
|
} else {
|
|
return ();
|
|
}
|
|
} elsif ($spec eq 'imports') {
|
|
if (defined($self->{'css_import_lines'})) {
|
|
return @{$self->{'css_import_lines'}};
|
|
} else {
|
|
return ();
|
|
}
|
|
} else {
|
|
if (defined($css_info)) {
|
|
if ($self->{'css_element_class_styles'}->{$css_info}) {
|
|
return $self->{'css_element_class_styles'}->{$css_info};
|
|
} else {
|
|
return undef;
|
|
}
|
|
} else {
|
|
return { %{$self->{'css_element_class_styles'}} };
|
|
}
|
|
}
|
|
}
|
|
|
|
my %default_css_string_commands_conversion;
|
|
my %default_css_string_types_conversion;
|
|
my %default_css_string_formatting_references;
|
|
|
|
sub html_convert_css_string($$;$)
|
|
{
|
|
my $self = shift;
|
|
my $element = shift;
|
|
my $explanation = shift;
|
|
|
|
my $saved_commands = {};
|
|
my $saved_types = {};
|
|
my $saved_formatting_references = {};
|
|
foreach my $cmdname (keys(%default_css_string_commands_conversion)) {
|
|
$saved_commands->{$cmdname} = $self->{'commands_conversion'}->{$cmdname};
|
|
$self->{'commands_conversion'}->{$cmdname}
|
|
= $default_css_string_commands_conversion{$cmdname};
|
|
}
|
|
foreach my $type (keys(%default_css_string_types_conversion)) {
|
|
$saved_types->{$type} = $self->{'types_conversion'}->{$type};
|
|
$self->{'types_conversion'}->{$type}
|
|
= $default_css_string_types_conversion{$type};
|
|
}
|
|
foreach my $formatting_reference
|
|
(keys(%default_css_string_formatting_references)) {
|
|
$saved_formatting_references->{$formatting_reference}
|
|
= $self->{'formatting_function'}->{$formatting_reference};
|
|
$self->{'formatting_function'}->{$formatting_reference}
|
|
= $default_css_string_formatting_references{$formatting_reference};
|
|
}
|
|
|
|
my $result = $self->convert_tree_new_formatting_context(
|
|
{'type' => '_string',
|
|
'contents' => [$element]},
|
|
'css_string', $explanation);
|
|
foreach my $cmdname (keys (%default_css_string_commands_conversion)) {
|
|
$self->{'commands_conversion'}->{$cmdname} = $saved_commands->{$cmdname};
|
|
}
|
|
foreach my $type (keys(%default_css_string_types_conversion)) {
|
|
$self->{'types_conversion'}->{$type} = $saved_types->{$type};
|
|
}
|
|
foreach my $formatting_reference (keys(%default_css_string_formatting_references)) {
|
|
$self->{'formatting_function'}->{$formatting_reference}
|
|
= $saved_formatting_references->{$formatting_reference};
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
my %special_list_mark_css_string_no_arg_command = (
|
|
# tried to use HYPHEN BULLET \2043 for use as in a bullet list, but, at least
|
|
# with my test of firefox the result is very different from a bullet.
|
|
# hyphen minus or hyphen \2010 are even smaller than hyphen bullet.
|
|
# Use the Unicode codepoint used normally for a mathematical minus \2212
|
|
# even though it is too large, since the others are too short...
|
|
# (which is actually the default, but this could change).
|
|
#'minus' => '-',
|
|
#'minus' => '\2010 ',
|
|
'minus' => '\2212 ',
|
|
);
|
|
|
|
sub html_convert_css_string_for_list_mark($$;$)
|
|
{
|
|
my $self = shift;
|
|
my $element = shift;
|
|
my $explanation = shift;
|
|
|
|
my $saved_css_string_no_arg_command = {};
|
|
foreach my $command (keys(%special_list_mark_css_string_no_arg_command)) {
|
|
$saved_css_string_no_arg_command->{$command}
|
|
= $self->{'no_arg_commands_formatting'}->{'css_string'}->{$command};
|
|
$self->{'no_arg_commands_formatting'}->{'css_string'}->{$command}
|
|
= $special_list_mark_css_string_no_arg_command{$command};
|
|
}
|
|
my $result = $self->html_convert_css_string($element, $explanation);
|
|
foreach my $command (keys(%special_list_mark_css_string_no_arg_command)) {
|
|
$self->{'no_arg_commands_formatting'}->{'css_string'}->{$command}
|
|
= $saved_css_string_no_arg_command->{$command};
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
# API to access converter state for conversion
|
|
|
|
sub in_math($)
|
|
{
|
|
my $self = shift;
|
|
return $self->{'document_context'}->[-1]->{'math'};
|
|
}
|
|
|
|
# set if in menu or preformatted command
|
|
sub in_preformatted($)
|
|
{
|
|
my $self = shift;
|
|
my $context = $self->{'document_context'}->[-1]->{'composition_context'}->[-1];
|
|
if ($preformatted_commands{$context}
|
|
or $self->{'pre_class_types'}->{$context}
|
|
or ($block_commands{$context}
|
|
and $block_commands{$context} eq 'menu'
|
|
and $self->_in_preformatted_in_menu())) {
|
|
return $context;
|
|
} else {
|
|
return undef;
|
|
}
|
|
}
|
|
|
|
sub in_upper_case($)
|
|
{
|
|
my $self = shift;
|
|
return $self->{'document_context'}->[-1]->{'formatting_context'}->[-1]
|
|
->{'upper_case'};
|
|
}
|
|
|
|
sub in_non_breakable_space($)
|
|
{
|
|
my $self = shift;
|
|
return $self->{'document_context'}->[-1]->{'formatting_context'}->[-1]
|
|
->{'no_break'};
|
|
}
|
|
|
|
sub in_space_protected($)
|
|
{
|
|
my $self = shift;
|
|
return $self->{'document_context'}->[-1]->{'formatting_context'}->[-1]
|
|
->{'space_protected'};
|
|
}
|
|
|
|
sub in_code($)
|
|
{
|
|
my $self = shift;
|
|
return $self->{'document_context'}->[-1]->{'monospace'}->[-1];
|
|
}
|
|
|
|
sub in_string($)
|
|
{
|
|
my $self = shift;
|
|
return $self->{'document_context'}->[-1]->{'string'};
|
|
}
|
|
|
|
sub in_verbatim($)
|
|
{
|
|
my $self = shift;
|
|
return $self->{'document_context'}->[-1]->{'verbatim'};
|
|
}
|
|
|
|
sub in_raw($)
|
|
{
|
|
my $self = shift;
|
|
return $self->{'document_context'}->[-1]->{'raw'};
|
|
}
|
|
|
|
sub in_multi_expanded($)
|
|
{
|
|
my $self = shift;
|
|
if (scalar(@{$self->{'multiple_pass'}})) {
|
|
return $self->{'multiple_pass'}->[-1];
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
sub paragraph_number($)
|
|
{
|
|
my $self = shift;
|
|
return $self->{'document_context'}->[-1]->{'formatting_context'}->[-1]
|
|
->{'paragraph_number'};
|
|
}
|
|
|
|
sub preformatted_number($)
|
|
{
|
|
my $self = shift;
|
|
return $self->{'document_context'}->[-1]->{'formatting_context'}->[-1]
|
|
->{'preformatted_number'};
|
|
}
|
|
|
|
sub count_elements_in_filename($$$)
|
|
{
|
|
my $self = shift;
|
|
my $spec = shift;
|
|
my $filename = shift;
|
|
|
|
if ($spec eq 'total') {
|
|
if (defined($self->{'elements_in_file_count'}->{$filename})) {
|
|
return $self->{'elements_in_file_count'}->{$filename};
|
|
}
|
|
} elsif ($spec eq 'remaining') {
|
|
if (defined($self->{'file_counters'}->{$filename})) {
|
|
return $self->{'file_counters'}->{$filename};
|
|
}
|
|
} elsif ($spec eq 'current') {
|
|
if (defined($self->{'file_counters'}->{$filename})) {
|
|
return $self->{'elements_in_file_count'}->{$filename}
|
|
- $self->{'file_counters'}->{$filename} +1;
|
|
}
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
sub top_block_command($)
|
|
{
|
|
my $self = shift;
|
|
return $self->{'document_context'}->[-1]->{'block_commands'}->[-1];
|
|
}
|
|
|
|
sub preformatted_classes_stack($)
|
|
{
|
|
my $self = shift;
|
|
return @{$self->{'document_context'}->[-1]->{'preformatted_classes'}};
|
|
}
|
|
|
|
sub in_align($)
|
|
{
|
|
my $self = shift;
|
|
my $context
|
|
= $self->{'document_context'}->[-1]->{'composition_context'}->[-1];
|
|
if ($HTML_align_commands{$context}) {
|
|
return $context;
|
|
} else {
|
|
return undef;
|
|
}
|
|
}
|
|
|
|
sub is_format_expanded($$)
|
|
{
|
|
my $self = shift;
|
|
my $format = shift;
|
|
|
|
return $self->{'expanded_formats_hash'}->{$format};
|
|
}
|
|
|
|
# the main data structure of the element target API is a hash reference, called
|
|
# the target information.
|
|
# The 'target' and 'filename' keys should be set for every type of element,
|
|
# but the other keys will only be set on some elements.
|
|
#
|
|
# The following keys can be set:
|
|
#
|
|
# Strings
|
|
#
|
|
# 'target': A unique string representing the target. Used as argument to
|
|
# 'id' attribute.
|
|
# 'contents_target': A unique string representing the target to the location
|
|
# of the element in the table of content.
|
|
# 'shortcontents_target': A unique string representing the target to the
|
|
# location of the element in the short table of contents
|
|
# 'node_filename': the file name deriving from the element node name
|
|
# 'section_filename': the file name deriving from the element section name
|
|
# 'special_element_filename': the file name of special elements
|
|
# (separate contents, about...)
|
|
# 'filename': the file name the element content is output to
|
|
# 'text', 'text_nonumber': a textual representation of the element where
|
|
# there is no restriction on the text formatting (ie HTML elements
|
|
# can be used).
|
|
# With _nonumber, no section number.
|
|
# 'string', 'string_nonumber': a textual representation of the element with
|
|
# restrictions on the available formatting, in practice no
|
|
# HTML elements, only entities to be able to use in attributes.
|
|
# With _nonumber, no section number.
|
|
#
|
|
# Other types
|
|
#
|
|
# 'tree', 'tree_nonumber: a Texinfo tree element which conversion should
|
|
# correspond to the element name.
|
|
# With _nonumber, no section number.
|
|
# 'node_command': the node element associated with the target element.
|
|
# 'root_element_command': the command associated to the top level element
|
|
# associated with the target element.
|
|
#
|
|
# Some functions cache their results in these hashes.
|
|
|
|
# $COMMAND should be a tree element which is a possible target of a link.
|
|
# return the target information.
|
|
sub _get_target($$)
|
|
{
|
|
my $self = shift;
|
|
my $command = shift;
|
|
my $target;
|
|
if (!defined($command)) {
|
|
cluck("_get_target command not defined");
|
|
}
|
|
if ($self->{'targets'}->{$command}) {
|
|
$target = $self->{'targets'}->{$command};
|
|
} elsif ($command->{'cmdname'}
|
|
# This should only happen for @*heading*, root_commands targets should
|
|
# already be set.
|
|
and $sectioning_heading_commands{$command->{'cmdname'}}
|
|
and !$root_commands{$command->{'cmdname'}}) {
|
|
$target = $self->_new_sectioning_command_target($command);
|
|
}
|
|
return $target;
|
|
}
|
|
|
|
# API for links and elements directions formatting
|
|
|
|
# This returns the id specific of the $COMMAND tree element
|
|
sub command_id($$)
|
|
{
|
|
my $self = shift;
|
|
my $command = shift;
|
|
my $target = $self->_get_target($command);
|
|
if ($target) {
|
|
return $target->{'target'};
|
|
} else {
|
|
return undef;
|
|
}
|
|
}
|
|
|
|
sub command_contents_target($$$)
|
|
{
|
|
my $self = shift;
|
|
my $command = shift;
|
|
my $contents_or_shortcontents = shift;
|
|
$contents_or_shortcontents = 'shortcontents'
|
|
if ($contents_or_shortcontents eq 'summarycontents');
|
|
|
|
my $target = $self->_get_target($command);
|
|
if ($target) {
|
|
return $target->{$contents_or_shortcontents .'_target'};
|
|
} else {
|
|
return undef;
|
|
}
|
|
}
|
|
|
|
sub _get_footnote_location_target($$)
|
|
{
|
|
my $self = shift;
|
|
my $command = shift;
|
|
|
|
if (defined($self->{'special_targets'})
|
|
and defined($self->{'special_targets'}->{'footnote_location'})
|
|
and defined($self->{'special_targets'}->{'footnote_location'}->{$command})) {
|
|
return $self->{'special_targets'}->{'footnote_location'}->{$command};
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
sub footnote_location_target($$)
|
|
{
|
|
my $self = shift;
|
|
my $command = shift;
|
|
|
|
my $footnote_location_special_target = _get_footnote_location_target($self,
|
|
$command);
|
|
if (defined($footnote_location_special_target)) {
|
|
return $footnote_location_special_target->{'target'};
|
|
}
|
|
}
|
|
|
|
sub command_filename($$)
|
|
{
|
|
my $self = shift;
|
|
my $command = shift;
|
|
|
|
my $target = $self->_get_target($command);
|
|
if ($target) {
|
|
if (exists($target->{'filename'})) {
|
|
return $target->{'filename'};
|
|
}
|
|
# this finds a special element for footnote command if such an element
|
|
# exists. This is best, the special element filename is the footnote
|
|
# filename.
|
|
my ($root_element, $root_command)
|
|
= $self->_html_get_tree_root_element($command, 1);
|
|
|
|
if (defined($root_element)
|
|
and $root_element->{'structure'}
|
|
and exists($root_element->{'structure'}->{'unit_filename'})) {
|
|
$target->{'filename'}
|
|
= $root_element->{'structure'}->{'unit_filename'};
|
|
return $root_element->{'structure'}->{'unit_filename'};
|
|
} else {
|
|
$target->{'filename'} = undef;
|
|
}
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
sub command_root_element_command($$)
|
|
{
|
|
my $self = shift;
|
|
my $command = shift;
|
|
|
|
my $target = $self->_get_target($command);
|
|
if ($target) {
|
|
if (not exists($target->{'root_element_command'})) {
|
|
# in contrast with command_filename() we find the root element through
|
|
# the location holding the @footnote command. It is better, as the
|
|
# footnote special element is not associated with a root command,
|
|
# it is better to stay in the document to find a root element.
|
|
my ($root_element, $root_command)
|
|
= $self->_html_get_tree_root_element($command);
|
|
if ($root_element and $root_element->{'extra'}) {
|
|
$target->{'root_element_command'}
|
|
= $root_element->{'extra'}->{'unit_command'};
|
|
} else {
|
|
$target->{'root_element_command'} = undef;
|
|
}
|
|
}
|
|
return $target->{'root_element_command'};
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
sub tree_unit_element_command($$)
|
|
{
|
|
my $self = shift;
|
|
my $element = shift;
|
|
|
|
if ($element and $element->{'extra'}) {
|
|
if ($element->{'extra'}->{'unit_command'}) {
|
|
return $element->{'extra'}->{'unit_command'};
|
|
} elsif (defined($element->{'type'})
|
|
and $element->{'type'} eq 'special_element') {
|
|
return $element;
|
|
}
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
sub command_node($$)
|
|
{
|
|
my $self = shift;
|
|
my $command = shift;
|
|
|
|
my $target = $self->_get_target($command);
|
|
if ($target) {
|
|
if (not exists($target->{'node_command'})) {
|
|
# this finds a special element for footnote command if
|
|
# such an element exists
|
|
my ($root_element, $root_command)
|
|
= $self->_html_get_tree_root_element($command, 1);
|
|
if (defined($root_command)) {
|
|
if ($root_command->{'cmdname'} and $root_command->{'cmdname'} eq 'node') {
|
|
$target->{'node_command'} = $root_command;
|
|
}
|
|
if ($root_command->{'extra'}
|
|
and $root_command->{'extra'}->{'associated_node'}) {
|
|
$target->{'node_command'}
|
|
= $root_command->{'extra'}->{'associated_node'};
|
|
}
|
|
} else {
|
|
$target->{'node_command'} = undef;
|
|
}
|
|
}
|
|
return $target->{'node_command'};
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
# Return string for linking to $COMMAND with <a href>
|
|
sub command_href($$;$$$)
|
|
{
|
|
my $self = shift;
|
|
my $command = shift;
|
|
my $source_filename = shift;
|
|
# for messages only
|
|
my $source_command = shift;
|
|
# to specify explicitly the target
|
|
my $specified_target = shift;
|
|
|
|
$source_filename = $self->{'current_filename'} if (!defined($source_filename));
|
|
|
|
if ($command->{'manual_content'}) {
|
|
return $self->_external_node_href($command, $source_filename,
|
|
$source_command);
|
|
}
|
|
|
|
my $target;
|
|
if (defined($specified_target)) {
|
|
$target = $specified_target;
|
|
} else {
|
|
my $target_command = $command;
|
|
# for sectioning command prefer the associated node
|
|
if ($command->{'extra'} and $command->{'extra'}->{'associated_node'}) {
|
|
$target_command = $command->{'extra'}->{'associated_node'};
|
|
}
|
|
my $target_information = $self->_get_target($target_command);
|
|
$target = $target_information->{'target'} if ($target_information);
|
|
}
|
|
return '' if (!defined($target));
|
|
my $href = '';
|
|
|
|
my $target_filename = $self->command_filename($command);
|
|
if (!defined($target_filename)) {
|
|
# Happens if there are no pages, for example if OUTPUT is set to ''
|
|
# as in the test cases. Also for things in @titlepage when
|
|
# titlepage is not output.
|
|
if ($self->{'tree_units'} and $self->{'tree_units'}->[0]
|
|
and $self->{'tree_units'}->[0]->{'structure'}
|
|
and defined($self->{'tree_units'}->[0]
|
|
->{'structure'}->{'unit_filename'})) {
|
|
# In that case use the first page.
|
|
$target_filename
|
|
= $self->{'tree_units'}->[0]->{'structure'}->{'unit_filename'};
|
|
}
|
|
}
|
|
if (defined($target_filename)) {
|
|
if (!defined($source_filename)
|
|
or $source_filename ne $target_filename) {
|
|
$href .= $target_filename;
|
|
# omit target if the command is an element command, there is only
|
|
# one element in file and there is a file in the href
|
|
my $command_root_element_command
|
|
= $self->command_root_element_command($command);
|
|
if (defined($source_filename)
|
|
and defined($command_root_element_command)
|
|
and ($command_root_element_command eq $command
|
|
or (defined($command_root_element_command->{'extra'})
|
|
and defined($command_root_element_command->{'extra'}
|
|
->{'associated_section'})
|
|
and $command_root_element_command->{'extra'}->{'associated_section'}
|
|
eq $command))) {
|
|
my $count_elements_in_file
|
|
= $self->count_elements_in_filename('total', $target_filename);
|
|
if (defined($count_elements_in_file) and $count_elements_in_file == 1) {
|
|
$target = '';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
$href .= '#' . $target if ($target ne '');
|
|
return $href;
|
|
}
|
|
|
|
my %contents_command_special_element_variety = (
|
|
'contents' => 'contents',
|
|
'shortcontents' => 'shortcontents',
|
|
'summarycontents' => 'shortcontents',
|
|
);
|
|
|
|
# Return string for linking to $CONTENTS_OR_SHORTCONTENTS associated
|
|
# element from $COMMAND with <a href>
|
|
sub command_contents_href($$$;$)
|
|
{
|
|
my $self = shift;
|
|
my $command = shift;
|
|
my $contents_or_shortcontents = shift;
|
|
my $source_filename = shift;
|
|
|
|
$source_filename = $self->{'current_filename'}
|
|
if (not defined($source_filename));
|
|
|
|
my ($special_element_variety, $target_element, $class_base,
|
|
$special_element_direction)
|
|
= $self->command_name_special_element_information($contents_or_shortcontents);
|
|
my $target
|
|
= $self->command_contents_target($command, $contents_or_shortcontents);
|
|
my $target_filename;
|
|
# !defined happens when called as convert() and not output()
|
|
if (defined($target_element)) {
|
|
$target_filename = $self->command_filename($target_element);
|
|
}
|
|
my $href = '';
|
|
if (defined($target_filename) and
|
|
(!defined($source_filename)
|
|
or $source_filename ne $target_filename)) {
|
|
$href .= $target_filename;
|
|
}
|
|
$href .= '#' . $target if ($target ne '');
|
|
return $href;
|
|
}
|
|
|
|
sub footnote_location_href($$;$$$)
|
|
{
|
|
my $self = shift;
|
|
my $command = shift;
|
|
my $source_filename = shift;
|
|
my $specified_target = shift;
|
|
my $target_filename = shift;
|
|
|
|
$source_filename = $self->{'current_filename'}
|
|
if (not defined($source_filename));
|
|
|
|
my $special_target = _get_footnote_location_target($self, $command);
|
|
my $target = '';
|
|
if (defined($specified_target)) {
|
|
$target = $specified_target;
|
|
} elsif (defined($special_target)) {
|
|
$target = $special_target->{'target'};
|
|
}
|
|
# In the default footnote formatting functions, which calls
|
|
# footnote_location_href, the target file is always known as the
|
|
# footnote in the document appears before the footnote text formatting.
|
|
# $target_filename is therefore always defined. It is a good thing
|
|
# for the case of @footnote being formatted more than once (in multiple
|
|
# @insertcopying for instance) as the file found just below may not be the
|
|
# correct one in such a case.
|
|
if (not defined($target_filename)) {
|
|
if (defined($special_target) and defined($special_target->{'filename'})) {
|
|
$target_filename = $special_target->{'filename'};
|
|
} else {
|
|
# in contrast with command_filename() we find the location holding
|
|
# the @footnote command, not the footnote element with footnotes
|
|
my ($root_element, $root_command)
|
|
= $self->_html_get_tree_root_element($command);
|
|
if (defined($root_element)) {
|
|
if (not defined($special_target)) {
|
|
$self->{'special_targets'}->{'footnote_location'}->{$command} = {};
|
|
$special_target
|
|
= $self->{'special_targets'}->{'footnote_location'}->{$command};
|
|
}
|
|
$special_target->{'filename'}
|
|
= $root_element->{'structure'}->{'unit_filename'};
|
|
$target_filename = $special_target->{'filename'};
|
|
}
|
|
}
|
|
}
|
|
my $href = '';
|
|
if (defined($target_filename) and
|
|
(!defined($source_filename)
|
|
or $source_filename ne $target_filename)) {
|
|
$href .= $target_filename;
|
|
}
|
|
$href .= '#' . $target if ($target ne '');
|
|
return $href;
|
|
}
|
|
|
|
# Return text to be used for a hyperlink to $COMMAND.
|
|
# $TYPE refers to the type of value returned from this function:
|
|
# 'text' - return text
|
|
# 'tree' - return a tree
|
|
# 'tree_nonumber' - return tree representing text without a chapter number
|
|
# being included.
|
|
# 'string' - return simpler text that can be used in element attributes
|
|
sub command_text($$;$)
|
|
{
|
|
my $self = shift;
|
|
my $command = shift;
|
|
my $type = shift;
|
|
|
|
if (!defined($type)) {
|
|
$type = 'text';
|
|
}
|
|
if (!defined($command)) {
|
|
cluck "in command_text($type) command not defined";
|
|
}
|
|
|
|
if ($command->{'manual_content'}) {
|
|
my $node_content = [];
|
|
$node_content = $command->{'node_content'}
|
|
if (defined($command->{'node_content'}));
|
|
my $tree;
|
|
if ($command->{'manual_content'}) {
|
|
$tree = {'type' => '_code',
|
|
'contents' => [{'text' => '('}, @{$command->{'manual_content'}},
|
|
{'text' => ')'}, @$node_content]};
|
|
} else {
|
|
$tree = {'type' => '_code',
|
|
'contents' => $node_content};
|
|
}
|
|
if ($type eq 'tree') {
|
|
return $tree;
|
|
} else {
|
|
if ($type eq 'string') {
|
|
$tree = {'type' => '_string',
|
|
'contents' => [$tree]};
|
|
}
|
|
my $result = $self->convert_tree_new_formatting_context(
|
|
# FIXME check if $document_global_context argument is really needed
|
|
$tree, $command->{'cmdname'}, 'command_text manual_content');
|
|
return $result;
|
|
}
|
|
}
|
|
|
|
my $target = $self->_get_target($command);
|
|
if ($target) {
|
|
my $explanation;
|
|
$explanation = "command_text:$type \@$command->{'cmdname'}"
|
|
if ($command->{'cmdname'});
|
|
if (defined($target->{$type})) {
|
|
return $target->{$type};
|
|
}
|
|
my $tree;
|
|
if (!$target->{'tree'}) {
|
|
if (defined($command->{'type'})
|
|
and $command->{'type'} eq 'special_element') {
|
|
my $special_element_variety
|
|
= $command->{'extra'}->{'special_element_variety'};
|
|
$tree
|
|
= $self->special_element_info('heading_tree',
|
|
$special_element_variety);
|
|
$tree = {} if (!defined($tree));
|
|
$explanation = "command_text $special_element_variety";
|
|
} elsif ($command->{'cmdname'} and ($command->{'cmdname'} eq 'node'
|
|
or $command->{'cmdname'} eq 'anchor')) {
|
|
# FIXME is it possible not to have contents (nor args)?
|
|
$tree = {'type' => '_code',
|
|
'contents' => $command->{'args'}->[0]->{'contents'}};
|
|
} elsif ($command->{'cmdname'} and ($command->{'cmdname'} eq 'float')) {
|
|
$tree = $self->float_type_number($command);
|
|
} elsif ($command->{'extra'}
|
|
and $command->{'extra'}->{'missing_argument'}) {
|
|
if ($type eq 'tree' or $type eq 'tree_nonumber') {
|
|
return {};
|
|
} else {
|
|
return '';
|
|
}
|
|
} else {
|
|
my $section_arg_contents = [];
|
|
$section_arg_contents = $command->{'args'}->[0]->{'contents'}
|
|
if $command->{'args'}->[0]->{'contents'};
|
|
if ($command->{'structure'}
|
|
and defined($command->{'structure'}->{'section_number'})
|
|
and ($self->get_conf('NUMBER_SECTIONS')
|
|
or !defined($self->get_conf('NUMBER_SECTIONS')))) {
|
|
if ($command->{'cmdname'} eq 'appendix'
|
|
and $command->{'structure'}->{'section_level'} == 1) {
|
|
$tree = $self->gdt('Appendix {number} {section_title}',
|
|
{'number' => {'text' => $command->{'structure'}
|
|
->{'section_number'}},
|
|
'section_title'
|
|
=> {'contents' => $section_arg_contents}});
|
|
} else {
|
|
# TRANSLATORS: numbered section title
|
|
$tree = $self->gdt('{number} {section_title}',
|
|
{'number' => {'text' => $command->{'structure'}
|
|
->{'section_number'}},
|
|
'section_title'
|
|
=> {'contents' => $section_arg_contents}});
|
|
}
|
|
} else {
|
|
$tree = {'contents' => $section_arg_contents};
|
|
}
|
|
|
|
$target->{'tree_nonumber'}
|
|
= {'contents' => $section_arg_contents};
|
|
}
|
|
$target->{'tree'} = $tree;
|
|
} else {
|
|
$tree = $target->{'tree'};
|
|
}
|
|
return $target->{'tree_nonumber'} if ($type eq 'tree_nonumber'
|
|
and $target->{'tree_nonumber'});
|
|
return $tree if ($type eq 'tree' or $type eq 'tree_nonumber');
|
|
|
|
$self->_new_document_context($command->{'cmdname'}, $explanation);
|
|
|
|
if ($type eq 'string') {
|
|
$tree = {'type' => '_string',
|
|
'contents' => [$tree]};
|
|
}
|
|
|
|
if ($type =~ /^(.*)_nonumber$/) {
|
|
$tree = $target->{'tree_nonumber'}
|
|
if (defined($target->{'tree_nonumber'}));
|
|
}
|
|
$self->{'ignore_notice'}++;
|
|
push @{$self->{'referred_command_stack'}}, $command;
|
|
$target->{$type} = $self->_convert($tree, $explanation);
|
|
pop @{$self->{'referred_command_stack'}};
|
|
$self->{'ignore_notice'}--;
|
|
|
|
$self->_pop_document_context();
|
|
return $target->{$type};
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
# Return the element in the tree that $LABEL refers to.
|
|
sub label_command($$)
|
|
{
|
|
my $self = shift;
|
|
my $label = shift;
|
|
if (!defined($label)) {
|
|
cluck;
|
|
}
|
|
if ($self->{'labels'}) {
|
|
return $self->{'labels'}->{$label};
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
sub special_direction_element($$)
|
|
{
|
|
my $self = shift;
|
|
my $direction = shift;
|
|
return $self->{'special_elements_directions'}->{$direction};
|
|
}
|
|
|
|
sub command_name_special_element_information($$)
|
|
{
|
|
my $self = shift;
|
|
my $cmdname = shift;
|
|
|
|
my $special_element_variety;
|
|
if (exists($contents_command_special_element_variety{$cmdname})) {
|
|
$special_element_variety
|
|
= $contents_command_special_element_variety{$cmdname};
|
|
} elsif ($cmdname eq 'footnote') {
|
|
$special_element_variety = 'footnotes';
|
|
} else {
|
|
return (undef, undef, undef, undef);
|
|
}
|
|
my $special_element_direction
|
|
= $self->special_element_info('direction', $special_element_variety);
|
|
my $special_element
|
|
= $self->special_direction_element($special_element_direction);
|
|
my $class_base
|
|
= $self->special_element_info('class', $special_element_variety);
|
|
return ($special_element_variety, $special_element, $class_base,
|
|
$special_element_direction);
|
|
}
|
|
|
|
sub global_direction_element($$)
|
|
{
|
|
my $self = shift;
|
|
my $direction = shift;
|
|
return $self->{'global_target_elements_directions'}->{$direction};
|
|
}
|
|
|
|
sub get_element_root_command_element($$)
|
|
{
|
|
my $self = shift;
|
|
my $element = shift;
|
|
|
|
my ($root_element, $root_command) = _html_get_tree_root_element($self, $element);
|
|
if (defined($root_command)) {
|
|
if ($self->get_conf('USE_NODES')) {
|
|
if ($root_command->{'cmdname'} and $root_command->{'cmdname'} eq 'node') {
|
|
return ($root_element, $root_command);
|
|
} elsif ($root_command->{'extra'}
|
|
and $root_command->{'extra'}->{'associated_node'}) {
|
|
return ($root_element, $root_command->{'extra'}->{'associated_node'});
|
|
}
|
|
} elsif ($root_command->{'cmdname'}
|
|
and $root_command->{'cmdname'} eq 'node'
|
|
and $root_command->{'extra'}
|
|
and $root_command->{'extra'}->{'associated_section'}) {
|
|
return ($root_element, $root_command->{'extra'}->{'associated_section'});
|
|
}
|
|
}
|
|
return ($root_element, $root_command);
|
|
}
|
|
|
|
my %valid_direction_return_type = (
|
|
# a string that can be used in a href linking to the direction
|
|
'href' => 1,
|
|
# a string representing the direction that can be used in
|
|
# context where only entities are available (attributes)
|
|
'string' => 1,
|
|
# a string representing the direction to be used in contexts
|
|
# not restricted in term of available formatting (ie with HTML elements)
|
|
'text' => 1,
|
|
# Texinfo tree element representing the direction
|
|
'tree' => 1,
|
|
# string representing the target, typically used as id and in href
|
|
'target' => 1,
|
|
# same as 'text', but select node in priority
|
|
'node' => 1,
|
|
# same as 'text_nonumber' but select section in priority
|
|
'section' => 1
|
|
);
|
|
|
|
foreach my $no_number_type ('text', 'tree', 'string') {
|
|
# without section number
|
|
$valid_direction_return_type{$no_number_type .'_nonumber'} = 1;
|
|
}
|
|
|
|
# sub from_element_direction($SELF, $DIRECTION, $TYPE, $SOURCE_ELEMENT,
|
|
# $SOURCE_FILENAME, $SOURCE_FOR_MESSAGES)
|
|
#
|
|
# Return text used for linking from $SOURCE_ELEMENT in direction $DIRECTION.
|
|
# The text returned depends on $TYPE.
|
|
#
|
|
# This is used both for tree unit elements and external nodes
|
|
#
|
|
# If $SOURCE_ELEMENT is undef, $self->{'current_root_element'} is used.
|
|
#
|
|
# $SOURCE_FOR_MESSAGES is an element used for messages formatting, to get a
|
|
# location in input file. It is better to choose the node and not the
|
|
# sectioning command associated with the element, as the error messages
|
|
# are about external nodes not found.
|
|
#
|
|
# $self->{'current_root_element'} undef happens at least when there is no
|
|
# output file, or for the table of content when frames are used. That call
|
|
# would result for instance from from_element_direction being called from
|
|
# _get_links, itself called from 'format_begin_file' which, in the default case
|
|
# points to _default_format_begin_file.
|
|
# TODO are there other cases?
|
|
sub from_element_direction($$$;$$$)
|
|
{
|
|
my $self = shift;
|
|
my $direction = shift;
|
|
my $type = shift;
|
|
my $source_element = shift;
|
|
my $source_filename = shift;
|
|
# for messages only
|
|
my $source_command = shift;
|
|
|
|
my $target_element;
|
|
my $command;
|
|
my $target;
|
|
|
|
$source_element = $self->{'current_root_element'} if (!defined($source_element));
|
|
$source_filename = $self->{'current_filename'} if (!defined($source_filename));
|
|
|
|
if (!$valid_direction_return_type{$type}) {
|
|
print STDERR "Incorrect type $type in from_element_direction call\n";
|
|
return undef;
|
|
}
|
|
my $global_target_element = $self->global_direction_element($direction);
|
|
if ($global_target_element) {
|
|
$target_element = $global_target_element;
|
|
# output TOP_NODE_UP related infos even if element is not
|
|
# defined which should mostly correspond to cases when there is no
|
|
# output file, for example in the tests.
|
|
} elsif ((not defined($source_element)
|
|
or ($source_element
|
|
and $self->element_is_tree_unit_top($source_element)))
|
|
and defined($self->get_conf('TOP_NODE_UP_URL'))
|
|
and ($direction eq 'Up' or $direction eq 'NodeUp')) {
|
|
if ($type eq 'href') {
|
|
return $self->get_conf('TOP_NODE_UP_URL');
|
|
} elsif ($type eq 'text' or $type eq 'node' or $type eq 'string'
|
|
or $type eq 'section') {
|
|
return $self->get_conf('TOP_NODE_UP');
|
|
} else {
|
|
cluck("type $type not available for TOP_NODE_UP\n");
|
|
return '';
|
|
}
|
|
} elsif (not $target_element and $source_element
|
|
and $source_element->{'structure'}
|
|
and $source_element->{'structure'}->{'directions'}
|
|
and $source_element->{'structure'}->{'directions'}->{$direction}) {
|
|
$target_element
|
|
= $source_element->{'structure'}->{'directions'}->{$direction};
|
|
}
|
|
|
|
if ($target_element) {
|
|
######## debug
|
|
if (!$target_element->{'type'}) {
|
|
die "No type for element_target $direction $target_element: "
|
|
. Texinfo::Common::debug_print_element_details($target_element, 1)
|
|
. "directions :"
|
|
. Texinfo::Structuring::print_element_directions($source_element);
|
|
}
|
|
########
|
|
if ($target_element->{'type'} eq 'external_node') {
|
|
my $external_node = $target_element->{'extra'};
|
|
#print STDERR "FROM_ELEMENT_DIRECTION ext node $type $direction\n"
|
|
# if ($self->get_conf('DEBUG'));
|
|
if ($type eq 'href') {
|
|
return $self->command_href($external_node, $source_filename,
|
|
$source_command);
|
|
} elsif ($type eq 'text' or $type eq 'node') {
|
|
return $self->command_text($external_node);
|
|
} elsif ($type eq 'string') {
|
|
return $self->command_text($external_node, $type);
|
|
}
|
|
} elsif ($type eq 'node') {
|
|
if ($target_element->{'extra'}
|
|
and $target_element->{'extra'}->{'unit_command'}) {
|
|
if ($target_element->{'extra'}->{'unit_command'}->{'cmdname'} eq 'node') {
|
|
$command = $target_element->{'extra'}->{'unit_command'};
|
|
} elsif ($target_element->{'extra'}->{'unit_command'}->{'extra'}
|
|
and $target_element->{'extra'}->{'unit_command'}
|
|
->{'extra'}->{'associated_node'}) {
|
|
$command = $target_element->{'extra'}->{'unit_command'}
|
|
->{'extra'}->{'associated_node'};
|
|
}
|
|
}
|
|
$target = $self->{'targets'}->{$command} if ($command);
|
|
$type = 'text';
|
|
} elsif ($type eq 'section') {
|
|
if ($target_element->{'extra'}
|
|
and $target_element->{'extra'}->{'unit_command'}) {
|
|
if ($target_element->{'extra'}->{'unit_command'}->{'cmdname'} ne 'node') {
|
|
$command = $target_element->{'extra'}->{'unit_command'};
|
|
} elsif ($target_element->{'extra'}->{'unit_command'}->{'extra'}
|
|
and $target_element->{'extra'}->{'unit_command'}
|
|
->{'extra'}->{'associated_section'}) {
|
|
$command = $target_element->{'extra'}->{'unit_command'}
|
|
->{'extra'}->{'associated_section'};
|
|
}
|
|
}
|
|
$target = $self->{'targets'}->{$command} if ($command);
|
|
$type = 'text_nonumber';
|
|
} else {
|
|
if (defined($target_element->{'type'})
|
|
and $target_element->{'type'} eq 'special_element') {
|
|
$command = $target_element;
|
|
} elsif ($target_element->{'extra'}) {
|
|
$command = $target_element->{'extra'}->{'unit_command'};
|
|
}
|
|
if ($type eq 'href') {
|
|
if (defined($command)) {
|
|
return $self->command_href($command, $source_filename);
|
|
} else {
|
|
return '';
|
|
}
|
|
}
|
|
$target = $self->{'targets'}->{$command} if ($command);
|
|
}
|
|
} elsif ($self->special_direction_element($direction)) {
|
|
$target_element = $self->special_direction_element($direction);
|
|
$command = $target_element;
|
|
if ($type eq 'href') {
|
|
return $self->command_href($target_element, $source_filename);
|
|
}
|
|
$target = $self->{'targets'}->{$target_element};
|
|
} else {
|
|
return undef;
|
|
}
|
|
|
|
if ($target and exists($target->{$type})) {
|
|
return $target->{$type};
|
|
} elsif ($type eq 'target') {
|
|
return undef;
|
|
} elsif ($command) {
|
|
#print STDERR "FROM_ELEMENT_DIRECTION $type $direction\n"
|
|
# if ($self->get_conf('DEBUG'));
|
|
return $self->command_text($command, $type);
|
|
}
|
|
}
|
|
|
|
|
|
my %valid_direction_string_type = (
|
|
# accesskey associated to the direction
|
|
'accesskey' => 1,
|
|
# direction button name
|
|
'button' => 1,
|
|
# description of the direction
|
|
'description' => 1,
|
|
# section number corresponding to the example in About text
|
|
'example' => 1,
|
|
# rel/ref string associated to the direction
|
|
'rel' => 1,
|
|
# few words text associated to the direction
|
|
'text' => 1,
|
|
);
|
|
|
|
my %valid_direction_string_context = (
|
|
'normal' => 1,
|
|
'string' => 1,
|
|
);
|
|
|
|
my %direction_type_translation_context = (
|
|
'button' => 'button label',
|
|
'description' => 'description',
|
|
'text' => 'string',
|
|
);
|
|
|
|
sub direction_string($$$;$)
|
|
{
|
|
my $self = shift;
|
|
my $direction = shift;
|
|
my $string_type = shift;
|
|
my $context = shift;
|
|
|
|
if (!$valid_direction_string_type{$string_type}) {
|
|
print STDERR "Incorrect type $string_type in direction_string call\n";
|
|
return undef;
|
|
}
|
|
|
|
$context = 'normal' if (!defined($context));
|
|
|
|
if (!$valid_direction_string_context{$context}) {
|
|
print STDERR "Incorrect context $context in direction_string call\n";
|
|
return undef;
|
|
}
|
|
|
|
$direction =~ s/^FirstInFile//;
|
|
|
|
if (not exists($self->{'directions_strings'}->{$string_type}->{$direction})
|
|
or not exists($self->{'directions_strings'}->{$string_type}
|
|
->{$direction}->{$context})) {
|
|
$self->{'directions_strings'}->{$string_type}->{$direction} = {}
|
|
if not exists($self->{'directions_strings'}->{$string_type}->{$direction});
|
|
my $translated_directions_strings = $self->{'translated_direction_strings'};
|
|
if (defined($translated_directions_strings->{$string_type}
|
|
->{$direction}->{'converted'})) {
|
|
# translate already converted direction strings
|
|
my $converted_directions
|
|
= $translated_directions_strings->{$string_type}->{$direction}->{'converted'};
|
|
my $context_converted_string;
|
|
if ($converted_directions->{$context}) {
|
|
$context_converted_string = $converted_directions->{$context};
|
|
} elsif ($context eq 'string'
|
|
and defined($converted_directions->{'normal'})) {
|
|
$context_converted_string = $converted_directions->{'normal'};
|
|
}
|
|
if (defined($context_converted_string)) {
|
|
my $result_string
|
|
= $self->gdt($context_converted_string, undef, undef, 'translated_text');
|
|
$self->{'directions_strings'}->{$string_type}->{$direction}->{$context}
|
|
= $self->substitute_html_non_breaking_space($result_string);
|
|
} else {
|
|
$self->{'directions_strings'}->{$string_type}->{$direction}->{$context}
|
|
= undef;
|
|
}
|
|
} elsif (defined($translated_directions_strings->{$string_type}
|
|
->{$direction}->{'to_convert'})) {
|
|
# translate direction strings that need to be translated and converted
|
|
my $context_string = $direction;
|
|
$context_string .= ' (current section)' if ($direction eq 'This');
|
|
$context_string = $context_string.' direction '
|
|
.$direction_type_translation_context{$string_type};
|
|
my $translated_tree
|
|
= $self->pgdt($context_string,
|
|
$translated_directions_strings->{$string_type}
|
|
->{$direction}->{'to_convert'});
|
|
my $converted_tree;
|
|
if ($context eq 'string') {
|
|
$converted_tree = {
|
|
'type' => '_string',
|
|
'contents' => [$translated_tree]};
|
|
} else {
|
|
$converted_tree = $translated_tree;
|
|
}
|
|
my $result_string = $self->convert_tree_new_formatting_context($converted_tree,
|
|
"direction $direction", undef, "direction $direction");
|
|
$self->{'directions_strings'}->{$string_type}->{$direction}->{$context}
|
|
= $result_string;
|
|
} else {
|
|
# FIXME or ''
|
|
$self->{'directions_strings'}->{$string_type}->{$direction}->{$context}
|
|
= undef;
|
|
}
|
|
}
|
|
return $self->{'directions_strings'}->{$string_type}->{$direction}->{$context};
|
|
}
|
|
|
|
my %default_translated_special_element_info;
|
|
|
|
# if SPECIAL_ELEMENT_VARIETY is not set, return all the varieties
|
|
sub special_element_info($$;$) {
|
|
my $self = shift;
|
|
my $type = shift;
|
|
my $special_element_variety = shift;
|
|
|
|
if ($self->{'translated_special_element_info'}->{$type}) {
|
|
my $translated_special_element_info
|
|
= $self->{'translated_special_element_info'}->{$type}->[1];
|
|
if (not defined($special_element_variety)) {
|
|
return sort(keys(%{$translated_special_element_info}));
|
|
}
|
|
|
|
if (not exists($self->{'special_element_info'}->{$type}
|
|
->{$special_element_variety})) {
|
|
my $special_element_info_string = $translated_special_element_info
|
|
->{$special_element_variety};
|
|
my $translated_tree;
|
|
if (defined($special_element_info_string)) {
|
|
my $translation_context = "$special_element_variety section heading";
|
|
$translated_tree = $self->pgdt($translation_context,
|
|
$special_element_info_string);
|
|
}
|
|
$self->{'special_element_info'}->{$type}->{$special_element_variety}
|
|
= $translated_tree;
|
|
}
|
|
}
|
|
if (not defined($special_element_variety)) {
|
|
return sort(keys(%{$self->{'special_element_info'}->{$type}}));
|
|
}
|
|
return $self->{'special_element_info'}->{$type}->{$special_element_variety};
|
|
}
|
|
|
|
# API for misc conversion and formatting functions
|
|
|
|
# it is considered 'top' only if element corresponds to @top or
|
|
# element is a node
|
|
sub element_is_tree_unit_top($$)
|
|
{
|
|
my $self = shift;
|
|
my $element = shift;
|
|
my $top_element = $self->global_direction_element('Top');
|
|
return (defined($top_element) and $top_element eq $element
|
|
and $element->{'extra'}
|
|
and $element->{'extra'}->{'unit_command'}
|
|
and ($element->{'extra'}->{'unit_command'}->{'cmdname'} eq 'node'
|
|
or $element->{'extra'}->{'unit_command'}->{'cmdname'} eq 'top'));
|
|
}
|
|
|
|
my %default_formatting_references;
|
|
sub default_formatting_function($$)
|
|
{
|
|
my $self = shift;
|
|
my $format = shift;
|
|
return $default_formatting_references{$format};
|
|
}
|
|
|
|
sub formatting_function($$)
|
|
{
|
|
my $self = shift;
|
|
my $format = shift;
|
|
return $self->{'formatting_function'}->{$format};
|
|
}
|
|
|
|
my %defaults_format_special_body_contents;
|
|
|
|
sub defaults_special_element_body_formatting($$)
|
|
{
|
|
my $self = shift;
|
|
my $special_element_variety = shift;
|
|
|
|
return $defaults_format_special_body_contents{$special_element_variety};
|
|
}
|
|
|
|
sub special_element_body_formatting($$)
|
|
{
|
|
my $self = shift;
|
|
my $special_element_variety = shift;
|
|
|
|
return $self->{'special_element_body'}->{$special_element_variety};
|
|
}
|
|
|
|
# Return the default for the function references used for
|
|
# the formatting of commands, in case a user still wants to call
|
|
# default @-commands formatting functions when replacing functions,
|
|
# using code along
|
|
# &{$self->default_command_conversion($cmdname)}($self, $cmdname, $command, args, $content)
|
|
my %default_commands_conversion;
|
|
|
|
sub default_command_conversion($$)
|
|
{
|
|
my $self = shift;
|
|
my $command = shift;
|
|
return $default_commands_conversion{$command};
|
|
}
|
|
|
|
sub command_conversion($$)
|
|
{
|
|
my $self = shift;
|
|
my $command = shift;
|
|
return $self->{'commands_conversion'}->{$command};
|
|
}
|
|
|
|
my %default_commands_open;
|
|
|
|
sub default_command_open($$)
|
|
{
|
|
my $self = shift;
|
|
my $command = shift;
|
|
return $default_commands_open{$command};
|
|
}
|
|
|
|
# used for customization only (in t2h_singular.init)
|
|
sub get_value($$)
|
|
{
|
|
my $self = shift;
|
|
my $value = shift;
|
|
if (defined($self->{'values'})
|
|
and exists ($self->{'values'}->{$value})) {
|
|
return $self->{'values'}->{$value};
|
|
} else {
|
|
return undef;
|
|
}
|
|
}
|
|
|
|
# $INITIALIZATION_VALUE is only used for the initialization.
|
|
# If it is not a reference, it is turned into a scalar reference.
|
|
sub shared_conversion_state($$;$)
|
|
{
|
|
my $self = shift;
|
|
my $state_name = shift;
|
|
my $initialization_value = shift;
|
|
|
|
if (not defined($self->{'shared_conversion_state'}->{$state_name})) {
|
|
if (not ref($initialization_value)) {
|
|
$self->{'shared_conversion_state'}->{$state_name} = \$initialization_value;
|
|
} else {
|
|
$self->{'shared_conversion_state'}->{$state_name} = $initialization_value;
|
|
}
|
|
}
|
|
return $self->{'shared_conversion_state'}->{$state_name};
|
|
}
|
|
|
|
sub register_footnote($$$$$$$)
|
|
{
|
|
my ($self, $command, $footid, $docid, $number_in_doc,
|
|
$footnote_location_filename, $multi_expanded_region) = @_;
|
|
my $in_skipped_node_top
|
|
= $self->shared_conversion_state('in_skipped_node_top', 0);
|
|
if ($$in_skipped_node_top != 1) {
|
|
push @{$self->{'pending_footnotes'}}, [$command, $footid, $docid,
|
|
$number_in_doc, $footnote_location_filename, $multi_expanded_region];
|
|
}
|
|
}
|
|
|
|
sub get_pending_footnotes($)
|
|
{
|
|
my $self = shift;
|
|
|
|
my @result = @{$self->{'pending_footnotes'}};
|
|
@{$self->{'pending_footnotes'}} = ();
|
|
return @result;
|
|
}
|
|
|
|
|
|
# API to register, cancel and get inline content that should be output
|
|
# when in an inline situation, mostly in a paragraph or preformatted
|
|
sub register_pending_formatted_inline_content($$$)
|
|
{
|
|
my $self = shift;
|
|
my $category = shift;
|
|
my $inline_content = shift;
|
|
|
|
if (not defined($self->{'pending_inline_content'})) {
|
|
$self->{'pending_inline_content'} = [];
|
|
}
|
|
push @{$self->{'pending_inline_content'}}, [$category, $inline_content];
|
|
}
|
|
|
|
# cancel only the first pending content for the category
|
|
sub cancel_pending_formatted_inline_content($$$)
|
|
{
|
|
my $self = shift;
|
|
my $category = shift;
|
|
|
|
if (defined($self->{'pending_inline_content'})) {
|
|
my @other_category_contents = ();
|
|
while (@{$self->{'pending_inline_content'}}) {
|
|
my $category_inline_content = pop @{$self->{'pending_inline_content'}};
|
|
if ($category_inline_content->[0] eq $category) {
|
|
push @{$self->{'pending_inline_content'}}, @other_category_contents;
|
|
return $category_inline_content->[1];
|
|
}
|
|
unshift @other_category_contents, $category_inline_content;
|
|
}
|
|
push @{$self->{'pending_inline_content'}}, @other_category_contents;
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
sub get_pending_formatted_inline_content($) {
|
|
my $self = shift;
|
|
|
|
if (not defined($self->{'pending_inline_content'})) {
|
|
return '';
|
|
} else {
|
|
my $result = '';
|
|
foreach my $category_inline_content (@{$self->{'pending_inline_content'}}) {
|
|
if (defined($category_inline_content->[1])) {
|
|
$result .= $category_inline_content->[1];
|
|
}
|
|
}
|
|
$self->{'pending_inline_content'} = undef;
|
|
return $result;
|
|
}
|
|
}
|
|
|
|
# API to associate inline content to an element, typically
|
|
# paragraph or preformatted. Allows to associate the pending
|
|
# content to the first inline element.
|
|
sub associate_pending_formatted_inline_content($$$) {
|
|
my $self = shift;
|
|
my $element = shift;
|
|
my $inline_content = shift;
|
|
|
|
if (not $self->{'associated_inline_content'}->{$element}) {
|
|
$self->{'associated_inline_content'}->{$element} = '';
|
|
}
|
|
$self->{'associated_inline_content'}->{$element} .= $inline_content;
|
|
}
|
|
|
|
sub get_associated_formatted_inline_content($$) {
|
|
my $self = shift;
|
|
my $element = shift;
|
|
|
|
if ($self->{'associated_inline_content'}->{$element}) {
|
|
my $result = $self->{'associated_inline_content'}->{$element};
|
|
delete $self->{'associated_inline_content'}->{$element};
|
|
return $result;
|
|
}
|
|
return '';
|
|
}
|
|
|
|
# API to register an information to a file and get it. To be able to
|
|
# set an information during conversion and get it back during headers
|
|
# and footers conversion
|
|
sub register_file_information($$;$)
|
|
{
|
|
my $self = shift;
|
|
my $key = shift;
|
|
my $value = shift;
|
|
|
|
$self->{'files_information'}->{$self->{'current_filename'}} = {}
|
|
if (!$self->{'files_information'}->{$self->{'current_filename'}});
|
|
$self->{'files_information'}->{$self->{'current_filename'}}->{$key} = $value;
|
|
}
|
|
|
|
sub get_file_information($$;$)
|
|
{
|
|
my $self = shift;
|
|
my $key = shift;
|
|
my $filename = shift;
|
|
|
|
if (not defined($filename)) {
|
|
$filename = $self->{'current_filename'};
|
|
}
|
|
if (not defined($filename)
|
|
or not $self->{'files_information'}
|
|
or not $self->{'files_information'}->{$filename}
|
|
or not exists($self->{'files_information'}->{$filename}->{$key})) {
|
|
return (0, undef);
|
|
}
|
|
return (1, $self->{'files_information'}->{$filename}->{$key})
|
|
}
|
|
|
|
# information from converter available 'read-only', in general set up before
|
|
# really starting the formatting (except for current_filename).
|
|
# 'floats', 'global_commands' and 'structuring' are set up in the generic
|
|
# converter
|
|
my %available_converter_info;
|
|
foreach my $converter_info ('copying_comment', 'current_filename',
|
|
'destination_directory', 'document_name', 'documentdescription_string',
|
|
'floats', 'global_commands',
|
|
'index_entries', 'index_entries_by_letter', 'indices_information',
|
|
'jslicenses', 'line_break_element', 'non_breaking_space', 'paragraph_symbol',
|
|
'simpletitle_command_name', 'simpletitle_tree', 'structuring',
|
|
'title_string', 'title_tree', 'title_titlepage') {
|
|
$available_converter_info{$converter_info} = 1;
|
|
}
|
|
|
|
sub get_info($$)
|
|
{
|
|
my $self = shift;
|
|
my $converter_info = shift;
|
|
|
|
if (not $available_converter_info{$converter_info}) {
|
|
confess("BUG: $converter_info not an available converter info");
|
|
}
|
|
if (defined($self->{'converter_info'}->{$converter_info})) {
|
|
if (ref($self->{'converter_info'}->{$converter_info}) eq 'SCALAR') {
|
|
return ${$self->{'converter_info'}->{$converter_info}};
|
|
} else {
|
|
return $self->{'converter_info'}->{$converter_info};
|
|
}
|
|
#} else {
|
|
# cluck();
|
|
}
|
|
}
|
|
|
|
# This function should be used in formatting functions when some
|
|
# Texinfo tree need to be converted.
|
|
sub convert_tree_new_formatting_context($$;$$$)
|
|
{
|
|
my $self = shift;
|
|
my $tree = shift;
|
|
my $context_string = shift;
|
|
my $multiple_pass = shift;
|
|
my $document_global_context = shift;
|
|
|
|
my $context_string_str = '';
|
|
if (defined($context_string)) {
|
|
$self->_new_document_context($context_string, $document_global_context);
|
|
$context_string_str = "C($context_string)";
|
|
}
|
|
my $multiple_pass_str = '';
|
|
if ($multiple_pass) {
|
|
$self->{'ignore_notice'}++;
|
|
push @{$self->{'multiple_pass'}}, $multiple_pass;
|
|
$multiple_pass_str = '|M'
|
|
}
|
|
print STDERR "new_fmt_ctx ${context_string_str}${multiple_pass_str}\n"
|
|
if ($self->get_conf('DEBUG'));
|
|
my $result = $self->convert_tree($tree, "new_fmt_ctx ${context_string_str}");
|
|
if (defined($context_string)) {
|
|
$self->_pop_document_context();
|
|
}
|
|
if ($multiple_pass) {
|
|
$self->{'ignore_notice'}--;
|
|
pop @{$self->{'multiple_pass'}};
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
my %defaults = (
|
|
'AVOID_MENU_REDUNDANCY' => 0,
|
|
'BIG_RULE' => '<hr>',
|
|
'BODYTEXT' => undef,
|
|
'CHAPTER_HEADER_LEVEL' => 2,
|
|
'CLOSE_QUOTE_SYMBOL' => undef,
|
|
'CONTENTS_OUTPUT_LOCATION' => 'after_top',
|
|
'CONVERT_TO_LATEX_IN_MATH' => undef,
|
|
'COMPLEX_FORMAT_IN_TABLE' => 0,
|
|
'COPIABLE_LINKS' => 1,
|
|
'DATE_IN_HEADER' => 0,
|
|
'DEFAULT_RULE' => '<hr>',
|
|
'documentlanguage' => 'en',
|
|
'DOCTYPE' => '<!DOCTYPE html>',
|
|
'DO_ABOUT' => 0,
|
|
'OUTPUT_CHARACTERS' => 0,
|
|
'EXTENSION' => 'html',
|
|
'EXTERNAL_CROSSREF_EXTENSION' => undef, # based on EXTENSION
|
|
'FOOTNOTE_END_HEADER_LEVEL' => 4,
|
|
'FOOTNOTE_SEPARATE_HEADER_LEVEL' => 4,
|
|
'FORMAT_MENU' => 'sectiontoc',
|
|
'HEADERS' => 1,
|
|
'INDEX_ENTRY_COLON' => '',
|
|
# if set style is added in attribute.
|
|
'INLINE_CSS_STYLE' => 0,
|
|
'JS_WEBLABELS' => 'generate',
|
|
'JS_WEBLABELS_FILE' => 'js_licenses.html', # no clash with node name
|
|
'MAX_HEADER_LEVEL' => 4,
|
|
'MENU_ENTRY_COLON' => ':',
|
|
'MENU_SYMBOL' => undef,
|
|
'MONOLITHIC' => 1,
|
|
'NO_CUSTOM_HTML_ATTRIBUTE' => 0,
|
|
# if set, no css is used.
|
|
'NO_CSS' => 0,
|
|
'NO_NUMBER_FOOTNOTE_SYMBOL' => '*',
|
|
'NODE_NAME_IN_MENU' => 1,
|
|
'OPEN_QUOTE_SYMBOL' => undef,
|
|
'OUTPUT_ENCODING_NAME' => 'utf-8',
|
|
'SECTION_NAME_IN_TITLE' => 0,
|
|
'SHORT_TOC_LINK_TO_TOC' => 1,
|
|
'SHOW_TITLE' => undef,
|
|
'SPLIT' => 'node',
|
|
'TOP_FILE' => 'index.html', # ignores EXTENSION
|
|
'TOP_NODE_FILE_TARGET' => 'index.html', # ignores EXTENSION
|
|
'USE_ACCESSKEY' => 1,
|
|
'USE_ISO' => 1,
|
|
'USE_LINKS' => 1,
|
|
'USE_NODES' => 1,
|
|
'USE_NODE_DIRECTIONS' => undef,
|
|
'USE_REL_REV' => 1,
|
|
'USE_TITLEPAGE_FOR_TITLE' => 1,
|
|
'WORDS_IN_PAGE' => 300,
|
|
'XREF_USE_NODE_NAME_ARG' => undef,
|
|
'XREF_USE_FLOAT_LABEL' => 0,
|
|
'xrefautomaticsectiontitle' => 'on',
|
|
|
|
# obsolete
|
|
'FRAMESET_DOCTYPE' => '<!DOCTYPE html>',
|
|
|
|
# Non-string customization variables
|
|
# _default_panel_button_dynamic_direction use nodes direction based on USE_NODE_DIRECTIONS
|
|
# or USE_NODES if USE_NODE_DIRECTIONS is undefined
|
|
'SECTION_BUTTONS' => [[ 'Next', \&_default_panel_button_dynamic_direction ],
|
|
[ 'Prev', \&_default_panel_button_dynamic_direction ],
|
|
[ 'Up', \&_default_panel_button_dynamic_direction ], ' ',
|
|
'Contents', 'Index', 'About'],
|
|
'SECTION_FOOTER_BUTTONS' => [[ 'Next', \&_default_panel_button_dynamic_direction_section_footer ],
|
|
[ 'Prev', \&_default_panel_button_dynamic_direction_section_footer ],
|
|
[ 'Up', \&_default_panel_button_dynamic_direction_section_footer ], ' ',
|
|
'Contents', 'Index'],
|
|
'LINKS_BUTTONS' => ['Top', 'Index', 'Contents', 'About',
|
|
'NodeUp', 'NodeNext', 'NodePrev'],
|
|
'NODE_FOOTER_BUTTONS' => [[ 'Next', \&_default_panel_button_dynamic_direction_node_footer ],
|
|
[ 'Prev', \&_default_panel_button_dynamic_direction_node_footer ],
|
|
[ 'Up', \&_default_panel_button_dynamic_direction_node_footer ],
|
|
' ', 'Contents', 'Index'],
|
|
'ACTIVE_ICONS' => undef,
|
|
'PASSIVE_ICONS' => undef,
|
|
# obsolete
|
|
'frame_pages_file_string' => {
|
|
'Frame' => '_frame',
|
|
'Toc_Frame' => '_toc_frame',
|
|
},
|
|
|
|
# non-customization variable converter defaults
|
|
'converted_format' => 'html',
|
|
);
|
|
|
|
foreach my $buttons ('CHAPTER_BUTTONS', 'MISC_BUTTONS', 'TOP_BUTTONS') {
|
|
$defaults{$buttons} = [@{$defaults{'SECTION_BUTTONS'}}];
|
|
}
|
|
|
|
foreach my $buttons ('CHAPTER_FOOTER_BUTTONS') {
|
|
$defaults{$buttons} = [@{$defaults{'SECTION_FOOTER_BUTTONS'}}];
|
|
}
|
|
|
|
|
|
my %default_special_element_info = (
|
|
|
|
'class' => {
|
|
'about' => 'about',
|
|
'contents' => 'contents',
|
|
'shortcontents' => 'shortcontents',
|
|
'footnotes' => 'footnotes',
|
|
},
|
|
|
|
'direction' => {
|
|
'about' => 'About',
|
|
'contents' => 'Contents',
|
|
'shortcontents' => 'Overview',
|
|
'footnotes' => 'Footnotes',
|
|
},
|
|
|
|
'order' => {
|
|
'contents' => 30,
|
|
'shortcontents' => 20,
|
|
'footnotes' => 10,
|
|
'about' => 40,
|
|
},
|
|
|
|
'file_string' => {
|
|
'contents' => '_toc',
|
|
'shortcontents' => '_ovr',
|
|
'footnotes' => '_fot',
|
|
'about' => '_abt',
|
|
},
|
|
|
|
'target' => {
|
|
'shortcontents' => 'SEC_Shortcontents',
|
|
'contents' => 'SEC_Contents',
|
|
'footnotes' => 'SEC_Footnotes',
|
|
'about' => 'SEC_About',
|
|
},
|
|
);
|
|
|
|
# translation context should be consistent with special_element_info()
|
|
%default_translated_special_element_info = (
|
|
|
|
'heading' => {
|
|
'about' => Texinfo::Common::pgdt('about section heading',
|
|
'About This Document'),
|
|
'contents' => Texinfo::Common::pgdt('contents section heading',
|
|
'Table of Contents'),
|
|
'shortcontents' => Texinfo::Common::pgdt(
|
|
'shortcontents section heading',
|
|
'Short Table of Contents'),
|
|
'footnotes' => Texinfo::Common::pgdt('footnotes section heading',
|
|
'Footnotes'),
|
|
},
|
|
);
|
|
|
|
my @global_directions = ('First', 'Last', 'Index', 'Top');
|
|
my %global_and_special_directions;
|
|
foreach my $global_direction (@global_directions) {
|
|
$global_and_special_directions{$global_direction} = 1;
|
|
}
|
|
foreach my $special_direction (values(
|
|
%{$default_special_element_info{'direction'}})) {
|
|
$global_and_special_directions{$special_direction} = 1;
|
|
}
|
|
|
|
my %default_converted_directions_strings = (
|
|
|
|
# see http://www.w3.org/TR/REC-html40/types.html#type-links
|
|
'rel' =>
|
|
{
|
|
'Top', 'start',
|
|
'Contents', 'contents',
|
|
'Overview', '',
|
|
'Index', 'index',
|
|
'This', '',
|
|
'Back', 'prev',
|
|
'FastBack', '',
|
|
'Prev', 'prev',
|
|
'Up', 'up',
|
|
'Next', 'next',
|
|
'NodeUp', 'up',
|
|
'NodeNext', 'next',
|
|
'NodePrev', 'prev',
|
|
'NodeForward', '',
|
|
'NodeBack', '',
|
|
'Forward', 'next',
|
|
'FastForward', '',
|
|
'About' , 'help',
|
|
'First', '',
|
|
'Last', '',
|
|
'NextFile', 'next',
|
|
'PrevFile', 'prev',
|
|
},
|
|
|
|
'accesskey' =>
|
|
{
|
|
'Top', '',
|
|
'Contents', '',
|
|
'Overview', '',
|
|
'Index', '',
|
|
'This', '',
|
|
'Back', 'p',
|
|
'FastBack', '',
|
|
'Prev', 'p',
|
|
'Up', 'u',
|
|
'Next', 'n',
|
|
'NodeUp', 'u',
|
|
'NodeNext', 'n',
|
|
'NodePrev', 'p',
|
|
'NodeForward', '',
|
|
'NodeBack', '',
|
|
'Forward', 'n',
|
|
'FastForward', '',
|
|
'About' , '',
|
|
'First', '',
|
|
'Last', '',
|
|
'NextFile', '',
|
|
'PrevFile', '',
|
|
},
|
|
|
|
'example' =>
|
|
{
|
|
'Top', ' '.$html_default_entity_nbsp.' ',
|
|
'Contents', ' '.$html_default_entity_nbsp.' ',
|
|
'Overview', ' '.$html_default_entity_nbsp.' ',
|
|
'Index', ' '.$html_default_entity_nbsp.' ',
|
|
'This', '1.2.3',
|
|
'Back', '1.2.2',
|
|
'FastBack', '1',
|
|
'Prev', '1.2.2',
|
|
'Up', '1.2',
|
|
'Next', '1.2.4',
|
|
'NodeUp', '1.2',
|
|
'NodeNext', '1.2.4',
|
|
'NodePrev', '1.2.2',
|
|
'NodeForward', '1.2.4',
|
|
'NodeBack', '1.2.2',
|
|
'Forward', '1.2.4',
|
|
'FastForward', '2',
|
|
'About', ' '.$html_default_entity_nbsp.' ',
|
|
'First', '1.',
|
|
'Last', '1.2.4',
|
|
'NextFile', ' '.$html_default_entity_nbsp.' ',
|
|
'PrevFile', ' '.$html_default_entity_nbsp.' ',
|
|
},
|
|
);
|
|
|
|
# translation contexts should be consistent with
|
|
# %direction_type_translation_context. If the direction is not used
|
|
# as is, it should also be taken into account in direction_string().
|
|
# For now 'This' becomes 'This (current section)'.
|
|
my %default_translated_directions_strings = (
|
|
'text' => {
|
|
' ' => {'converted' => ' '.$html_default_entity_nbsp.' '},
|
|
'Top' => {'to_convert'
|
|
=> Texinfo::Common::pgdt('Top direction string', 'Top')},
|
|
'Contents' => {'to_convert'
|
|
=> Texinfo::Common::pgdt('Contents direction string', 'Contents')},
|
|
'Overview' => {'to_convert'
|
|
=> Texinfo::Common::pgdt(
|
|
'Overview direction string', 'Overview')},
|
|
'Index' => {'to_convert'
|
|
=> Texinfo::Common::pgdt('Index direction string', 'Index')},
|
|
'This' => {'to_convert'
|
|
=> Texinfo::Common::pgdt('This (current section) direction string',
|
|
'current')},
|
|
'Back' => {'converted' => ' < '},
|
|
'FastBack' => {'converted' => ' << '},
|
|
'Prev' => {'to_convert'
|
|
=> Texinfo::Common::pgdt('Prev direction string', 'Prev')},
|
|
'Up' => {'to_convert'
|
|
=> Texinfo::Common::pgdt('Up direction string', ' Up ')},
|
|
'Next' => {'to_convert'
|
|
=> Texinfo::Common::pgdt('Next direction string', 'Next')},
|
|
'NodeUp' => {'to_convert'
|
|
=> Texinfo::Common::pgdt('NodeUp direction string', 'Up')},
|
|
'NodeNext' => {'to_convert'
|
|
=> Texinfo::Common::pgdt('NodeNext direction string', 'Next')},
|
|
'NodePrev' => {'to_convert'
|
|
=> Texinfo::Common::pgdt('NodePrev direction string', 'Previous')},
|
|
'NodeForward' => {'to_convert'
|
|
=> Texinfo::Common::pgdt('NodeForward direction string', 'Forward node')},
|
|
'NodeBack' => {'to_convert'
|
|
=> Texinfo::Common::pgdt('NodeBack direction string', 'Back node')},
|
|
'Forward' => {'converted' => ' > '},
|
|
'FastForward' => {'converted' => ' >> '},
|
|
'About' => {'converted' => ' ? '},
|
|
'First' => {'converted' => ' |< '},
|
|
'Last' => {'converted' => ' >| '},
|
|
'NextFile' => {'to_convert'
|
|
=> Texinfo::Common::pgdt('NextFile direction string', 'Next file')},
|
|
'PrevFile' => {'to_convert'
|
|
=> Texinfo::Common::pgdt('PrevFile direction string', 'Previous file')},
|
|
},
|
|
|
|
'description' => {
|
|
'Top' => {'to_convert' => Texinfo::Common::pgdt(
|
|
'Top direction description', 'Cover (top) of document')},
|
|
'Contents' => {'to_convert' => Texinfo::Common::pgdt(
|
|
'Contents direction description',
|
|
'Table of contents')},
|
|
'Overview' => {'to_convert' => Texinfo::Common::pgdt(
|
|
'Overview direction description',
|
|
'Short table of contents')},
|
|
'Index' => {'to_convert' => Texinfo::Common::pgdt(
|
|
'Index direction description', 'Index')},
|
|
'This' => {'to_convert' => Texinfo::Common::pgdt(
|
|
'This (current section) direction description',
|
|
'Current section')},
|
|
'Back' => {'to_convert' => Texinfo::Common::pgdt(
|
|
'Back direction description',
|
|
'Previous section in reading order')},
|
|
'FastBack' => {'to_convert' => Texinfo::Common::pgdt(
|
|
'FastBack direction description',
|
|
'Beginning of this chapter or previous chapter')},
|
|
'Prev' => {'to_convert' => Texinfo::Common::pgdt(
|
|
'Prev direction description',
|
|
'Previous section on same level')},
|
|
'Up' => {'to_convert' => Texinfo::Common::pgdt(
|
|
'Up direction description', 'Up section')},
|
|
'Next' => {'to_convert' => Texinfo::Common::pgdt(
|
|
'Next direction description', 'Next section on same level')},
|
|
'NodeUp' => {'to_convert' => Texinfo::Common::pgdt(
|
|
'NodeUp direction description', 'Up node')},
|
|
'NodeNext' => {'to_convert' => Texinfo::Common::pgdt(
|
|
'NodeNext direction description', 'Next node')},
|
|
'NodePrev' => {'to_convert' => Texinfo::Common::pgdt(
|
|
'NodePrev direction description', 'Previous node')},
|
|
'NodeForward' => {'to_convert' => Texinfo::Common::pgdt(
|
|
'NodeForward direction description',
|
|
'Next node in node reading order')},
|
|
'NodeBack' => {'to_convert' => Texinfo::Common::pgdt(
|
|
'NodeBack direction description',
|
|
'Previous node in node reading order')},
|
|
'Forward' => {'to_convert' => Texinfo::Common::pgdt(
|
|
'Forward direction description',
|
|
'Next section in reading order')},
|
|
'FastForward' => {'to_convert' => Texinfo::Common::pgdt(
|
|
'FastForward direction description', 'Next chapter')},
|
|
'About' => {'to_convert' => Texinfo::Common::pgdt(
|
|
'About direction description', 'About (help)')},
|
|
'First' => {'to_convert' => Texinfo::Common::pgdt(
|
|
'First direction description',
|
|
'First section in reading order')},
|
|
'Last' => {'to_convert' => Texinfo::Common::pgdt(
|
|
'Last direction description',
|
|
'Last section in reading order')},
|
|
'NextFile' => {'to_convert' => Texinfo::Common::pgdt(
|
|
'NextFile direction description',
|
|
'Forward section in next file')},
|
|
'PrevFile' => {'to_convert' => Texinfo::Common::pgdt(
|
|
'PrevFile direction description',
|
|
'Back section in previous file')},
|
|
},
|
|
|
|
'button' => {
|
|
' ' => {'converted' => ' '},
|
|
'Top' => {'to_convert'
|
|
=> Texinfo::Common::pgdt('Top direction button label', 'Top')},
|
|
'Contents' => {'to_convert'
|
|
=> Texinfo::Common::pgdt('Contents direction button label', 'Contents')},
|
|
'Overview' => {'to_convert'
|
|
=> Texinfo::Common::pgdt('Overview direction button label', 'Overview')},
|
|
'Index' => {'to_convert'
|
|
=> Texinfo::Common::pgdt('Index direction button label', 'Index')},
|
|
'This' => {'to_convert'
|
|
=> Texinfo::Common::pgdt('This direction button label', 'This')},
|
|
'Back' => {'to_convert'
|
|
=> Texinfo::Common::pgdt('Back direction button label', 'Back')},
|
|
'FastBack' => {'to_convert'
|
|
=> Texinfo::Common::pgdt('FastBack direction button label', 'FastBack')},
|
|
'Prev' => {'to_convert'
|
|
=> Texinfo::Common::pgdt('Prev direction button label', 'Prev')},
|
|
'Up' => {'to_convert'
|
|
=> Texinfo::Common::pgdt('Up direction button label', 'Up')},
|
|
'Next' => {'to_convert'
|
|
=> Texinfo::Common::pgdt('Next direction button label', 'Next')},
|
|
'NodeUp' => {'to_convert'
|
|
=> Texinfo::Common::pgdt('NodeUp direction button label', 'NodeUp')},
|
|
'NodeNext' => {'to_convert'
|
|
=> Texinfo::Common::pgdt('NodeNext direction button label', 'NodeNext')},
|
|
'NodePrev' => {'to_convert'
|
|
=> Texinfo::Common::pgdt('NodePrev direction button label', 'NodePrev')},
|
|
'NodeForward' => {'to_convert'
|
|
=> Texinfo::Common::pgdt('NodeForward direction button label', 'NodeForward')},
|
|
'NodeBack' => {'to_convert'
|
|
=> Texinfo::Common::pgdt('NodeBack direction button label', 'NodeBack')},
|
|
'Forward' => {'to_convert'
|
|
=> Texinfo::Common::pgdt('Forward direction button label', 'Forward')},
|
|
'FastForward' => {'to_convert'
|
|
=> Texinfo::Common::pgdt('FastForward direction button label', 'FastForward')},
|
|
'About' => {'to_convert'
|
|
=> Texinfo::Common::pgdt('About direction button label', 'About')},
|
|
'First' => {'to_convert'
|
|
=> Texinfo::Common::pgdt('First direction button label', 'First')},
|
|
'Last' => {'to_convert'
|
|
=> Texinfo::Common::pgdt('Last direction button label', 'Last')},
|
|
'NextFile' => {'to_convert'
|
|
=> Texinfo::Common::pgdt('NextFile direction button label', 'NextFile')},
|
|
'PrevFile' => {'to_convert'
|
|
=> Texinfo::Common::pgdt('PrevFile direction button label', 'PrevFile')},
|
|
}
|
|
);
|
|
|
|
sub _translate_names($)
|
|
{
|
|
my $self = shift;
|
|
print STDERR "\nTRANSLATE_NAMES encoding_name: "
|
|
.$self->get_conf('OUTPUT_ENCODING_NAME')
|
|
." documentlanguage: ".$self->get_conf('documentlanguage')."\n"
|
|
if ($self->get_conf('DEBUG'));
|
|
|
|
# reset strings such that they are translated when needed.
|
|
foreach my $string_type (keys(%default_translated_directions_strings)) {
|
|
$self->{'directions_strings'}->{$string_type} = {};
|
|
}
|
|
|
|
# could also use keys of $self->{'translated_special_element_info'}
|
|
foreach my $type (keys(%default_translated_special_element_info)) {
|
|
$self->{'special_element_info'}->{$type.'_tree'} = {};
|
|
}
|
|
|
|
# delete the tree and formatted results for special elements
|
|
# such that they are redone with the new tree when needed.
|
|
foreach my $special_element_variety ($self->special_element_info('direction')) {
|
|
my $special_element_direction
|
|
= $self->special_element_info('direction', $special_element_variety);
|
|
my $special_element
|
|
= $self->special_direction_element($special_element_direction);
|
|
if ($special_element and
|
|
$self->{'targets'}->{$special_element}) {
|
|
my $target = $self->{'targets'}->{$special_element};
|
|
foreach my $key ('text', 'string', 'tree') {
|
|
delete $target->{$key};
|
|
}
|
|
}
|
|
}
|
|
my %translated_commands;
|
|
foreach my $context ('normal', 'preformatted', 'string', 'css_string') {
|
|
foreach my $command (keys(%{$self->{'no_arg_commands_formatting'}
|
|
->{$context}})) {
|
|
if (defined($self->{'no_arg_commands_formatting'}
|
|
->{$context}->{$command}->{'translated_converted'})
|
|
and not $self->{'no_arg_commands_formatting'}
|
|
->{$context}->{$command}->{'unset'}) {
|
|
$translated_commands{$command} = 1;
|
|
$self->{'no_arg_commands_formatting'}->{$context}->{$command}->{'text'}
|
|
= $self->gdt($self->{'no_arg_commands_formatting'}
|
|
->{$context}->{$command}->{'translated_converted'},
|
|
undef, undef, 'translated_text');
|
|
} elsif ($context eq 'normal') {
|
|
my $translated_tree;
|
|
if (defined($self->{'no_arg_commands_formatting'}
|
|
->{$context}->{$command}->{'translated_to_convert'})) {
|
|
$translated_tree = $self->gdt($self->{'no_arg_commands_formatting'}
|
|
->{$context}->{$command}->{'translated_to_convert'});
|
|
} else {
|
|
# default translated commands
|
|
$translated_tree
|
|
= Texinfo::Convert::Utils::translated_command_tree($self, $command);
|
|
}
|
|
if (defined($translated_tree) and $translated_tree ne '') {
|
|
$self->{'no_arg_commands_formatting'}->{$context}->{$command}->{'tree'}
|
|
= $translated_tree;
|
|
$translated_commands{$command} = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
foreach my $command (keys(%translated_commands)) {
|
|
$self->_complete_no_arg_commands_formatting($command, 1);
|
|
}
|
|
|
|
print STDERR "END TRANSLATE_NAMES\n\n" if ($self->get_conf('DEBUG'));
|
|
}
|
|
|
|
# redefined functions
|
|
#
|
|
# Texinfo::Translations::gdt redefined to call user defined function.
|
|
sub gdt($$;$$$$)
|
|
{
|
|
my ($self, $message, $replaced_substrings, $message_context, $type, $lang) = @_;
|
|
if (defined($self->{'formatting_function'}->{'format_translate_string'})) {
|
|
my $format_lang = $lang;
|
|
$format_lang = $self->get_conf('documentlanguage')
|
|
if ($self and !defined($format_lang));
|
|
my $translated_string
|
|
= &{$self->{'formatting_function'}->{'format_translate_string'}}($self,
|
|
$message, $format_lang, $replaced_substrings,
|
|
$message_context, $type);
|
|
if (defined($translated_string)) {
|
|
return $translated_string;
|
|
}
|
|
}
|
|
return $self->SUPER::gdt($message, $replaced_substrings, $message_context,
|
|
$type, $lang);
|
|
}
|
|
|
|
|
|
sub converter_defaults($$)
|
|
{
|
|
my $self = shift;
|
|
my $conf = shift;
|
|
if ($conf and defined($conf->{'TEXI2HTML'})) {
|
|
my $default_ref = { %defaults };
|
|
my %texi2html_defaults = %$default_ref;
|
|
_set_variables_texi2html(\%texi2html_defaults);
|
|
return %texi2html_defaults;
|
|
}
|
|
return %defaults;
|
|
}
|
|
|
|
my %css_element_class_styles = (
|
|
%css_rules_not_collected,
|
|
|
|
'ul.toc-numbered-mark' => 'list-style: none',
|
|
'pre.menu-comment-preformatted' => 'font-family: serif',
|
|
# using display: inline is an attempt to avoid a line break when in
|
|
# preformatted in menu. In 2022 it does not seems to work in firefox,
|
|
# there is still a line break.
|
|
'pre.menu-entry-description-preformatted' => 'font-family: serif; display: inline',
|
|
'pre.menu-preformatted' => 'font-family: serif',
|
|
'a.summary-letter-printindex' => 'text-decoration: none',
|
|
'pre.display-preformatted' => 'font-family: inherit',
|
|
'span.program-in-footer' => 'font-size: smaller', # used with PROGRAM_NAME_IN_FOOTER
|
|
'span.sansserif' => 'font-family: sans-serif; font-weight: normal',
|
|
'span.r' => 'font-family: initial; font-weight: normal; font-style: normal',
|
|
'td.index-entry-level-1' => 'padding-left: 1.5em',
|
|
'td.index-entry-level-2' => 'padding-left: 3.0em',
|
|
'kbd.key' => 'font-style: normal',
|
|
'kbd.kbd' => 'font-style: oblique',
|
|
'strong.def-name' => 'font-family: monospace; font-weight: bold; '
|
|
.'font-size: larger',
|
|
'p.flushleft-paragraph' => 'text-align:left',
|
|
'p.flushright-paragraph' => 'text-align:right',
|
|
'h1.centerchap' => 'text-align:center',
|
|
'h2.centerchap' => 'text-align:center',
|
|
'h3.centerchap' => 'text-align:center',
|
|
'h1.settitle' => 'text-align:center',
|
|
'h1.shorttitlepage' => 'text-align:center',
|
|
'h3.subtitle' => 'text-align:right',
|
|
'h4.centerchap' => 'text-align:center',
|
|
'div.center' => 'text-align:center',
|
|
'blockquote.indentedblock' => 'margin-right: 0em',
|
|
'td.printindex-index-entry' => 'vertical-align: top',
|
|
'td.printindex-index-section' => 'vertical-align: top; padding-left: 1em',
|
|
'td.printindex-index-see-also' => 'vertical-align: top; padding-left: 1em',
|
|
'td.menu-entry-destination' => 'vertical-align: top',
|
|
'td.menu-entry-description' => 'vertical-align: top',
|
|
'th.entries-header-printindex' => 'text-align:left',
|
|
'th.sections-header-printindex' => 'text-align:left; padding-left: 1em',
|
|
'th.menu-comment' => 'text-align:left',
|
|
'td.category-def' => 'text-align:right',
|
|
'td.call-def' => 'text-align:left',
|
|
'td.button-direction-about' => 'text-align:center',
|
|
'td.name-direction-about' => 'text-align:center',
|
|
|
|
# The anchor element is wrapped in a <span> rather than a block level
|
|
# element to avoid it appearing unless the mouse pointer is directly
|
|
# over the text, as it is annoying for anchors to flicker when
|
|
# you are moving your pointer elsewhere. "line-height: 0em" stops the
|
|
# invisible text from changing vertical spacing.
|
|
'a.copiable-link' => 'visibility: hidden; '
|
|
.'text-decoration: none; line-height: 0em',
|
|
'span:hover a.copiable-link' => 'visibility: visible',
|
|
);
|
|
|
|
$css_element_class_styles{'pre.format-preformatted'}
|
|
= $css_element_class_styles{'pre.display-preformatted'};
|
|
|
|
my %preformatted_commands_context = %preformatted_commands;
|
|
$preformatted_commands_context{'verbatim'} = 1;
|
|
|
|
my %pre_class_commands;
|
|
foreach my $preformatted_command (keys(%preformatted_commands_context)) {
|
|
# no class for the @small* variants
|
|
if ($small_block_associated_command{$preformatted_command}) {
|
|
$pre_class_commands{$preformatted_command}
|
|
= $small_block_associated_command{$preformatted_command};
|
|
} else {
|
|
$pre_class_commands{$preformatted_command} = $preformatted_command;
|
|
}
|
|
}
|
|
$pre_class_commands{'menu'} = 'menu';
|
|
|
|
my %default_pre_class_types;
|
|
$default_pre_class_types{'menu_comment'} = 'menu-comment';
|
|
|
|
my %indented_preformatted_commands;
|
|
foreach my $indented_format ('example', 'display', 'lisp') {
|
|
$indented_preformatted_commands{$indented_format} = 1;
|
|
$indented_preformatted_commands{"small$indented_format"} = 1;
|
|
|
|
$css_element_class_styles{"div.$indented_format"} = 'margin-left: 3.2em';
|
|
}
|
|
delete $css_element_class_styles{"div.lisp"}; # output as div.example instead
|
|
|
|
# types that are in code style in the default case. '_code' is not
|
|
# a type that can appear in the tree built from Texinfo code, it is used
|
|
# to format a tree fragment as if it was in a @code @-command.
|
|
my %default_code_types = (
|
|
'_code' => 1,
|
|
);
|
|
|
|
# specification of arguments formatting
|
|
my %default_commands_args = (
|
|
'anchor' => [['monospacestring']],
|
|
'email' => [['url', 'monospacestring'], ['normal']],
|
|
'footnote' => [[]],
|
|
'printindex' => [[]],
|
|
'uref' => [['url', 'monospacestring'], ['normal'], ['normal']],
|
|
'url' => [['url', 'monospacestring'], ['normal'], ['normal']],
|
|
'sp' => [[]],
|
|
'inforef' => [['monospace'],['normal'],['filenametext']],
|
|
'xref' => [['monospace'],['normal'],['normal'],['filenametext'],['normal']],
|
|
'pxref' => [['monospace'],['normal'],['normal'],['filenametext'],['normal']],
|
|
'ref' => [['monospace'],['normal'],['normal'],['filenametext'],['normal']],
|
|
'link' => [['monospace'],['normal'],['filenametext']],
|
|
'image' => [['url', 'filenametext', 'monospacestring'],['filenametext'],['filenametext'],['string', 'normal'],['filenametext']],
|
|
# FIXME shouldn't it better not to convert if later ignored?
|
|
# note that right now ignored argument are in elided empty types
|
|
# but this could change.
|
|
'inlinefmt' => [['monospacetext'],['normal']],
|
|
'inlinefmtifelse' => [['monospacetext'],['normal'],['normal']],
|
|
'inlineraw' => [['monospacetext'],['raw']],
|
|
'inlineifclear' => [['monospacetext'],['normal']],
|
|
'inlineifset' => [['monospacetext'],['normal']],
|
|
'item' => [[]],
|
|
'itemx' => [[]],
|
|
'value' => [['monospacestring']],
|
|
);
|
|
|
|
foreach my $explained_command (keys(%explained_commands)) {
|
|
$default_commands_args{$explained_command}
|
|
= [['normal'], ['string']];
|
|
}
|
|
|
|
# intercept warning and error messages to take 'ignore_notice' into
|
|
# account
|
|
sub _noticed_line_warn($$$)
|
|
{
|
|
my $self = shift;
|
|
my $text = shift;
|
|
my $line_nr = shift;
|
|
return if ($self->{'ignore_notice'});
|
|
return $self->line_warn($self, $text, $line_nr);
|
|
}
|
|
|
|
my %kept_line_commands;
|
|
|
|
# TODO add the possibility to customize to add more commands to
|
|
# @informative_global_commands?
|
|
my @informative_global_commands = ('documentlanguage', 'footnotestyle',
|
|
'xrefautomaticsectiontitle', 'deftypefnnewline');
|
|
|
|
my @contents_commands = ('contents', 'shortcontents', 'summarycontents');
|
|
|
|
foreach my $line_command (@informative_global_commands,
|
|
@contents_commands, keys(%formattable_line_commands),
|
|
keys(%formatted_line_commands),
|
|
keys(%default_index_commands)) {
|
|
$kept_line_commands{$line_command} = 1;
|
|
}
|
|
|
|
foreach my $line_command (keys(%line_commands)) {
|
|
$default_commands_conversion{$line_command} = undef
|
|
unless ($kept_line_commands{$line_command});
|
|
}
|
|
|
|
foreach my $nobrace_command (keys(%nobrace_commands)) {
|
|
$default_commands_conversion{$nobrace_command} = undef
|
|
unless ($formatted_nobrace_commands{$nobrace_command});
|
|
}
|
|
|
|
# formatted/formattable @-commands that are not converted in
|
|
# HTML in the default case.
|
|
$default_commands_conversion{'page'} = undef;
|
|
$default_commands_conversion{'need'} = undef;
|
|
$default_commands_conversion{'vskip'} = undef;
|
|
|
|
foreach my $ignored_brace_commands ('caption', 'shortcaption',
|
|
'hyphenation', 'sortas') {
|
|
$default_commands_conversion{$ignored_brace_commands} = undef;
|
|
}
|
|
|
|
foreach my $ignored_block_commands ('ignore', 'macro', 'rmacro', 'linemacro',
|
|
'copying', 'documentdescription', 'titlepage', 'direntry') {
|
|
$default_commands_conversion{$ignored_block_commands} = undef;
|
|
};
|
|
|
|
# Formatting of commands without args
|
|
|
|
# The hash holding the defaults for the formatting of
|
|
# most commands without args. It has three contexts as keys,
|
|
# 'normal' in normal text, 'preformatted' in @example and similar
|
|
# commands, and 'string' for contexts where HTML elements should not
|
|
# be used.
|
|
my %default_no_arg_commands_formatting = (
|
|
'normal' => {},
|
|
'preformatted' => {},
|
|
'string' => {},
|
|
# more internal
|
|
'css_string' => {},
|
|
);
|
|
|
|
foreach my $command (keys(%Texinfo::Convert::Converter::xml_text_entity_no_arg_commands_formatting)) {
|
|
$default_no_arg_commands_formatting{'normal'}->{$command} =
|
|
{'text' =>
|
|
$Texinfo::Convert::Converter::xml_text_entity_no_arg_commands_formatting{
|
|
$command}};
|
|
}
|
|
|
|
$default_no_arg_commands_formatting{'normal'}->{' '} = {'text' => ' '};
|
|
$default_no_arg_commands_formatting{'normal'}->{"\t"} = {'text' => ' '};
|
|
$default_no_arg_commands_formatting{'normal'}->{"\n"} = {'text' => ' '};
|
|
|
|
# possible example of use, right now not used, as
|
|
# the generic Converter customization is directly used through
|
|
# the call to Texinfo::Convert::Utils::translated_command_tree().
|
|
#$default_no_arg_commands_formatting{'normal'}->{'error'}->{'translated_converted'} = 'error-->';
|
|
## This is used to have gettext pick up the chain to be translated
|
|
#if (0) {
|
|
# my $not_existing;
|
|
# $not_existing->gdt('error-->');
|
|
#}
|
|
|
|
$default_no_arg_commands_formatting{'normal'}->{'enddots'}
|
|
= {'element' => 'small', 'text' => '...'};
|
|
$default_no_arg_commands_formatting{'preformatted'}->{'dots'}
|
|
= {'text' => '...'};
|
|
$default_no_arg_commands_formatting{'preformatted'}->{'enddots'}
|
|
= {'text' => '...'};
|
|
$default_no_arg_commands_formatting{'normal'}->{'*'} = {'text' => '<br>'};
|
|
# this is used in math too, not sure that it is the best
|
|
# in that context, '<br>' could be better.
|
|
$default_no_arg_commands_formatting{'preformatted'}->{'*'} = {'text' => "\n"};
|
|
|
|
# escaped code points in CSS
|
|
# https://www.w3.org/TR/css-syntax/#consume-escaped-code-point
|
|
# Consume as many hex digits as possible, but no more than 5. Note that this means 1-6 hex digits have been consumed in total. If the next input code point is whitespace, consume it as well. Interpret the hex digits as a hexadecimal number.
|
|
# Note that in style= HTML attributes entities are used to
|
|
# protect CSS strings. For example, the CSS string a'b"
|
|
# is protected as CSS as a\'b", and " is escaped in an HTML style
|
|
# attribute: style="list-style-type: 'a\'b"'"
|
|
|
|
foreach my $no_brace_command (keys(%nobrace_symbol_text)) {
|
|
$default_no_arg_commands_formatting{'css_string'}->{$no_brace_command}
|
|
= {'text' => $nobrace_symbol_text{$no_brace_command}};
|
|
}
|
|
|
|
foreach my $command (keys(%{$default_no_arg_commands_formatting{'normal'}})) {
|
|
if (defined($Texinfo::Convert::Unicode::unicode_map{$command})
|
|
and $Texinfo::Convert::Unicode::unicode_map{$command} ne '') {
|
|
my $char_nr = hex($Texinfo::Convert::Unicode::unicode_map{$command});
|
|
my $css_string;
|
|
if ($char_nr < 128) { # 7bit ascii
|
|
$css_string = chr($char_nr);
|
|
} else {
|
|
$css_string = "\\$Texinfo::Convert::Unicode::unicode_map{$command} ";
|
|
}
|
|
$default_no_arg_commands_formatting{'css_string'}->{$command}
|
|
= {'text' => $css_string};
|
|
} elsif ($default_no_arg_commands_formatting{'preformatted'}->{$command}) {
|
|
$default_no_arg_commands_formatting{'css_string'}->{$command}
|
|
= {'text'
|
|
=> $default_no_arg_commands_formatting{'preformatted'}->{$command}->{'text'}};
|
|
} elsif ($default_no_arg_commands_formatting{'normal'}->{$command}->{'text'}) {
|
|
$default_no_arg_commands_formatting{'css_string'}->{$command}
|
|
= {'text' => $default_no_arg_commands_formatting{'normal'}->{$command}->{'text'}};
|
|
} elsif (exists($nobrace_symbol_text{$command})
|
|
and $nobrace_symbol_text{$command} eq '') {
|
|
# @- @/ @/ @|
|
|
$default_no_arg_commands_formatting{'css_string'}->{$command}
|
|
= {'text' => ''};
|
|
} else {
|
|
warn "BUG: $command: no css_string\n";
|
|
}
|
|
}
|
|
|
|
# remove to force using only translations (as the command
|
|
# is in the default converter translated commands)
|
|
delete $default_no_arg_commands_formatting{'css_string'}->{'error'};
|
|
|
|
$default_no_arg_commands_formatting{'css_string'}->{'*'}->{'text'} = '\A ';
|
|
|
|
$default_no_arg_commands_formatting{'css_string'}->{' '}->{'text'} = ' ';
|
|
$default_no_arg_commands_formatting{'css_string'}->{"\t"}->{'text'} = ' ';
|
|
$default_no_arg_commands_formatting{'css_string'}->{"\n"}->{'text'} = ' ';
|
|
$default_no_arg_commands_formatting{'css_string'}->{'tie'}->{'text'} = ' ';
|
|
|
|
# w not in css_string, set the corresponding css_element_class_styles
|
|
# especially, which also has none and not w in the class
|
|
$css_element_class_styles{'ul.mark-none'} = 'list-style-type: none';
|
|
|
|
# setup css_element_class_styles for mark commands based on css strings
|
|
foreach my $mark_command (keys(%{$default_no_arg_commands_formatting{'css_string'}})) {
|
|
if (defined($brace_commands{$mark_command})) {
|
|
my $css_string;
|
|
if ($mark_command eq 'bullet') {
|
|
$css_string = 'disc';
|
|
} elsif ($default_no_arg_commands_formatting{'css_string'}->{$mark_command}
|
|
and $default_no_arg_commands_formatting{'css_string'}
|
|
->{$mark_command}->{'text'}) {
|
|
if ($special_list_mark_css_string_no_arg_command{$mark_command}) {
|
|
$css_string = $special_list_mark_css_string_no_arg_command{$mark_command};
|
|
} else {
|
|
$css_string
|
|
= $default_no_arg_commands_formatting{'css_string'}
|
|
->{$mark_command}->{'text'};
|
|
}
|
|
$css_string =~ s/^(\\[A-Z0-9]+) $/$1/;
|
|
$css_string = '"'.$css_string.'"';
|
|
}
|
|
if (defined($css_string)) {
|
|
$css_element_class_styles{"ul.mark-$mark_command"}
|
|
= "list-style-type: $css_string";
|
|
}
|
|
}
|
|
}
|
|
|
|
# used to show the built-in CSS rules
|
|
sub builtin_default_css_text()
|
|
{
|
|
my $css_text = '';
|
|
foreach my $css_rule (sort(keys(%css_element_class_styles))) {
|
|
if ($css_element_class_styles{$css_rule} ne '') {
|
|
$css_text .= "$css_rule {$css_element_class_styles{$css_rule}}\n";
|
|
}
|
|
}
|
|
return $css_text;
|
|
}
|
|
|
|
sub _text_element_conversion($$$)
|
|
{
|
|
my $self = shift;
|
|
my $specification = shift;
|
|
my $command = shift;
|
|
|
|
my $text = '';
|
|
# note that there could be elements in text
|
|
if (exists($specification->{'text'})) {
|
|
$text = $specification->{'text'};
|
|
}
|
|
|
|
if (exists($specification->{'element'})) {
|
|
return $self->html_attribute_class($specification->{'element'}, [$command])
|
|
.'>'. $text . '</'.$specification->{'element'}.'>';
|
|
} else {
|
|
return $text;
|
|
}
|
|
}
|
|
|
|
sub _convert_no_arg_command($$$)
|
|
{
|
|
my $self = shift;
|
|
my $cmdname = shift;
|
|
my $command = shift;
|
|
|
|
if ($cmdname eq 'click' and $command->{'extra'}
|
|
and exists($command->{'extra'}->{'clickstyle'})) {
|
|
my $click_cmdname = $command->{'extra'}->{'clickstyle'};
|
|
if (($self->in_preformatted() or $self->in_math()
|
|
and $self->{'no_arg_commands_formatting'}->{'preformatted'}
|
|
->{$click_cmdname})
|
|
or ($self->in_string() and
|
|
$self->{'no_arg_commands_formatting'}->{'string'}->{$click_cmdname})
|
|
or ($self->{'no_arg_commands_formatting'}->{'normal'}->{$click_cmdname})) {
|
|
$cmdname = $click_cmdname;
|
|
}
|
|
}
|
|
if ($self->in_upper_case() and $letter_no_arg_commands{$cmdname}
|
|
and $self->{'no_arg_commands_formatting'}->{'normal'}->{uc($cmdname)}) {
|
|
$cmdname = uc($cmdname);
|
|
}
|
|
|
|
my $result;
|
|
|
|
if ($self->in_preformatted() or $self->in_math()) {
|
|
$result = $self->_text_element_conversion(
|
|
$self->{'no_arg_commands_formatting'}->{'preformatted'}->{$cmdname},
|
|
$cmdname);
|
|
} elsif ($self->in_string()) {
|
|
$result = $self->_text_element_conversion(
|
|
$result = $self->{'no_arg_commands_formatting'}->{'string'}->{$cmdname},
|
|
$cmdname);
|
|
} else {
|
|
$result = $self->_text_element_conversion(
|
|
$result = $self->{'no_arg_commands_formatting'}->{'normal'}->{$cmdname},
|
|
$cmdname);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
foreach my $command(keys(%{$default_no_arg_commands_formatting{'normal'}})) {
|
|
$default_commands_conversion{$command} = \&_convert_no_arg_command;
|
|
}
|
|
|
|
sub _css_string_convert_no_arg_command($$$)
|
|
{
|
|
my $self = shift;
|
|
my $cmdname = shift;
|
|
my $command = shift;
|
|
|
|
if ($cmdname eq 'click' and $command->{'extra'}
|
|
and exists($command->{'extra'}->{'clickstyle'})) {
|
|
my $click_cmdname = $command->{'extra'}->{'clickstyle'};
|
|
if ($self->{'no_arg_commands_formatting'}->{'css_string'}->{$click_cmdname}) {
|
|
$cmdname = $click_cmdname;
|
|
}
|
|
}
|
|
if ($self->in_upper_case() and $letter_no_arg_commands{$cmdname}
|
|
and $self->{'no_arg_commands_formatting'}->{'css_string'}->{uc($cmdname)}) {
|
|
$cmdname = uc($cmdname);
|
|
}
|
|
#if (not defined($self->{'no_arg_commands_formatting'}->{'css_string'}->{$cmdname}->{'text'})) {
|
|
# cluck ("BUG: CSS $cmdname no text");
|
|
#}
|
|
return $self->{'no_arg_commands_formatting'}->{'css_string'}
|
|
->{$cmdname}->{'text'};
|
|
}
|
|
|
|
foreach my $command(keys(%{$default_no_arg_commands_formatting{'normal'}})) {
|
|
$default_css_string_commands_conversion{$command} = \&_css_string_convert_no_arg_command;
|
|
}
|
|
|
|
sub _convert_today_command($$$)
|
|
{
|
|
my $self = shift;
|
|
my $cmdname = shift;
|
|
my $command = shift;
|
|
|
|
my $tree = $self->Texinfo::Convert::Utils::expand_today();
|
|
return $self->convert_tree($tree, 'convert today');
|
|
}
|
|
|
|
$default_commands_conversion{'today'} = \&_convert_today_command;
|
|
|
|
# style commands
|
|
|
|
my %quoted_style_commands;
|
|
foreach my $quoted_command ('samp') {
|
|
$quoted_style_commands{$quoted_command} = 1;
|
|
}
|
|
|
|
my %style_commands_element = ('preformatted' => {});
|
|
$style_commands_element{'normal'} = {
|
|
'b' => 'b',
|
|
'cite' => 'cite',
|
|
'code' => 'code',
|
|
'command' => 'code',
|
|
'dfn' => 'em',
|
|
'dmn' => 'span',
|
|
'emph' => 'em',
|
|
'env' => 'code',
|
|
'file' => 'samp',
|
|
'headitemfont' => 'b', # no effect: the @multitable prototypes are ignored
|
|
# and headitem are in <th> rather than <td>.
|
|
# 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 . '<!-- /@w -->';
|
|
}
|
|
}
|
|
$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' => $args->[0]->{'monospacestring'}}));
|
|
}
|
|
|
|
$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. Both ascii and non-ascii spaces are
|
|
# 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</a>";
|
|
}
|
|
}
|
|
|
|
$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 = '';
|
|
if ($command->{'args'}->[0]
|
|
and $command->{'args'}->[0]->{'contents'}) {
|
|
$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) {
|
|
# TRANSLATORS: abbreviation or acronym 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 = "<sup>$number_in_doc</sup>";
|
|
}
|
|
return $self->html_attribute_class('a', [$cmdname])
|
|
." id=\"$docid\" href=\"$footnote_href\">$footnote_number_text</a>";
|
|
}
|
|
$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</a>";
|
|
}
|
|
|
|
$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]->{'filenametext'})
|
|
and $args->[0]->{'filenametext'} 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\\)</em>";
|
|
}
|
|
return $self->html_attribute_class('em', [$cmdname]).">$arg</em>";
|
|
}
|
|
|
|
$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->get_conf('OUTPUT_CHARACTERS'),
|
|
$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->get_conf('OUTPUT_CHARACTERS'),
|
|
$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
|
|
.'</code>'.$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 = "&#x$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;
|
|
my $target = 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\"";
|
|
|
|
# The ID of this heading is likely the point the user would prefer being
|
|
# linked to over the $target, since that's where they would be seeing a
|
|
# copiable anchor.
|
|
$target = $id;
|
|
}
|
|
my $inside = $text;
|
|
if (defined $target && $self->get_conf('COPIABLE_LINKS')) {
|
|
# Span-wrap this anchor, so that the existing span:hover a.copiable-link
|
|
# rule applies.
|
|
$inside = "<span>$text";
|
|
$inside .= $self->_get_copiable_anchor($target);
|
|
$inside .= '</span>';
|
|
}
|
|
my $result = $self->html_attribute_class("h$level", $classes)
|
|
."${id_str}>$inside</h$level>";
|
|
# 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\"></a>";
|
|
}
|
|
|
|
# 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 = "<a href=\"$href\"${hyperlink_attributes}>$node</a>";
|
|
} elsif (defined($node) and $node =~ /\S/) {
|
|
$hyperlink = $node;
|
|
}
|
|
if (defined($hyperlink)) {
|
|
# i18n
|
|
$result = $self->direction_string($direction, 'text').": $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(
|
|
'<img src="'.$self->url_protect_url_text($icon)
|
|
."\" border=\"0\" alt=\"$alt\" align=\"middle\"");
|
|
}
|
|
|
|
sub _direction_href_attributes($$)
|
|
{
|
|
my $self = shift;
|
|
my $direction = shift;
|
|
|
|
my $href_attributes = '';
|
|
if ($self->get_conf('USE_ACCESSKEY')) {
|
|
my $accesskey = $self->direction_string($direction, 'accesskey', 'string');
|
|
if (defined($accesskey) and ($accesskey ne '')) {
|
|
$href_attributes = " accesskey=\"$accesskey\"";
|
|
}
|
|
}
|
|
if ($self->get_conf('USE_REL_REV')) {
|
|
my $button_rel = $self->direction_string($direction, 'rel', 'string');
|
|
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 = "<a href=\"$href\"${anchor_attributes}>$$text</a>";
|
|
} 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 = "<a href=\"$href\"${anchor_attributes}>$text_formatted</a>";
|
|
} 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_string = $self->direction_string($button,
|
|
'button', 'string');
|
|
$active = &{$self->formatting_function('format_button_icon_img')}($self,
|
|
$button_name_string, $self->get_conf('ACTIVE_ICONS')->{' '});
|
|
} else {
|
|
$active = $self->direction_string($button, 'text');
|
|
}
|
|
$need_delimiter = 0;
|
|
} else {
|
|
my $href = $self->from_element_direction($button, 'href',
|
|
undef, undef, $source_command);
|
|
if ($href) {
|
|
# button is active
|
|
my $btitle = '';
|
|
my $description = $self->direction_string($button, 'description', 'string');
|
|
if (defined($description)) {
|
|
$btitle = ' title="' . $description . '"';
|
|
}
|
|
if ($self->get_conf('USE_ACCESSKEY')) {
|
|
my $accesskey = $self->direction_string($button, 'accesskey', 'string');
|
|
if (defined($accesskey) and $accesskey ne '') {
|
|
$btitle .= " accesskey=\"$accesskey\"";
|
|
}
|
|
}
|
|
if ($self->get_conf('USE_REL_REV')) {
|
|
my $button_rel = $self->direction_string($button, 'rel', 'string');
|
|
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')) {
|
|
# FIXME strip FirstInFile from $button to get $active_icon?
|
|
my $active_icon = $self->get_conf('ACTIVE_ICONS')->{$button};
|
|
my $button_name_string = $self->direction_string($button,
|
|
'button', 'string');
|
|
if (defined($active_icon) and $active_icon ne '') {
|
|
# use icon
|
|
$active = "<a href=\"$href\"${btitle}>".
|
|
&{$self->formatting_function('format_button_icon_img')}($self,
|
|
$button_name_string, $active_icon,
|
|
$self->from_element_direction($button, 'string')) ."</a>";
|
|
$use_icon = 1;
|
|
}
|
|
}
|
|
if (!$use_icon) {
|
|
# use text
|
|
$active = '[' . "<a href=\"$href\"${btitle}>".
|
|
$self->direction_string($button, 'text')."</a>" . ']';
|
|
}
|
|
} else {
|
|
# button is passive
|
|
my $use_icon;
|
|
if ($self->get_conf('ICONS') and $self->get_conf('PASSIVE_ICONS')) {
|
|
# FIXME strip FirstInFile from $button to get $passive_icon?
|
|
my $passive_icon = $self->get_conf('PASSIVE_ICONS')->{$button};
|
|
my $button_name_string = $self->direction_string($button,
|
|
'button', 'string');
|
|
if ($passive_icon and $passive_icon ne '') {
|
|
$passive = &{$self->formatting_function('format_button_icon_img')}(
|
|
$self, $button_name_string, $passive_icon,
|
|
$self->from_element_direction($button, 'string'));
|
|
$use_icon = 1;
|
|
}
|
|
}
|
|
if (!$use_icon) {
|
|
$passive = '[' . $self->direction_string($button, 'text') . ']';
|
|
}
|
|
}
|
|
$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 .= "<tr>" unless $vertical;
|
|
} else {
|
|
$result .= $self->html_attribute_class('div', ['nav-panel']).">\n<p>\n";
|
|
}
|
|
|
|
my $first_button = 1;
|
|
foreach my $button (@$buttons) {
|
|
if ($self->get_conf('HEADER_IN_TABLE')) {
|
|
$result .= '<tr>'."\n" if $vertical;
|
|
$result .= '<td>';
|
|
}
|
|
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 .= "</td>\n";
|
|
$result .= "</tr>\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 .= "</tr>" unless $vertical;
|
|
$result .= "</table>\n";
|
|
} else {
|
|
$result .= "</p>\n</div>\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 .= '<table border="0" cellpadding="0" cellspacing="0">
|
|
<tr>
|
|
<td>
|
|
';
|
|
}
|
|
$result .= &{$self->formatting_function('format_navigation_panel')}($self,
|
|
$buttons, $cmdname, $element,
|
|
$self->get_conf('VERTICAL_HEAD_NAVIGATION'));
|
|
if ($self->get_conf('VERTICAL_HEAD_NAVIGATION')) {
|
|
$result .= '</td>
|
|
<td>
|
|
';
|
|
} 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 "
|
|
# uncomment to get perl object names
|
|
#."$tree_unit (@{$tree_unit->{'contents'}}) ".
|
|
. "(".join('|', map{Texinfo::Common::debug_print_element($_)}
|
|
@{$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 "
|
|
# uncomment next line for the perl object name
|
|
#."$element "
|
|
.Texinfo::Convert::Texinfo::root_heading_command_to_texinfo($element)."\n"
|
|
if ($self->get_conf('DEBUG'));
|
|
my $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 ('shortcontents', 'contents') {
|
|
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'}
|
|
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'}
|
|
or not $element->{'extra'}->{'associated_section'})))
|
|
and defined($element->{'extra'})
|
|
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, "</div>\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 = "<a href=\"$content_href\">$heading</a>";
|
|
}
|
|
}
|
|
|
|
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.'</strong>'."\n";
|
|
} else {
|
|
$result .= &{$self->formatting_function('format_heading_text')}($self,
|
|
$level_corrected_cmdname, \@heading_classes, $heading,
|
|
$heading_level +$self->get_conf('CHAPTER_HEADER_LEVEL') -1,
|
|
$heading_id, $element, $element_id);
|
|
}
|
|
} elsif (defined($heading_id)) {
|
|
# case of a lone node and no header, and case of an empty @top
|
|
$result .= &{$self->formatting_function('format_separate_anchor')}($self,
|
|
$heading_id, $cmdname);
|
|
}
|
|
$result .= $content if (defined($content));
|
|
|
|
$result .= $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'})
|
|
and 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)
|
|
.'><tr><td>'.$self->get_info('non_breaking_space').'</td><td>'.$content
|
|
."</td></tr></table>\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.'</div>'."\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 . '</blockquote>'."\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 . '</pre>';
|
|
} 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\\]".'</em>';
|
|
} else {
|
|
$result .= $self->html_attribute_class('em').'>'."$content".'</em>';
|
|
}
|
|
$result .= '</div>';
|
|
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.'</div>';
|
|
}
|
|
|
|
$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'})
|
|
and 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
|
|
|
|
if ($self->in_preformatted()) {
|
|
return $self->html_attribute_class('pre', [$cmdname]).'>'.$arg ."\n</pre>";
|
|
} else {
|
|
return $self->html_attribute_class('p', [$cmdname]).'>'.$arg ."\n</p>";
|
|
}
|
|
}
|
|
|
|
$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</div>";
|
|
}
|
|
}
|
|
|
|
$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'}
|
|
or !$command->{'extra'}->{'titlepage'});
|
|
if (!$self->in_string()) {
|
|
return $self->html_attribute_class('strong', [$cmdname])
|
|
.">$args->[0]->{'normal'}</strong>"
|
|
.$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'}</h1>\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'}</h3>\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;
|
|
|
|
# should probably never happen
|
|
return '' if ($self->in_string());
|
|
|
|
my $floats = $self->get_info('floats');
|
|
my $listoffloats_name = $command->{'extra'}->{'float_type'};
|
|
if ($floats and $floats->{$listoffloats_name}
|
|
and scalar(@{$floats->{$listoffloats_name}})) {
|
|
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 .= '<dt>';
|
|
my $float_text = $self->command_text($float);
|
|
if (defined($float_text) and $float_text ne '') {
|
|
if ($float_href) {
|
|
$result .= "<a href=\"$float_href\">$float_text</a>";
|
|
} else {
|
|
$result .= $float_text;
|
|
}
|
|
}
|
|
$result .= '</dt>';
|
|
my $caption;
|
|
my $caption_cmdname;
|
|
if ($float->{'extra'} and $float->{'extra'}->{'shortcaption'}) {
|
|
$caption = $float->{'extra'}->{'shortcaption'};
|
|
$caption_cmdname = 'shortcaption';
|
|
} elsif ($float->{'extra'} and $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.'</dd>'."\n";
|
|
}
|
|
return $result . "</dl>\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 ."</div>\n";
|
|
}
|
|
my $begin_row = '';
|
|
my $end_row = '';
|
|
if ($self->_in_preformatted_in_menu()) {
|
|
$begin_row = '<tr><td>';
|
|
$end_row = '</td></tr>';
|
|
}
|
|
return $self->html_attribute_class('table', [$cmdname])
|
|
." border=\"0\" cellspacing=\"0\">${begin_row}\n"
|
|
. $content . "${end_row}</table>\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 and $caption->{'args'}->[0]
|
|
and $caption->{'args'}->[0]->{'contents'}) {
|
|
$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 = '<p>'.$prepended_text.'</p>';
|
|
}
|
|
} 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.'</div>';
|
|
} elsif (defined($prepended) and $prepended_text ne '') {
|
|
$float_type_number_caption
|
|
= $self->html_attribute_class('div', ['type-number-float']). '>'
|
|
. $prepended_text .'</div>';
|
|
}
|
|
return $self->html_attribute_class('div', [$cmdname]). "${id_str}>\n"
|
|
. $content . $float_type_number_caption . '</div>';
|
|
}
|
|
$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'}}) {
|
|
if ($author->{'args'}->[0]
|
|
and $author->{'args'}->[0]->{'contents'}) {
|
|
# TRANSLATORS: quotation author
|
|
my $centered_author = $self->gdt("\@center --- \@emph{{author}}",
|
|
{'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 . "</blockquote>\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;
|
|
|
|
return $content if ($self->in_string());
|
|
|
|
my $title_content = '';
|
|
if ($args->[0] and $args->[0]->{'normal'} ne '') {
|
|
$title_content = "<tr><th>\n". $args->[0]->{'normal'} ."</th></tr>";
|
|
}
|
|
my $cartouche_content = '';
|
|
if ($content =~ /\S/) {
|
|
$cartouche_content = "<tr><td>\n". $content ."</td></tr>";
|
|
}
|
|
if ($cartouche_content ne '' or $title_content ne '') {
|
|
return $self->html_attribute_class('table', [$cmdname])
|
|
. " border=\"1\">${title_content}${cartouche_content}"
|
|
. "</table>\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. "</ul>\n";
|
|
} elsif ($self->get_conf('NO_CSS')) {
|
|
return $self->html_attribute_class('ul', [$cmdname])
|
|
.">\n" . $content. "</ul>\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. "</ul>\n";
|
|
} else {
|
|
return $self->html_attribute_class('ul', [$cmdname])
|
|
.">\n" . $content. "</ul>\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 . "</ol>\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 . "</table>\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 . "</dl>\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 '<li>' . $content . '</li>';
|
|
} else {
|
|
return '';
|
|
}
|
|
} elsif ($command->{'parent'}->{'cmdname'}
|
|
and $command->{'parent'}->{'cmdname'} eq 'enumerate') {
|
|
if ($content =~ /\S/) {
|
|
return '<li>' . ' ' . $content . '</li>';
|
|
} 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 . '</code>';
|
|
last;
|
|
}
|
|
}
|
|
}
|
|
my $open_tag = ($cmdname eq 'item') ? '' : '<dt>';
|
|
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 = "<a id=\"$index_id\"></a>";
|
|
if ($anchor ne '') {
|
|
$anchor_span_open = '<span>';
|
|
$anchor_span_close = '</span>';
|
|
}
|
|
} else {
|
|
$anchor = '';
|
|
$index_id = '';
|
|
}
|
|
return "$open_tag$index_id$anchor_span_open$result$anchor$anchor_span_close</dt>\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 "<th${fractions}>" . $content . '</th>';
|
|
} else {
|
|
return "<td${fractions}>" . $content . '</td>';
|
|
}
|
|
}
|
|
$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 'link' and $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 'link' or $cmdname eq 'inforef') {
|
|
$args->[3] = $args->[2];
|
|
$args->[2] = undef;
|
|
}
|
|
|
|
my $file_arg_tree;
|
|
my $file = '';
|
|
if ($args->[3]
|
|
and defined($args->[3]->{'filenametext'})
|
|
and $args->[3]->{'filenametext'} ne '') {
|
|
$file_arg_tree = $args->[3]->{'tree'};
|
|
$file = $args->[3]->{'filenametext'};
|
|
}
|
|
|
|
my $book = '';
|
|
$book = $args->[4]->{'normal'}
|
|
if ($args->[4] and defined($args->[4]->{'normal'}));
|
|
|
|
my $node_arg = $root->{'args'}->[0];
|
|
|
|
# internal reference
|
|
if ($cmdname ne 'inforef' and $book eq '' and $file eq ''
|
|
and $node_arg and $node_arg->{'extra'}
|
|
and defined($node_arg->{'extra'}->{'normalized'})
|
|
and !$node_arg->{'extra'}->{'manual_content'}
|
|
and $self->label_command($node_arg->{'extra'}->{'normalized'})) {
|
|
my $node
|
|
= $self->label_command($node_arg->{'extra'}->{'normalized'});
|
|
# This is the node if USE_NODES, otherwise this may be the sectioning
|
|
# command (if the sectioning command is really associated to the node)
|
|
my $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'}
|
|
and $node->{'extra'}->{'associated_section'}
|
|
# this condition avoids infinite recursions, indeed in that case
|
|
# the node will be used and not the section. There should not be
|
|
# @*ref in nodes, and even if there are, it does not seems to be
|
|
# possible to construct an infinite recursion with nodes only
|
|
# as the node must both be a reference target and refer to a specific
|
|
# target at the same time, which is not possible.
|
|
and not grep {$_ eq $node->{'extra'}->{'associated_section'}}
|
|
@{$self->{'referred_command_stack'}}) {
|
|
$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</a>" if ($href ne ''
|
|
and !$self->in_string());
|
|
|
|
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' or $cmdname eq 'link') {
|
|
$tree = $self->gdt('{reference_name}',
|
|
{ 'reference_name' => {'type' => '_converted', 'text' => $reference} });
|
|
}
|
|
} else {
|
|
# external reference
|
|
|
|
# We setup a label_info based on the node argument and not directly the
|
|
# node argument to be able to use the $file argument
|
|
my $label_info = {};
|
|
if ($node_arg->{'extra'}) {
|
|
$label_info->{'node_content'} = $node_arg->{'extra'}->{'node_content'}
|
|
if ($node_arg->{'extra'}->{'node_content'});
|
|
$label_info->{'normalized'} = $node_arg->{'extra'}->{'normalized'}
|
|
if (exists($node_arg->{'extra'}->{'normalized'}));
|
|
}
|
|
# file argument takes precedence over the file in the node (file)node entry
|
|
if (defined($file_arg_tree) and $file ne '') {
|
|
$label_info->{'manual_content'} = $file_arg_tree->{'contents'};
|
|
} elsif ($node_arg and $node_arg->{'extra'}
|
|
and $node_arg->{'extra'}->{'manual_content'}) {
|
|
$label_info->{'manual_content'}
|
|
= $node_arg->{'extra'}->{'manual_content'};
|
|
my $file_with_node_tree = {'type' => '_code',
|
|
'contents' => [@{$label_info->{'manual_content'}}]};
|
|
$file = $self->convert_tree($file_with_node_tree, 'node file in ref');
|
|
}
|
|
my $href = $self->command_href($label_info, undef, $root);
|
|
|
|
if ($book eq '') {
|
|
if (!defined($name)) {
|
|
my $node_name = $self->command_text($label_info);
|
|
$name = $node_name;
|
|
}
|
|
} elsif (!defined($name) and $label_info->{'node_content'}) {
|
|
my $node_no_file_tree = {'type' => '_code',
|
|
'contents' => [@{$label_info->{'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 = "<a ${manual_name_attribute}href=\"$href\">$name</a>";
|
|
} elsif ($book ne '') {
|
|
$book_reference = "<a ${manual_name_attribute}href=\"$href\">$book</a>";
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
|
|
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)) {
|
|
my ($root_element, $root_command)
|
|
= $self->get_element_root_command_element($command);
|
|
if ($root_command) {
|
|
$index_element_id = $self->command_id($root_command);
|
|
}
|
|
if (not defined($index_element_id)) {
|
|
# to avoid duplicate names, use a prefix that cannot happen in anchors
|
|
my $target_prefix = 't_i';
|
|
$index_element_id = $target_prefix;
|
|
}
|
|
}
|
|
my $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;
|
|
# since we normalize, a different formatting will not trigger a new
|
|
# formatting of the main entry or a subentry level. This is the
|
|
# same for Texinfo TeX
|
|
my $normalized_entry_levels = [];
|
|
foreach my $index_entry_ref (@{$letter_entry->{'entries'}}) {
|
|
$entry_nr++;
|
|
my $main_entry_element = $index_entry_ref->{'entry_element'};
|
|
next if ($self->get_conf('NO_TOP_NODE_OUTPUT')
|
|
and defined($main_entry_element->{'extra'}->{'element_node'})
|
|
and $main_entry_element->{'extra'}->{'element_node'}->{'extra'}
|
|
and $main_entry_element->{'extra'}->{'element_node'}
|
|
->{'extra'}->{'normalized'}
|
|
and $main_entry_element->{'extra'}->{'element_node'}
|
|
->{'extra'}->{'normalized'} eq 'Top');
|
|
|
|
# to avoid double error messages, call convert_tree_new_formatting_context
|
|
# below with a multiple_pass argument if an entry was already formatted once,
|
|
# for example if there are multiple printindex.
|
|
if (!$formatted_index_entries->{$index_entry_ref}) {
|
|
$formatted_index_entries->{$index_entry_ref} = 1;
|
|
} else {
|
|
$formatted_index_entries->{$index_entry_ref}++;
|
|
}
|
|
|
|
my $entry_content_element
|
|
= Texinfo::Common::index_content_element($main_entry_element);
|
|
|
|
my $in_code = 0;
|
|
my $indices_information = $self->get_info('indices_information');
|
|
$in_code = 1
|
|
if ($indices_information->{$index_entry_ref->{'index_name'}}->{'in_code'});
|
|
my $entry_ref_tree = {'contents' => [$entry_content_element]};
|
|
$entry_ref_tree->{'type'} = '_code' if ($in_code);
|
|
|
|
# index entry with @seeentry or @seealso
|
|
if ($main_entry_element->{'extra'}
|
|
and ($main_entry_element->{'extra'}->{'seeentry'}
|
|
or $main_entry_element->{'extra'}->{'seealso'})) {
|
|
my $referred_entry;
|
|
my $seenentry = 1;
|
|
if ($main_entry_element->{'extra'}->{'seeentry'}) {
|
|
$referred_entry = $main_entry_element->{'extra'}->{'seeentry'};
|
|
} else {
|
|
$referred_entry = $main_entry_element->{'extra'}->{'seealso'};
|
|
$seenentry = 0;
|
|
}
|
|
my @referred_contents;
|
|
if ($referred_entry->{'args'} and $referred_entry->{'args'}->[0]
|
|
and $referred_entry->{'args'}->[0]->{'contents'}) {
|
|
@referred_contents
|
|
= @{$referred_entry->{'args'}->[0]->{'contents'}};
|
|
}
|
|
my $referred_tree = {'contents' => \@referred_contents};
|
|
$referred_tree->{'type'} = '_code' if ($in_code);
|
|
my $entry;
|
|
# for @seealso, to appear where chapter/node ususally appear
|
|
my $reference = '';
|
|
my $delimiter = '';
|
|
my $entry_class;
|
|
my $section_class;
|
|
if ($seenentry) {
|
|
my $result_tree;
|
|
if ($in_code) {
|
|
$result_tree
|
|
# TRANSLATORS: redirect to another index entry
|
|
= $self->gdt('@code{{main_index_entry}}, @emph{See} @code{{seenentry}}',
|
|
{'main_index_entry' => $entry_ref_tree,
|
|
'seenentry' => $referred_tree});
|
|
} else {
|
|
$result_tree
|
|
# TRANSLATORS: redirect to another index entry
|
|
= $self->gdt('{main_index_entry}, @emph{See} {seenentry}',
|
|
{'main_index_entry' => $entry_ref_tree,
|
|
'seenentry' => $referred_tree});
|
|
}
|
|
if ($formatted_index_entries->{$index_entry_ref} > 1) {
|
|
# call with multiple_pass argument
|
|
$entry = $self->convert_tree_new_formatting_context($result_tree,
|
|
"index $index_name l $letter index entry $entry_nr seenentry",
|
|
"index formatted $formatted_index_entries->{$index_entry_ref}")
|
|
} else {
|
|
$entry = $self->convert_tree($result_tree,
|
|
"index $index_name l $letter index entry $entry_nr seenentry");
|
|
}
|
|
$entry_class = "$cmdname-index-see-entry";
|
|
$section_class = "$cmdname-index-see-entry-section";
|
|
} else {
|
|
# TRANSLATORS: refer to another index entry
|
|
my $reference_tree = $self->gdt('@emph{See also} {see_also_entry}',
|
|
{'see_also_entry' => $referred_tree});
|
|
if ($formatted_index_entries->{$index_entry_ref} > 1) {
|
|
# call with multiple_pass argument
|
|
$entry = $self->convert_tree_new_formatting_context($entry_ref_tree,
|
|
"index $index_name l $letter index entry $entry_nr (with seealso)",
|
|
"index formatted $formatted_index_entries->{$index_entry_ref}");
|
|
$reference
|
|
= $self->convert_tree_new_formatting_context($reference_tree,
|
|
"index $index_name l $letter index entry $entry_nr seealso",
|
|
"index formatted $formatted_index_entries->{$index_entry_ref}");
|
|
} else {
|
|
$entry = $self->convert_tree($entry_ref_tree,
|
|
"index $index_name l $letter index entry $entry_nr (with seealso)");
|
|
$reference
|
|
= $self->convert_tree_new_formatting_context($reference_tree,
|
|
"index $index_name l $letter index entry $entry_nr seealso");
|
|
}
|
|
$entry = '<code>' .$entry .'</code>' if ($in_code);
|
|
$delimiter = $self->get_conf('INDEX_ENTRY_COLON');
|
|
# TODO add the information that it is associated with see also?
|
|
$entry_class = "$cmdname-index-entry";
|
|
$section_class = "$cmdname-index-see-also";
|
|
}
|
|
|
|
$entries_text .= '<tr><td></td>'
|
|
.$self->html_attribute_class('td', [$entry_class]).'>'
|
|
. $entry .
|
|
$delimiter . '</td>'
|
|
.$self->html_attribute_class('td', [$section_class]).'>';
|
|
$entries_text .= $reference;
|
|
$entries_text .= "</td></tr>\n";
|
|
|
|
$normalized_entry_levels = [];
|
|
next;
|
|
}
|
|
|
|
# determine the trees and normalized main entry and subentries, to be
|
|
# compared with the previous line normalized entries to determine
|
|
# what is already formatted as part of the previous lines and
|
|
# what levels should be added. The last level is always formatted.
|
|
my @new_normalized_entry_levels;
|
|
my @entry_trees;
|
|
$new_normalized_entry_levels[0]
|
|
= uc(Texinfo::Convert::NodeNameNormalization::convert_to_normalized(
|
|
$entry_ref_tree));
|
|
$entry_trees[0] = $entry_ref_tree;
|
|
my $subentry = $index_entry_ref->{'entry_element'};
|
|
my $subentry_level = 1;
|
|
my $subentries_max_level = 2;
|
|
while ($subentry->{'extra'} and $subentry->{'extra'}->{'subentry'}
|
|
and $subentry_level <= $subentries_max_level) {
|
|
$subentry = $subentry->{'extra'}->{'subentry'};
|
|
my @subentry_contents;
|
|
if ($subentry->{'args'} and $subentry->{'args'}->[0]
|
|
and $subentry->{'args'}->[0]->{'contents'}) {
|
|
@subentry_contents = @{$subentry->{'args'}->[0]->{'contents'}};
|
|
}
|
|
my $subentry_tree = {'contents' => \@subentry_contents};
|
|
$subentry_tree->{'type'} = '_code' if ($in_code);
|
|
if ($subentry_level >= $subentries_max_level) {
|
|
# at the max, concatenate the remaining subentries
|
|
my $other_subentries_tree = $self->comma_index_subentries_tree($subentry);
|
|
push @{$subentry_tree->{'contents'}},
|
|
@{$other_subentries_tree->{'contents'}}
|
|
if defined($other_subentries_tree);
|
|
} else {
|
|
push @new_normalized_entry_levels,
|
|
uc(Texinfo::Convert::NodeNameNormalization::convert_to_normalized(
|
|
$subentry_tree));
|
|
}
|
|
push @entry_trees, $subentry_tree;
|
|
$subentry_level ++;
|
|
}
|
|
#print STDERR join('|', @new_normalized_entry_levels)."\n";
|
|
# last entry, always converted, associated to chapter/node and
|
|
# with an hyperlinh
|
|
my $entry_tree = pop @entry_trees;
|
|
# indentation level of the last entry
|
|
my $entry_level = 0;
|
|
|
|
# format the leading entries when there are subentries.
|
|
# Each on a line with increasing indentation, no hyperlink.
|
|
if (scalar(@entry_trees) > 0) {
|
|
# find the level not already formatted as part of the previous lines
|
|
my $starting_subentry_level = 0;
|
|
foreach my $subentry_tree (@entry_trees) {
|
|
if ((scalar(@$normalized_entry_levels) > $starting_subentry_level)
|
|
and $normalized_entry_levels->[$starting_subentry_level]
|
|
eq $new_normalized_entry_levels[$starting_subentry_level]) {
|
|
} else {
|
|
last;
|
|
}
|
|
$starting_subentry_level ++;
|
|
}
|
|
$entry_level = $starting_subentry_level;
|
|
foreach my $level ($starting_subentry_level .. scalar(@entry_trees)-1) {
|
|
my $entry;
|
|
if ($formatted_index_entries->{$index_entry_ref} > 1) {
|
|
# call with multiple_pass argument
|
|
$entry = $self->convert_tree_new_formatting_context($entry_trees[$level],
|
|
"index $index_name l $letter index entry $entry_nr subentry $level",
|
|
"index formatted $formatted_index_entries->{$index_entry_ref}")
|
|
} else {
|
|
$entry = $self->convert_tree($entry_trees[$level],
|
|
"index $index_name l $letter index entry $entry_nr subentry $level");
|
|
}
|
|
$entry = '<code>' .$entry .'</code>' if ($in_code);
|
|
my @td_entry_classes = ("$cmdname-index-entry");
|
|
# indent
|
|
if ($level > 0) {
|
|
push @td_entry_classes, "index-entry-level-$level";
|
|
}
|
|
$entries_text .= '<tr><td></td>'
|
|
# FIXME same class used for leading element of the entry and
|
|
# last element of the entry. Could be different.
|
|
.$self->html_attribute_class('td', \@td_entry_classes).'>'
|
|
. $entry . '</td>'
|
|
# empty cell, no section for this line
|
|
. "<td></td></tr>\n";
|
|
|
|
$entry_level = $level+1;
|
|
}
|
|
}
|
|
|
|
my $entry;
|
|
if ($formatted_index_entries->{$index_entry_ref} > 1) {
|
|
# call with multiple_pass argument
|
|
$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/ and $entry_level == 0);
|
|
|
|
$normalized_entry_levels = [@new_normalized_entry_levels];
|
|
$entry = '<code>' .$entry .'</code>' if ($in_code);
|
|
my $target_element = $index_entry_ref->{'entry_element'};
|
|
$target_element = $index_entry_ref->{'entry_associated_element'}
|
|
if ($index_entry_ref->{'entry_associated_element'});
|
|
my $entry_href = $self->command_href($target_element);
|
|
my $formatted_entry = "<a href=\"$entry_href\">$entry</a>";
|
|
my @td_entry_classes = ("$cmdname-index-entry");
|
|
# subentry
|
|
if ($entry_level > 0) {
|
|
push @td_entry_classes, "index-entry-level-$entry_level";
|
|
}
|
|
|
|
my $associated_command;
|
|
if ($self->get_conf('NODE_NAME_IN_INDEX')) {
|
|
$associated_command = $main_entry_element->{'extra'}->{'element_node'};
|
|
if (!defined($associated_command)) {
|
|
$associated_command
|
|
= $self->command_node($target_element);
|
|
}
|
|
if (!defined($associated_command)
|
|
# do not warn if the entry is in a special region, like titlepage
|
|
and not $main_entry_element->{'extra'}->{'element_region'}
|
|
and $formatted_index_entries->{$index_entry_ref} == 1) {
|
|
# NOTE _noticed_line_warn is not used as printindex should not
|
|
# happen in multiple tree parsing that lead to ignore_notice being set,
|
|
# but the error message is printed only for the first entry formatting.
|
|
$self->line_warn($self,
|
|
sprintf(
|
|
__("entry for index `%s' for \@printindex %s outside of any node"),
|
|
$index_entry_ref->{'index_name'},
|
|
$index_name),
|
|
$main_entry_element->{'source_info'});
|
|
}
|
|
}
|
|
if (!$associated_command) {
|
|
$associated_command
|
|
= $self->command_root_element_command($target_element);
|
|
if (!$associated_command) {
|
|
# Use Top if not associated command found
|
|
$associated_command
|
|
= $self->tree_unit_element_command(
|
|
$self->global_direction_element('Top'));
|
|
# NOTE the warning here catches the most relevant cases of
|
|
# index entry that is not associated to the right command, which
|
|
# are very few in the test suite. There is also a warning in the
|
|
# parser with a much broader scope with possible overlap, but the
|
|
# overlap is not a problem.
|
|
# NODE_NAME_IN_INDEX may be undef even with USE_NODES set if the
|
|
# converter is called as convert() as in the test suite
|
|
if (defined($self->get_conf('NODE_NAME_IN_INDEX'))
|
|
and not $self->get_conf('NODE_NAME_IN_INDEX')
|
|
# do not warn if the entry is in a special region, like titlepage
|
|
and not $main_entry_element->{'extra'}->{'element_region'}
|
|
and $formatted_index_entries->{$index_entry_ref} == 1) {
|
|
# NOTE _noticed_line_warn is not used as printindex should not
|
|
# happen in multiple tree parsing that lead to ignore_notice being set,
|
|
# but the error message is printed only for the first entry formatting.
|
|
# NOTE the index entry may be associated to a node in that case.
|
|
$self->line_warn($self,
|
|
sprintf(
|
|
__("entry for index `%s' for \@printindex %s outside of any section"),
|
|
$index_entry_ref->{'index_name'},
|
|
$index_name),
|
|
$main_entry_element->{'source_info'});
|
|
}
|
|
}
|
|
}
|
|
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 .= '<tr><td></td>'
|
|
.$self->html_attribute_class('td', \@td_entry_classes).'>'
|
|
. $formatted_entry . $self->get_conf('INDEX_ENTRY_COLON') . '</td>'
|
|
.$self->html_attribute_class('td', ["$cmdname-index-section"]).'>';
|
|
$entries_text
|
|
.= "<a href=\"$associated_command_href\">$associated_command_text</a>"
|
|
if ($associated_command_href);
|
|
$entries_text .= "</td></tr>\n";
|
|
}
|
|
# a letter and associated indice entries
|
|
if ($entries_text ne '') {
|
|
$result_index_entries .= '<tr>' .
|
|
"<th id=\"$letter_id{$letter}\">".
|
|
&{$self->formatting_function('format_protect_text')}($self, $letter)
|
|
. "</th></tr>\n" . $entries_text
|
|
. "<tr><td colspan=\"3\">".$self->get_conf('DEFAULT_RULE')."</td></tr>\n";
|
|
push @letter_entries, $letter_entry;
|
|
}
|
|
}
|
|
|
|
# Do the summary letters linking to the letters done above
|
|
my @non_alpha = ();
|
|
my @alpha = ();
|
|
foreach my $letter_entry (@letter_entries) {
|
|
my $letter = $letter_entry->{'letter'};
|
|
my $summary_letter_link
|
|
= $self->html_attribute_class('a',["summary-letter-$cmdname"])
|
|
." href=\"#$letter_id{$letter}\"><b>".
|
|
&{$self->formatting_function('format_protect_text')}($self, $letter)
|
|
.'</b></a>';
|
|
if ($letter_is_symbol{$letter}) {
|
|
push @non_alpha, $summary_letter_link;
|
|
} else {
|
|
push @alpha, $summary_letter_link;
|
|
}
|
|
}
|
|
|
|
if (scalar(@non_alpha) + scalar(@alpha) == 0) {
|
|
$self->_pop_document_context();
|
|
return '';
|
|
}
|
|
|
|
my $non_breaking_space = $self->get_info('non_breaking_space');
|
|
|
|
# Format the summary letters
|
|
my $join = '';
|
|
my $non_alpha_text = '';
|
|
my $alpha_text = '';
|
|
if (scalar(@non_alpha) + scalar(@alpha) > 1) {
|
|
$join = " $non_breaking_space \n".$self->get_info('line_break_element')."\n"
|
|
if (scalar(@non_alpha) and scalar(@alpha));
|
|
if (scalar(@non_alpha)) {
|
|
$non_alpha_text = join("\n $non_breaking_space \n", @non_alpha) . "\n";
|
|
}
|
|
if (scalar(@alpha)) {
|
|
$alpha_text = join("\n $non_breaking_space \n", @alpha)
|
|
. "\n $non_breaking_space \n";
|
|
}
|
|
}
|
|
my $result = $self->html_attribute_class('div',
|
|
[$cmdname, "$index_name-$cmdname"]).">\n";
|
|
# format the summary
|
|
if (scalar(@non_alpha) + scalar(@alpha) > 1) {
|
|
my $summary_header = $self->html_attribute_class('table',
|
|
["$index_name-letters-header-$cmdname"]).'><tr><th>'
|
|
# TRANSLATORS: before list of letters and symbols grouping index entries
|
|
. $self->convert_tree($self->gdt('Jump to')) .": $non_breaking_space </th><td>" .
|
|
$non_alpha_text . $join . $alpha_text . "</td></tr></table>\n";
|
|
|
|
$result .= $summary_header;
|
|
}
|
|
|
|
# now format the index entries
|
|
$result
|
|
.= $self->html_attribute_class('table', ["$index_name-entries-$cmdname"])
|
|
." border=\"0\">\n" . '<tr><td></td>'
|
|
. $self->html_attribute_class('th', ["entries-header-$cmdname"]).'>'
|
|
# TRANSLATORS: index entries column header in index formatting
|
|
. $self->convert_tree($self->gdt('Index Entry')) .'</th>'
|
|
. $self->html_attribute_class('th', ["sections-header-$cmdname"]).'>'
|
|
# TRANSLATORS: section of index entry column header in index formatting
|
|
. $self->convert_tree($self->gdt('Section')) . "</th></tr>\n"
|
|
. "<tr><td colspan=\"3\">".$self->get_conf('DEFAULT_RULE')
|
|
."</td></tr>\n";
|
|
$result .= $result_index_entries;
|
|
$result .= "</table>\n";
|
|
|
|
$self->_pop_document_context();
|
|
|
|
if (scalar(@non_alpha) + scalar(@alpha) > 1) {
|
|
my $summary_footer = $self->html_attribute_class('table',
|
|
["$index_name-letters-footer-$cmdname"]).'><tr><th>'
|
|
# TRANSLATORS: before list of letters and symbols grouping index entries
|
|
. $self->convert_tree($self->gdt('Jump to'))
|
|
. ": $non_breaking_space </th><td>"
|
|
. $non_alpha_text . $join . $alpha_text . "</td></tr></table>\n";
|
|
$result .= $summary_footer
|
|
}
|
|
return $result . "</div>\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 (defined($id) and $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";
|
|
my $heading_tree = $self->special_element_info('heading_tree',
|
|
$special_element_variety);
|
|
if (defined($heading_tree)) {
|
|
$heading = $self->convert_tree($heading_tree,
|
|
"convert $cmdname special heading");
|
|
} else {
|
|
$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 . "</div>\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);
|
|
|
|
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', 'postamble_after_end',
|
|
'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."</p>";
|
|
} else {
|
|
return "<p>".$content."</p>";
|
|
}
|
|
} 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, 1);
|
|
}
|
|
|
|
$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 <pre> 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 <pre> is ignored.
|
|
my $result = $self->html_attribute_class('pre', [$pre_class]).'>'
|
|
. $content . '</pre>';
|
|
|
|
# this may happen with lines without textual content
|
|
# between a def* and def*x.
|
|
if ($element->{'parent'}->{'cmdname'}
|
|
and $element->{'parent'}->{'cmdname'} =~ /^def/) {
|
|
$result = '<dd>'.$result.'</dd>';
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
$default_types_conversion{'preformatted'} = \&_convert_preformatted_type;
|
|
|
|
sub _convert_balanced_braces_type($$$$) {
|
|
my $self = shift;
|
|
my $type = shift;
|
|
my $element = shift;
|
|
my $content = shift;
|
|
|
|
return $content;
|
|
}
|
|
|
|
$default_types_conversion{'balanced_braces'} = \&_convert_balanced_braces_type;
|
|
|
|
# use the type and not the index commands names, as they are diverse and
|
|
# can be dynamically added, so it is difficult to use as selector for output
|
|
# formatting. The command name can be obtained here as $element->{'cmdname'}.
|
|
sub _convert_index_entry_command_type($$$$)
|
|
{
|
|
my $self = shift;
|
|
my $type = shift;
|
|
my $element = shift;
|
|
my $content = shift;
|
|
|
|
my $index_id = $self->command_id($element);
|
|
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_types_conversion{'index_entry_command'} = \&_convert_index_entry_command_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('OUTPUT_CHARACTERS')
|
|
# and $self->get_conf('OUTPUT_ENCODING_NAME')
|
|
# and $self->get_conf('OUTPUT_ENCODING_NAME') eq 'utf-8') {
|
|
if ($self->{'conf'}->{'OUTPUT_CHARACTERS'}
|
|
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->{'no_break'}) {
|
|
my $non_breaking_space = $self->get_info('non_breaking_space');
|
|
$text =~ s/\n/ /g;
|
|
$text =~ s/ +/$non_breaking_space/g;
|
|
# API info: in_space_protected() API code conforming would be:
|
|
#} elsif ($self->in_space_protected()) {
|
|
} elsif ($formatting_context->{'space_protected'}) {
|
|
if (chomp($text)) {
|
|
# API info: API code conforming would be:
|
|
# $self->get_info('line_break_element')
|
|
my $line_break_element = $self->{'line_break_element'};
|
|
# protect spaces in line_break_element formatting.
|
|
# Note that this case is theoretical right now, as it is not possible
|
|
# to redefine $self->{'line_break_element'} and there are no spaces
|
|
# in the possible values. However this is a deficiency of the API,
|
|
# it would be better to be able to redefine $self->{'line_break_element'}
|
|
$line_break_element =~ s/ /\x{1F}/g;
|
|
$text .= $line_break_element;
|
|
}
|
|
# Protect spaces within text
|
|
my $non_breaking_space = $self->get_info('non_breaking_space');
|
|
$text =~ s/ /$non_breaking_space/g;
|
|
# Revert protected spaces in leading html attribute
|
|
$text =~ s/\x{1F}/ /g;
|
|
}
|
|
return $text;
|
|
}
|
|
|
|
$default_types_conversion{'text'} = \&_convert_text;
|
|
|
|
sub _css_string_convert_text($$$)
|
|
{
|
|
my $self = 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 = '<tr>' . $content . '</tr>';
|
|
if ($element->{'contents'}
|
|
and scalar(@{$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 '<thead>' . $content . '</thead>' . "\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 '<tbody>' . $content . '</tbody>' . "\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 $name_entry;
|
|
my $menu_description;
|
|
my $menu_entry_node;
|
|
my $menu_entry_leading_text;
|
|
my @menu_entry_separators;
|
|
|
|
foreach my $arg (@{$element->{'contents'}}) {
|
|
if ($arg->{'type'} eq 'menu_entry_leading_text') {
|
|
$menu_entry_leading_text = $arg;
|
|
} elsif ($arg->{'type'} eq 'menu_entry_name') {
|
|
$name_entry = $arg;
|
|
} elsif ($arg->{'type'} eq 'menu_entry_description') {
|
|
$menu_description = $arg;
|
|
} elsif ($arg->{'type'} eq 'menu_entry_separator') {
|
|
push @menu_entry_separators, $arg;
|
|
} elsif ($arg->{'type'} eq 'menu_entry_node') {
|
|
$menu_entry_node = $arg;
|
|
}
|
|
}
|
|
|
|
my $href = '';
|
|
my $rel = '';
|
|
my $section;
|
|
my $label_info = $menu_entry_node->{'extra'};
|
|
|
|
# external node
|
|
my $external_node;
|
|
if ($label_info and $label_info->{'manual_content'}) {
|
|
$href = $self->command_href($label_info, undef, $element);
|
|
$external_node = 1;
|
|
# may not be defined in case of menu entry node consisting only of spaces
|
|
} elsif ($label_info and defined($label_info->{'normalized'})) {
|
|
my $node = $self->label_command($label_info->{'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'} and $node->{'extra'}->{'isindex'}) {
|
|
# Mark the target as an index. See
|
|
# http://microformats.org/wiki/existing-rel-values#HTML5_link_type_extensions
|
|
$rel = ' rel="index"';
|
|
}
|
|
}
|
|
}
|
|
|
|
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 $leading_text = $menu_entry_leading_text->{'text'};
|
|
$leading_text =~ s/\*/$MENU_SYMBOL/;
|
|
my $result_name_node = $leading_text;
|
|
|
|
if ($name_entry) {
|
|
$result_name_node
|
|
.= $self->convert_tree($name_entry,
|
|
"menu_arg menu_entry_name preformatted");
|
|
my $name_separator = shift @menu_entry_separators;
|
|
$result_name_node
|
|
.= $self->convert_tree($name_separator,
|
|
"menu_arg name separator preformatted");
|
|
}
|
|
|
|
if ($menu_entry_node) {
|
|
# 'contents' seems to always be defined. If it is
|
|
# not the case, it should not be an issue as an undefined
|
|
# 'contents' is ignored.
|
|
my $name = $self->convert_tree(
|
|
{'type' => '_code',
|
|
'contents' => $menu_entry_node->{'contents'}},
|
|
"menu_arg menu_entry_node preformatted");
|
|
if ($href ne '' and !$in_string) {
|
|
$result_name_node .= "<a href=\"$href\"$rel$accesskey>".$name."</a>";
|
|
} else {
|
|
$result_name_node .= $name;
|
|
}
|
|
}
|
|
if (scalar(@menu_entry_separators)) {
|
|
my $node_separator = shift @menu_entry_separators;
|
|
$result_name_node
|
|
.= $self->convert_tree($node_separator,
|
|
"menu_arg node separator preformatted");
|
|
}
|
|
|
|
if (!$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 . '</pre>';
|
|
}
|
|
|
|
my $description = '';
|
|
if ($menu_description) {
|
|
$description .= $self->convert_tree($menu_description,
|
|
"menu_arg description preformatted");
|
|
}
|
|
|
|
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 = "<a href=\"$href\"$rel$accesskey>".$name."</a>";
|
|
}
|
|
}
|
|
if (!defined($name) or $name eq '') {
|
|
if ($name_entry) {
|
|
$name = $self->convert_tree($name_entry, 'convert menu_entry_name');
|
|
}
|
|
if (!defined($name) or $name eq '') {
|
|
if ($label_info and $label_info->{'manual_content'}) {
|
|
$name = $self->command_text($label_info);
|
|
} elsif ($label_info) {
|
|
$name = $self->convert_tree({'type' => '_code',
|
|
'contents' => $label_info->{'node_content'}},
|
|
'menu_arg name');
|
|
} else {
|
|
$name = '';
|
|
}
|
|
}
|
|
$name =~ s/^\s*//;
|
|
$name_no_number = $name;
|
|
if ($href ne '') {
|
|
$name = "<a href=\"$href\"$rel$accesskey>".$name."</a>";
|
|
}
|
|
$name = "$MENU_SYMBOL ".$name;
|
|
}
|
|
my $description = '';
|
|
if ($menu_description) {
|
|
$description = $self->convert_tree($menu_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 '<tr>'
|
|
.$self->html_attribute_class('td', ['menu-entry-destination']).'>'
|
|
."$name$MENU_ENTRY_COLON</td>"
|
|
."<td>${non_breaking_space}${non_breaking_space}</td>"
|
|
.$self->html_attribute_class('td', ['menu-entry-description']).'>'
|
|
."$description</td></tr>\n";
|
|
}
|
|
|
|
$default_types_conversion{'menu_entry'} = \&_convert_menu_entry_type;
|
|
|
|
sub _convert_menu_comment_type($$$$)
|
|
{
|
|
my $self = shift;
|
|
my $type = shift;
|
|
my $element = shift;
|
|
my $content = shift;
|
|
|
|
if ($self->_in_preformatted_in_menu() or $self->in_string()) {
|
|
return $content;
|
|
} else {
|
|
return '<tr>'.$self->html_attribute_class('th', ['menu-comment'])
|
|
. ' colspan="3">'.$content .'</th></tr>';
|
|
}
|
|
}
|
|
|
|
$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 '<li>'. $content .'</li>';
|
|
} elsif ($top_block_command eq 'table' or $top_block_command eq 'vtable'
|
|
or $top_block_command eq 'ftable') {
|
|
return '<dd>'. $content .'</dd>'."\n";
|
|
} elsif ($top_block_command eq 'multitable') {
|
|
$content =~ s/^\s*//;
|
|
$content =~ s/\s*$//;
|
|
|
|
return '<tr><td>'.$content.'</td></tr>'."\n";
|
|
}
|
|
}
|
|
|
|
$default_types_conversion{'before_item'} = \&_convert_before_item_type;
|
|
|
|
sub _convert_table_term_type($$$$)
|
|
{
|
|
my $self = shift;
|
|
my $type = shift;
|
|
my $element = shift;
|
|
my $content = shift;
|
|
|
|
return '<dt>'.$content;
|
|
}
|
|
|
|
$default_types_conversion{'table_term'} = \&_convert_table_term_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)));
|
|
}
|
|
|
|
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 ($category_element, $class_element,
|
|
$type_element, $name_element, $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 ($type_element) {
|
|
my $type_text = $self->_convert({'type' => '_code',
|
|
'contents' => [$type_element]});
|
|
if ($type_text ne '') {
|
|
$result_type = $self->html_attribute_class('code', ['def-type']).'>'.
|
|
$type_text .'</code>';
|
|
}
|
|
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 ($name_element) {
|
|
$result_name = $self->html_attribute_class('strong', ['def-name']).'>'.
|
|
$self->_convert({'type' => '_code', 'contents' => [$name_element]})
|
|
.'</strong>';
|
|
}
|
|
|
|
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.'</code>'
|
|
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 .'</var>';
|
|
}
|
|
}
|
|
}
|
|
|
|
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_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 . '</td>'.$self->html_attribute_class('td', ['category-def'])
|
|
. '>' . '[' . $category_result . ']' . "</td></tr>\n";
|
|
}
|
|
|
|
my $category_result = '';
|
|
my $category_tree;
|
|
if ($category_element) {
|
|
if ($class_element) {
|
|
if ($command_name eq 'deftypeop'
|
|
and $type_element
|
|
and $self->get_conf('deftypefnnewline') eq 'on') {
|
|
$category_tree = $self->gdt('{category} on @code{{class}}:@* ',
|
|
{'category' => $category_element,
|
|
'class' => $class_element});
|
|
} elsif ($command_name eq 'defop' or $command_name eq 'deftypeop') {
|
|
$category_tree = $self->gdt('{category} on @code{{class}}: ',
|
|
{'category' => $category_element,
|
|
'class' => $class_element});
|
|
} elsif ($command_name eq 'defcv' or $command_name eq 'deftypecv') {
|
|
$category_tree = $self->gdt('{category} of @code{{class}}: ',
|
|
{'category' => $category_element,
|
|
'class' => $class_element});
|
|
}
|
|
} elsif ($type_element
|
|
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 <br> 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_element});
|
|
} else {
|
|
$category_tree = $self->gdt('{category}: ', {'category' => $category_element});
|
|
}
|
|
$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.'</span>';
|
|
}
|
|
}
|
|
my $anchor_span_open = '';
|
|
my $anchor_span_close = '';
|
|
my $anchor = $self->_get_copiable_anchor($index_id);
|
|
if ($anchor ne '') {
|
|
$anchor_span_open = '<span>';
|
|
$anchor_span_close = '</span>';
|
|
}
|
|
return $self->html_attribute_class('dt', \@classes)
|
|
. "$index_label>" . $category_result . $anchor_span_open
|
|
. $def_call
|
|
. "$anchor$anchor_span_close</dt>\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</a>";
|
|
}
|
|
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 '<dd>' . $content . '</dd>';
|
|
} else {
|
|
return '<tr><td colspan="2">' . $content . '</td></tr>';
|
|
}
|
|
}
|
|
}
|
|
|
|
$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 ."</dl>\n";
|
|
} else {
|
|
return $self->html_attribute_class('table', \@classes)." width=\"100%\">\n"
|
|
. $content . "</table>\n";
|
|
}
|
|
}
|
|
|
|
foreach my $command (keys(%def_commands), 'defblock') {
|
|
$default_commands_conversion{$command} = \&_convert_def_command;
|
|
}
|
|
|
|
sub _convert_table_definition_type($$$$)
|
|
{
|
|
my $self = shift;
|
|
my $type = shift;
|
|
my $element = shift;
|
|
my $content = shift;
|
|
|
|
return $content if ($self->in_string());
|
|
if ($content =~ /\S/) {
|
|
return '<dd>' . $content . '</dd>'."\n";
|
|
}
|
|
}
|
|
|
|
$default_types_conversion{'table_definition'}
|
|
= \&_convert_table_definition_type;
|
|
$default_types_conversion{'inter_item'}
|
|
= \&_convert_table_definition_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 ('shortcontents', 'contents') {
|
|
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->special_element_info('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 . '</div>';
|
|
$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 .= "</td>
|
|
</tr>
|
|
</table>"."\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,
|
|
'format_translate_string' => undef,
|
|
);
|
|
|
|
# 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_converted' 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_converted'})) {
|
|
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 <hr> to <hr/>
|
|
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 = <HTMLXREF>) {
|
|
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_files;
|
|
my $htmlxref_mode = $self->get_conf('HTMLXREF_MODE');
|
|
return if (defined($htmlxref_mode) and $htmlxref_mode eq 'none');
|
|
my $htmlxref_file_name = 'htmlxref.cnf';
|
|
if (defined($htmlxref_mode) and $htmlxref_mode eq 'file') {
|
|
if (defined($self->get_conf('HTMLXREF_FILE'))) {
|
|
$htmlxref_file_name = $self->get_conf('HTMLXREF_FILE');
|
|
}
|
|
my ($encoded_htmlxref_file_name, $htmlxref_file_encoding)
|
|
= $self->encoded_output_file_name($htmlxref_file_name);
|
|
if (-e $encoded_htmlxref_file_name and -r $encoded_htmlxref_file_name) {
|
|
@htmlxref_files = ($encoded_htmlxref_file_name);
|
|
} else {
|
|
$self->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, '.';
|
|
|
|
# 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);
|
|
@htmlxref_files
|
|
= Texinfo::Common::locate_init_file($encoded_htmlxref_file_name,
|
|
\@htmlxref_dirs, 1);
|
|
}
|
|
}
|
|
|
|
$self->{'htmlxref'} = {};
|
|
if (scalar(@htmlxref_files)) {
|
|
$self->{'htmlxref'} = _parse_htmlxref_files($self,
|
|
\@htmlxref_files);
|
|
}
|
|
}
|
|
|
|
# converter state
|
|
#
|
|
# output_init_conf
|
|
#
|
|
# API exists
|
|
# shared_conversion_state
|
|
# Set through the shared_conversion_state API (among others):
|
|
# explained_commands # used only in an @-command conversion function
|
|
# element_explanation_contents # same as above
|
|
#
|
|
# 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
|
|
# directions_strings
|
|
# translated_direction_strings
|
|
#
|
|
# API exists
|
|
# special_element_info
|
|
# translated_special_element_info
|
|
#
|
|
# 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
|
|
#
|
|
# No API, converter internals
|
|
# tree_units
|
|
# out_filepaths (partially common with Texinfo::Converter)
|
|
# current_root_element
|
|
# seen_ids
|
|
# ignore_notice
|
|
# options_latex_math
|
|
# htmlxref
|
|
# check_htmlxref_already_warned
|
|
# referred_command_stack
|
|
#
|
|
# from Converter
|
|
# labels
|
|
|
|
my %special_characters = (
|
|
'paragraph_symbol' => ['¶', '00B6'],
|
|
'left_quote' => ['‘', '2018'],
|
|
'right_quote' => ['’', '2019'],
|
|
'bullet' => ['•', '2022'],
|
|
'non_breaking_space' => [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('OUTPUT_CHARACTERS')
|
|
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'} = '<br/>';
|
|
} else {
|
|
$self->{'line_break_element'} = '<br>';
|
|
}
|
|
$conf_default_no_arg_commands_formatting_normal->{'*'}->{'text'}
|
|
= $self->{'line_break_element'};
|
|
|
|
# three types of direction strings:
|
|
# * strings not translated, already converted
|
|
# * strings translated
|
|
# - strings already converted
|
|
# - strings not already converted
|
|
$self->{'directions_strings'} = {};
|
|
|
|
my $customized_direction_strings
|
|
= Texinfo::Config::GNUT_get_direction_string_info();
|
|
foreach my $string_type (keys(%default_converted_directions_strings)) {
|
|
$self->{'directions_strings'}->{$string_type} = {};
|
|
foreach my $direction
|
|
(keys(%{$default_converted_directions_strings{$string_type}})) {
|
|
$self->{'directions_strings'}->{$string_type}->{$direction} = {};
|
|
my $string_contexts;
|
|
if ($customized_direction_strings->{$string_type}
|
|
and $customized_direction_strings->{$string_type}->{$direction}) {
|
|
if (defined($customized_direction_strings->{$string_type}
|
|
->{$direction}->{'converted'})) {
|
|
$string_contexts
|
|
= $customized_direction_strings->{$string_type}
|
|
->{$direction}->{'converted'};
|
|
}
|
|
} else {
|
|
my $string
|
|
= $default_converted_directions_strings{$string_type}->{$direction};
|
|
$string_contexts
|
|
= {'normal' => $string};
|
|
}
|
|
$string_contexts->{'string'} = $string_contexts->{'normal'}
|
|
if (not defined($string_contexts->{'string'}));
|
|
foreach my $context (keys(%$string_contexts)) {
|
|
$self->{'directions_strings'}->{$string_type}->{$direction}->{$context}
|
|
= $self->substitute_html_non_breaking_space(
|
|
$string_contexts->{$context});
|
|
}
|
|
}
|
|
}
|
|
$self->{'translated_direction_strings'} = {};
|
|
foreach my $string_type (keys(%default_translated_directions_strings)) {
|
|
$self->{'translated_direction_strings'}->{$string_type} = {};
|
|
foreach my $direction
|
|
(keys(%{$default_translated_directions_strings{$string_type}})) {
|
|
if ($customized_direction_strings->{$string_type}
|
|
and $customized_direction_strings->{$string_type}->{$direction}) {
|
|
$self->{'translated_direction_strings'}->{$string_type}->{$direction}
|
|
= $customized_direction_strings->{$string_type}->{$direction};
|
|
} else {
|
|
if ($default_translated_directions_strings{$string_type}->{$direction}
|
|
->{'converted'}) {
|
|
$self->{'translated_direction_strings'}->{$string_type}
|
|
->{$direction} = {'converted' => {}};
|
|
foreach my $context ('normal', 'string') {
|
|
$self->{'translated_direction_strings'}->{$string_type}
|
|
->{$direction}->{'converted'}->{$context}
|
|
= $default_translated_directions_strings{$string_type}
|
|
->{$direction}->{'converted'};
|
|
}
|
|
} else {
|
|
$self->{'translated_direction_strings'}->{$string_type}->{$direction}
|
|
= $default_translated_directions_strings{$string_type}->{$direction};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$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('OUTPUT_CHARACTERS')
|
|
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 (!exists($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_info
|
|
= Texinfo::Config::GNUT_get_special_element_info();
|
|
|
|
$self->{'special_element_info'} = {};
|
|
foreach my $type (keys(%default_special_element_info)) {
|
|
$self->{'special_element_info'}->{$type} = {};
|
|
foreach my $special_element_variety
|
|
(keys(%{$default_special_element_info{$type}})) {
|
|
if (exists($customized_special_element_info->{$type})
|
|
and exists($customized_special_element_info
|
|
->{$type}->{$special_element_variety})) {
|
|
$self->{'special_element_info'}->{$type}->{$special_element_variety}
|
|
= $customized_special_element_info->{$type}->{$special_element_variety};
|
|
} else {
|
|
$self->{'special_element_info'}->{$type}->{$special_element_variety}
|
|
= $default_special_element_info{$type}->{$special_element_variety};
|
|
}
|
|
}
|
|
}
|
|
|
|
$self->{'translated_special_element_info'} = {};
|
|
foreach my $type (keys(%default_translated_special_element_info)) {
|
|
$self->{'special_element_info'}->{$type} = {};
|
|
$self->{'special_element_info'}->{$type.'_tree'} = {};
|
|
$self->{'translated_special_element_info'}->{$type.'_tree'} = [$type, {}];
|
|
foreach my $special_element_variety
|
|
(keys(%{$default_translated_special_element_info{$type}})) {
|
|
if (exists($customized_special_element_info->{$type})
|
|
and exists($customized_special_element_info
|
|
->{$type}->{$special_element_variety})) {
|
|
$self->{'translated_special_element_info'}->{$type.'_tree'}
|
|
->[1]->{$special_element_variety}
|
|
= $customized_special_element_info->{$type}->{$special_element_variety};
|
|
} else {
|
|
$self->{'translated_special_element_info'}->{$type.'_tree'}
|
|
->[1]->{$special_element_variety}
|
|
= $default_translated_special_element_info{$type}
|
|
->{$special_element_variety};
|
|
}
|
|
}
|
|
}
|
|
|
|
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 = "<style type=\"text/css\">\n<!--\n";
|
|
$css_text .= join('', @css_import_lines) . "\n"
|
|
if (@css_import_lines);
|
|
foreach my $element_class (@css_element_classes) {
|
|
my $css_style = $self->css_get_info('style', $element_class);
|
|
$css_text .= "$element_class {$css_style}\n"
|
|
if defined($css_style );
|
|
}
|
|
$css_text .= join('', @css_rule_lines) . "\n"
|
|
if (@css_rule_lines);
|
|
$css_text .= "-->\n</style>\n";
|
|
foreach my $ref (@$css_refs) {
|
|
$css_text .= $self->close_html_lone_element(
|
|
'<link rel="stylesheet" type="text/css" href="'.
|
|
$self->url_protect_url_text($ref).'"')."\n";
|
|
}
|
|
return $css_text;
|
|
}
|
|
|
|
sub _process_css_file($$$)
|
|
{
|
|
my $self = shift;
|
|
my $fh =shift;
|
|
my $file = shift;
|
|
|
|
my $in_rules = 0;
|
|
my $in_comment = 0;
|
|
my $in_import = 0;
|
|
my $in_string = 0;
|
|
my $rules = [];
|
|
my $imports = [];
|
|
my $line_nr = 0;
|
|
while (my $line = <$fh>) {
|
|
$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. $normalized is the normalized label name
|
|
# and $node_contents is the label contents. Labels are typically associated
|
|
# to @node, @anchor or @float and to external nodes.
|
|
sub _normalized_label_id_file($$$)
|
|
{
|
|
my $self = shift;
|
|
my $normalized = shift;
|
|
my $node_contents = shift;
|
|
|
|
my $target;
|
|
if (!defined($normalized)) {
|
|
$normalized = Texinfo::Convert::NodeNameNormalization::normalize_node(
|
|
{ 'contents' => $node_contents });
|
|
}
|
|
|
|
if (defined($normalized)) {
|
|
$target = _normalized_to_id($normalized);
|
|
} else {
|
|
$target = '';
|
|
}
|
|
# to find out the Top node, one could check $normalized
|
|
if (defined($self->{'file_id_setting'}->{'label_target_name'})) {
|
|
$target = &{$self->{'file_id_setting'}->{'label_target_name'}}($self,
|
|
$normalized, $node_contents, $target);
|
|
}
|
|
|
|
my $filename = $self->node_information_filename($normalized,
|
|
$node_contents);
|
|
|
|
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') {
|
|
# @top is allowed to be empty. In that case it gets this target name
|
|
$target_base = 'SEC_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, @node, @float label,
|
|
# @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 (sort(keys(%{$self->{'labels'}}))) {
|
|
my $target_element = $self->{'labels'}->{$label};
|
|
my $label_element = Texinfo::Common::get_label_element($target_element);
|
|
my ($node_filename, $target)
|
|
= $self->_normalized_label_id_file($target_element->{'extra'}->{'normalized'},
|
|
$label_element->{'contents'});
|
|
$node_filename .= $extension;
|
|
if (defined($self->{'file_id_setting'}->{'node_file_name'})) {
|
|
# a non defined filename is ok if called with convert, but not
|
|
# if output in files. We reset if undef, silently unless verbose
|
|
# in case called by convert.
|
|
my $user_node_filename
|
|
= &{$self->{'file_id_setting'}->{'node_file_name'}}(
|
|
$self, $target_element, $node_filename);
|
|
if (defined($user_node_filename)) {
|
|
$node_filename = $user_node_filename;
|
|
} elsif ($self->get_conf('VERBOSE')) {
|
|
$self->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'
|
|
# uncomment to get the perl object names
|
|
#."($target_element)"
|
|
." \@$target_element->{'cmdname'} $target, $node_filename\n";
|
|
}
|
|
$self->{'targets'}->{$target_element} = {'target' => $target,
|
|
'node_filename' => $node_filename};
|
|
$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($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($current->{'structure'}->{'associated_unit'})."\n" if ($debug);
|
|
$current = $current->{'structure'}->{'associated_unit'};
|
|
} elsif ($current->{'parent'}) {
|
|
#print STDERR "PARENT ".Texinfo::Common::debug_print_element($current->{'parent'})."\n" if ($debug);
|
|
$current = $current->{'parent'};
|
|
} else {
|
|
#print STDERR "UNKNOWN ROOT ".Texinfo::Common::debug_print_element($current)."\n" if ($debug);
|
|
return (undef, $root_command);
|
|
}
|
|
}
|
|
}
|
|
|
|
sub _html_set_pages_files($$$$$$$$)
|
|
{
|
|
my $self = 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;
|
|
# associate a file to the source information leading to set the file
|
|
# name. Use the first element source information associated to a file
|
|
# The source information can be either a tree element associated to
|
|
# the 'file_info_element' key, with a 'file_info_type' 'node' or
|
|
# 'section'... or a specific source associated to the 'file_info_name'
|
|
# key with 'file_info_type' 'special_file', or a source set if
|
|
# nothing was found, with 'file_info_type' 'stand_in_file' and a
|
|
# 'file_info_name'. Redirection files are added in the output()
|
|
# function.
|
|
my %files_source_info = ();
|
|
if (!$self->get_conf('SPLIT')) {
|
|
$filenames_paths{$output_filename} = $output_file;
|
|
foreach my $tree_unit (@$tree_units) {
|
|
$unit_file_name_paths{$tree_unit} = $output_filename;
|
|
}
|
|
$files_source_info{$output_filename}
|
|
= {'file_info_type' => 'special_file',
|
|
'file_info_name' => 'non_split'};
|
|
} 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;
|
|
$files_source_info{$top_node_filename}
|
|
= {'file_info_type' => 'special_file',
|
|
'file_info_name' => 'Top'};
|
|
}
|
|
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;
|
|
my $file_source_info = {'file_info_type' => 'stand_in_file',
|
|
'file_info_name' => 'unknown_node'};
|
|
$files_source_info{$node_filename} = $file_source_info
|
|
unless ($files_source_info{$node_filename});
|
|
} 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'};
|
|
my $file_source_info = {'file_info_type' => 'node',
|
|
'file_info_element' => $root_command};
|
|
$files_source_info{$node_filename} = $file_source_info
|
|
unless ($files_source_info{$node_filename}
|
|
and $files_source_info{$node_filename}
|
|
->{'file_info_type'} ne 'stand_in_file');
|
|
}
|
|
$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;
|
|
$files_source_info{$top_node_filename}
|
|
= {'file_info_type' => 'special_file',
|
|
'file_info_name' => 'Top'};
|
|
} else {
|
|
my $section_filename
|
|
= $self->{'targets'}->{$command}->{'section_filename'};
|
|
$filenames_paths{$section_filename} = undef;
|
|
$unit_file_name_paths{$file_tree_unit} = $section_filename;
|
|
$files_source_info{$section_filename}
|
|
= {'file_info_type' => 'section',
|
|
'file_info_element' => $command}
|
|
unless($files_source_info{$section_filename}
|
|
and $files_source_info{$section_filename}
|
|
->{'file_info_type'} ne 'stand_in_file');
|
|
}
|
|
} 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;
|
|
$files_source_info{$top_node_filename}
|
|
= {'file_info_type' => 'stand_in_file',
|
|
'file_info_name' => 'Top'}
|
|
unless($files_source_info{$top_node_filename});
|
|
} else {
|
|
my $filename = $document_name . "_$file_nr";
|
|
$filename .= $extension;
|
|
$filenames_paths{$filename} = undef;
|
|
$unit_file_name_paths{$file_tree_unit} = $filename;
|
|
$files_source_info{$filename}
|
|
= {'file_info_type' => 'stand_in_file',
|
|
'file_info_name' => 'unknown'}
|
|
unless($files_source_info{$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};
|
|
# check
|
|
if (!$files_source_info{$filename}) {
|
|
print STDERR "BUG: no files_source_info: $filename\n";
|
|
}
|
|
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;
|
|
$files_source_info{$filename} = {'file_info_type' => 'special_file',
|
|
'file_info_name' => 'user_defined'};
|
|
}
|
|
}
|
|
$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 '
|
|
# uncomment for perl object name
|
|
#."$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) {
|
|
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'
|
|
# uncomment for perl object name
|
|
#." $special_element"
|
|
.": $filename($self->{'file_counters'}->{$filename})\n"
|
|
if ($self->get_conf('DEBUG'));
|
|
my $file_source_info = {'file_info_element' => $special_element,
|
|
'file_info_type' => 'special_element'};
|
|
$files_source_info{$filename} = $file_source_info
|
|
unless($files_source_info{$filename}
|
|
and $files_source_info{$filename}->{'file_info_type'}
|
|
ne 'stand_in_file');
|
|
}
|
|
}
|
|
}
|
|
foreach my $filename (keys(%filenames_paths)) {
|
|
$self->set_file_path($filename, $destination_directory,
|
|
$filenames_paths{$filename});
|
|
}
|
|
return %files_source_info;
|
|
}
|
|
|
|
# $ROOT is a parsed Texinfo tree. Return a list of the "elements" we need to
|
|
# output in the HTML file(s). Each "element" is what can go in one HTML file,
|
|
# such as the content between @node lines in the Texinfo source.
|
|
# Also 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($_)}
|
|
sort(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 ($special_elements and defined($tree_units) and scalar(@$tree_units)) {
|
|
my $previous_tree_unit = $tree_units->[-1];
|
|
foreach my $special_element (@$special_elements) {
|
|
$special_element->{'structure'}->{'unit_prev'} = $previous_tree_unit;
|
|
$previous_tree_unit->{'structure'}->{'unit_next'} = $special_element;
|
|
$previous_tree_unit = $special_element;
|
|
}
|
|
}
|
|
|
|
#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) {
|
|
if ($self->get_conf('CONTENTS_OUTPUT_LOCATION') eq 'separate_element') {
|
|
foreach my $cmdname ('shortcontents', 'contents') {
|
|
my $special_element_variety
|
|
= $contents_command_special_element_variety{$cmdname};
|
|
if ($self->get_conf($cmdname)) {
|
|
$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 = [];
|
|
# sort special elements according to their index order from
|
|
# special_element_info 'order'.
|
|
# First reverse the hash, using arrays in case some elements are at the
|
|
# same index, and sort to get alphabetically sorted special element
|
|
# varieties that are at the same index.
|
|
my %special_elements_indices;
|
|
foreach my $special_element_variety
|
|
(sort($self->special_element_info('order'))) {
|
|
my $index = $self->special_element_info('order', $special_element_variety);
|
|
$special_elements_indices{$index} = []
|
|
if (not exists ($special_elements_indices{$index}));
|
|
push @{$special_elements_indices{$index}}, $special_element_variety;
|
|
}
|
|
# now sort according to indices
|
|
my @sorted_elements_varieties;
|
|
foreach my $index (sort { $a <=> $b } (keys(%special_elements_indices))) {
|
|
push @sorted_elements_varieties, @{$special_elements_indices{$index}};
|
|
}
|
|
|
|
foreach my $special_element_variety (@sorted_elements_varieties) {
|
|
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->special_element_info('direction', $special_element_variety);
|
|
$self->{'special_elements_directions'}->{$special_element_direction}
|
|
= $element;
|
|
push @$special_elements, $element;
|
|
|
|
my $target
|
|
= $self->special_element_info('target', $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)) {
|
|
my $special_element_file_string =
|
|
$self->special_element_info('file_string', $special_element_variety);
|
|
$special_element_file_string = '' if (!defined($special_element_file_string));
|
|
$default_filename = $document_name . $special_element_file_string;
|
|
$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'
|
|
# uncomment for the perl object name
|
|
#." $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->special_element_info('direction', $special_element_variety);
|
|
$self->{'special_elements_directions'}->{$special_element_direction}
|
|
= $contents_element;
|
|
my $target
|
|
= $self->special_element_info('target', $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'
|
|
# uncomment to get the perl obect name
|
|
#." $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 associated unit for node_top: "
|
|
.Texinfo::Common::debug_print_element($node_top, 1);
|
|
}
|
|
$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"
|
|
# uncomment to get the perl object name
|
|
# ."($global_element)"
|
|
.': '. Texinfo::Structuring::root_or_external_element_cmd_texi($global_element)."\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sub _prepare_index_entries($)
|
|
{
|
|
my $self = shift;
|
|
|
|
my $indices_information = $self->{'indices_information'};
|
|
if ($indices_information) {
|
|
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($indices_information);
|
|
my $index_entries_sort_strings;
|
|
($self->{'index_entries_by_letter'}, $index_entries_sort_strings)
|
|
= Texinfo::Structuring::sort_indices($self,
|
|
$self, $merged_index_entries,
|
|
$indices_information,
|
|
'by_letter');
|
|
$self->{'index_entries'} = $merged_index_entries;
|
|
|
|
foreach my $index_name (sort(keys(%$indices_information))) {
|
|
foreach my $index_entry (@{$indices_information->{$index_name}
|
|
->{'index_entries'}}) {
|
|
my $main_entry_element = $index_entry->{'entry_element'};
|
|
# does not refer to the document
|
|
next if ($main_entry_element->{'extra'}
|
|
and ($main_entry_element->{'extra'}->{'seeentry'}
|
|
or $main_entry_element->{'extra'}->{'seealso'}));
|
|
my $region = '';
|
|
$region = "$main_entry_element->{'extra'}->{'element_region'}-"
|
|
if (defined($main_entry_element->{'extra'}->{'element_region'}));
|
|
my $entry_reference_content_element
|
|
= Texinfo::Common::index_content_element($main_entry_element, 1);
|
|
my @contents = ($entry_reference_content_element);
|
|
my $subentries_tree
|
|
= $self->comma_index_subentries_tree($main_entry_element,
|
|
' ');
|
|
if (defined($subentries_tree)) {
|
|
push @contents, @{$subentries_tree->{'contents'}};
|
|
}
|
|
my $trimmed_contents
|
|
= Texinfo::Common::trim_spaces_comment_from_content(\@contents);
|
|
my $normalized_index =
|
|
Texinfo::Convert::NodeNameNormalization::normalize_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;
|
|
my $target_element = $main_entry_element;
|
|
$target_element = $index_entry->{'entry_associated_element'}
|
|
if ($index_entry->{'entry_associated_element'});
|
|
$self->{'targets'}->{$target_element} = {'target' => $target, };
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sub _prepare_footnotes($)
|
|
{
|
|
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'
|
|
# uncomment for the perl object name
|
|
#." $footnote"
|
|
.": target $footid, nr $footnote_nr\n"
|
|
.Texinfo::Convert::Texinfo::convert_to_texinfo($footnote)."\n"
|
|
if ($self->get_conf('DEBUG'));
|
|
}
|
|
}
|
|
}
|
|
|
|
sub _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->{'normalized'},
|
|
$external_node->{'node_content'});
|
|
|
|
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)});
|
|
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 .= "<li><a${href_attribute}$accesskey>$text</a>";
|
|
} else {
|
|
$result .= "<li>$text";
|
|
}
|
|
$result .= "</li>\n";
|
|
}
|
|
}
|
|
$result .= "</ul>\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'}
|
|
and $section->{'extra'}->{'associated_node'}->{'extra'}->{'isindex'}) {
|
|
$rel = ' rel="index"';
|
|
}
|
|
$result .= "<li><a${toc_name_attribute}${href_attribute}$rel>$text</a>";
|
|
} else {
|
|
$result .= "<li>$text";
|
|
}
|
|
}
|
|
} elsif ($section->{'structure'}->{'section_childs'}
|
|
and @{$section->{'structure'}->{'section_childs'}}
|
|
and $toplevel_contents) {
|
|
$result .= "<li>";
|
|
}
|
|
# 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 .= "</li>\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 .= "</li>\n" unless ($section->{'cmdname'} eq 'top');
|
|
last;
|
|
}
|
|
while ($section->{'structure'}->{'section_up'}) {
|
|
$section = $section->{'structure'}->{'section_up'};
|
|
$result .= "</li>\n"
|
|
. ' ' x (2*($section->{'structure'}->{'section_level'} - $min_root_level))
|
|
. "</ul>";
|
|
if ($section eq $top_section) {
|
|
$result .= "</li>\n" if ($toplevel_contents);
|
|
last SECTION;
|
|
}
|
|
if ($section->{'structure'}->{'section_next'}) {
|
|
$result .= "</li>\n";
|
|
$section = $section->{'structure'}->{'section_next'};
|
|
last;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (@{$section_root->{'structure'}->{'section_childs'}} > 1) {
|
|
$result .= "\n</ul>";
|
|
}
|
|
if ($contents and !defined($self->get_conf('AFTER_TOC_LINES'))
|
|
or (!$contents and !defined($self->get_conf('AFTER_SHORT_TOC_LINES')))) {
|
|
$result .= "\n</div>\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.'</span>';
|
|
}
|
|
$program_text .= "<p>
|
|
$program_string
|
|
</p>";
|
|
}
|
|
|
|
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 .=
|
|
'<a href="'.$self->url_protect_url_text($js_path).'" rel="jslicense"><small>'
|
|
.$self->convert_tree($self->gdt('JavaScript license information'))
|
|
.'</small></a>';
|
|
}
|
|
}
|
|
|
|
return "${program_text}
|
|
|
|
$pre_body_close
|
|
</body>
|
|
</html>
|
|
";
|
|
}
|
|
|
|
sub _root_html_element_attributes_string($)
|
|
{
|
|
my $self = shift;
|
|
if (defined($self->get_conf('HTML_ROOT_ELEMENT_ATTRIBUTES'))
|
|
and $self->get_conf('HTML_ROOT_ELEMENT_ATTRIBUTES') ne '') {
|
|
return ' '.$self->get_conf('HTML_ROOT_ELEMENT_ATTRIBUTES');
|
|
}
|
|
return '';
|
|
}
|
|
|
|
# This is used for normal output files and other files, like
|
|
# redirection file headers. $COMMAND is the tree element for
|
|
# a @node that is being output in the file.
|
|
sub _file_header_information($$;$)
|
|
{
|
|
my $self = shift;
|
|
my $command = shift;
|
|
my $filename = shift;
|
|
|
|
my $title;
|
|
if ($command) {
|
|
my $command_string = $self->command_text($command, 'string');
|
|
if (defined($command_string)
|
|
and $command_string ne $self->get_info('title_string')) {
|
|
my $element_tree;
|
|
if ($self->get_conf('SECTION_NAME_IN_TITLE')
|
|
and $command->{'extra'}
|
|
and $command->{'extra'}->{'associated_section'}
|
|
and $command->{'extra'}->{'associated_section'}->{'args'}
|
|
and $command->{'extra'}->{'associated_section'}->{'args'}->[0]) {
|
|
$element_tree = $command->{'extra'}->{'associated_section'}->{'args'}->[0];
|
|
} else {
|
|
$element_tree = $self->command_text($command, 'tree');
|
|
}
|
|
# TRANSLATORS: sectioning element title for the page header
|
|
my $title_tree = $self->gdt('{element_text} ({title})',
|
|
{ 'title' => $self->get_info('title_tree'),
|
|
'element_text' => $element_tree });
|
|
$title = $self->convert_tree_new_formatting_context(
|
|
{'type' => '_string', 'contents' => [$title_tree]},
|
|
$command->{'cmdname'}, 'element_title');
|
|
}
|
|
}
|
|
$title = $self->get_info('title_string') if (!defined($title));
|
|
|
|
my $description = $self->get_info('documentdescription_string');
|
|
$description = $title
|
|
if (not defined($description) or $description eq '');
|
|
$description = $self->close_html_lone_element(
|
|
"<meta name=\"description\" content=\"$description\"" )
|
|
if ($description ne '');
|
|
my $encoding = '';
|
|
$encoding
|
|
= $self->close_html_lone_element(
|
|
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=".
|
|
$self->get_conf('OUTPUT_ENCODING_NAME')."\"" )
|
|
if (defined($self->get_conf('OUTPUT_ENCODING_NAME'))
|
|
and ($self->get_conf('OUTPUT_ENCODING_NAME') ne ''));
|
|
|
|
my $date = '';
|
|
if ($self->get_conf('DATE_IN_HEADER')) {
|
|
my $today = $self->convert_tree_new_formatting_context(
|
|
{'cmdname' => 'today'}, 'DATE_IN_HEADER');
|
|
$date =
|
|
$self->close_html_lone_element(
|
|
"<meta name=\"date\" content=\"$today\"")."\n";
|
|
}
|
|
|
|
my $css_lines = &{$self->formatting_function('format_css_lines')}($self,
|
|
$filename);
|
|
|
|
my $doctype = $self->get_conf('DOCTYPE');
|
|
my $root_html_element_attributes = $self->_root_html_element_attributes_string();
|
|
my $bodytext = $self->get_conf('BODYTEXT');
|
|
if ($self->get_conf('HTML_MATH') and $self->get_conf('HTML_MATH') eq 'mathjax'
|
|
and $self->get_file_information('mathjax', $filename)) {
|
|
$bodytext .= ' class="tex2jax_ignore"';
|
|
}
|
|
my $copying_comment = $self->get_info('copying_comment');
|
|
$copying_comment = ''
|
|
if (not defined($copying_comment));
|
|
my $after_body_open = '';
|
|
$after_body_open = $self->get_conf('AFTER_BODY_OPEN')
|
|
if (defined($self->get_conf('AFTER_BODY_OPEN')));
|
|
my $extra_head = '';
|
|
$extra_head = $self->get_conf('EXTRA_HEAD')
|
|
if (defined($self->get_conf('EXTRA_HEAD')));
|
|
my $program_and_version = $self->get_conf('PACKAGE_AND_VERSION');
|
|
my $program_homepage = $self->get_conf('PACKAGE_URL');
|
|
my $program = $self->get_conf('PROGRAM');
|
|
my $generator = '';
|
|
if (defined($program) and $program ne '') {
|
|
$generator =
|
|
$self->close_html_lone_element(
|
|
"<meta name=\"Generator\" content=\"$program\"") . "\n";
|
|
}
|
|
|
|
if (defined($self->get_conf('INFO_JS_DIR'))) {
|
|
if (!$self->get_conf('SPLIT')) {
|
|
$self->document_error($self,
|
|
sprintf(__("%s not meaningful for non-split output"),
|
|
'INFO_JS_DIR'));
|
|
} else {
|
|
my $jsdir = $self->get_conf('INFO_JS_DIR');
|
|
if ($jsdir eq '.') {
|
|
$jsdir = '';
|
|
} else {
|
|
$jsdir =~ s,/*$,/,; # append a single slash
|
|
}
|
|
|
|
$extra_head .= $self->close_html_lone_element(
|
|
'<link rel="stylesheet" type="text/css" href="'.
|
|
$self->url_protect_url_text($jsdir).'info.css"')."\n".
|
|
'<script src="'.$self->url_protect_url_text($jsdir)
|
|
.'modernizr.js" type="text/javascript"></script>
|
|
<script src="'.$self->url_protect_url_text($jsdir)
|
|
.'info.js" type="text/javascript"></script>';
|
|
}
|
|
}
|
|
if ((defined($self->get_conf('HTML_MATH'))
|
|
and $self->get_conf('HTML_MATH') eq 'mathjax')
|
|
and ($self->get_file_information('mathjax', $filename)
|
|
# FIXME do we really want the script element if no math was seen?
|
|
or !$self->get_conf('SPLIT'))) {
|
|
my $mathjax_script = $self->get_conf('MATHJAX_SCRIPT');
|
|
|
|
$extra_head .=
|
|
"<script type='text/javascript'>
|
|
MathJax = {
|
|
options: {
|
|
skipHtmlTags: {'[-]': ['pre']},
|
|
ignoreHtmlClass: 'tex2jax_ignore',
|
|
processHtmlClass: 'tex2jax_process'
|
|
},
|
|
};
|
|
</script>"
|
|
.'<script type="text/javascript" id="MathJax-script" async
|
|
src="'.$self->url_protect_url_text($mathjax_script).'">
|
|
</script>';
|
|
|
|
}
|
|
|
|
return ($title, $description, $encoding, $date, $css_lines,
|
|
$doctype, $root_html_element_attributes, $bodytext, $copying_comment,
|
|
$after_body_open, $extra_head, $program_and_version, $program_homepage,
|
|
$program, $generator);
|
|
}
|
|
|
|
sub _get_links($$$$)
|
|
{
|
|
my $self = shift;
|
|
my $filename = shift;
|
|
my $element = shift;
|
|
my $node_command = shift;
|
|
|
|
my $links = '';
|
|
if ($self->get_conf('USE_LINKS')) {
|
|
my $link_buttons = $self->get_conf('LINKS_BUTTONS');
|
|
foreach my $link (@$link_buttons) {
|
|
my $link_href = $self->from_element_direction($link, 'href', $element,
|
|
$filename, $node_command);
|
|
#print STDERR "$link -> $link_href \n";
|
|
if ($link_href and $link_href ne '') {
|
|
my $link_string = $self->from_element_direction($link, 'string',
|
|
$element);
|
|
my $link_title = '';
|
|
$link_title = " title=\"$link_string\"" if (defined($link_string));
|
|
my $rel = '';
|
|
my $button_rel = $self->direction_string($link, 'rel', 'string');
|
|
$rel = " rel=\"".$button_rel.'"' if (defined($button_rel));
|
|
$links .= $self->close_html_lone_element(
|
|
"<link href=\"$link_href\"${rel}${link_title}")."\n";
|
|
}
|
|
}
|
|
}
|
|
return $links;
|
|
}
|
|
|
|
sub _default_format_begin_file($$$)
|
|
{
|
|
my $self = shift;
|
|
my $filename = shift;
|
|
my $element = shift;
|
|
|
|
my ($element_command, $node_command, $command_for_title);
|
|
if ($element) {
|
|
$element_command = $self->tree_unit_element_command($element);
|
|
$node_command = $element_command;
|
|
if ($element_command and $element_command->{'cmdname'}
|
|
and $element_command->{'cmdname'} ne 'node'
|
|
and $element_command->{'extra'}
|
|
and $element_command->{'extra'}->{'associated_node'}) {
|
|
$node_command = $element_command->{'extra'}->{'associated_node'};
|
|
}
|
|
|
|
$command_for_title = $element_command if ($self->get_conf('SPLIT'));
|
|
}
|
|
|
|
my ($title, $description, $encoding, $date, $css_lines,
|
|
$doctype, $root_html_element_attributes, $bodytext, $copying_comment,
|
|
$after_body_open, $extra_head, $program_and_version, $program_homepage,
|
|
$program, $generator) = $self->_file_header_information($command_for_title,
|
|
$filename);
|
|
|
|
my $links = $self->_get_links($filename, $element, $node_command);
|
|
|
|
my $result = "$doctype
|
|
<html${root_html_element_attributes}>
|
|
<!-- Created by $program_and_version, $program_homepage -->
|
|
<head>
|
|
$encoding
|
|
$copying_comment<title>$title</title>
|
|
|
|
$description\n".
|
|
$self->close_html_lone_element(
|
|
"<meta name=\"keywords\" content=\"$title\"")."\n".
|
|
$self->close_html_lone_element(
|
|
"<meta name=\"resource-type\" content=\"document\"")."\n".
|
|
$self->close_html_lone_element(
|
|
"<meta name=\"distribution\" content=\"global\"") . "\n" .
|
|
${generator} . ${date} .
|
|
$self->close_html_lone_element(
|
|
"<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"")."\n".
|
|
"
|
|
${links}$css_lines
|
|
$extra_head
|
|
</head>
|
|
|
|
<body $bodytext>
|
|
$after_body_open";
|
|
|
|
return $result;
|
|
}
|
|
|
|
sub _default_format_node_redirection_page($$)
|
|
{
|
|
my $self = shift;
|
|
my $command = shift;
|
|
|
|
my ($title, $description, $encoding, $date, $css_lines,
|
|
$doctype, $root_html_element_attributes, $bodytext, $copying_comment,
|
|
$after_body_open, $extra_head, $program_and_version, $program_homepage,
|
|
$program, $generator) = $self->_file_header_information($command);
|
|
|
|
my $name = $self->command_text($command);
|
|
my $href = $self->command_href($command);
|
|
my $direction = "<a href=\"$href\">$name</a>";
|
|
my $string = $self->convert_tree(
|
|
$self->gdt('The node you are looking for is at {href}.',
|
|
{ 'href' => {'type' => '_converted', 'text' => $direction }}));
|
|
my $result = "$doctype
|
|
<html${root_html_element_attributes}>
|
|
<!-- Created by $program_and_version, $program_homepage -->
|
|
<!-- This file redirects to the location of a node or anchor -->
|
|
<head>
|
|
$encoding
|
|
$copying_comment<title>$title</title>
|
|
|
|
$description\n".
|
|
$self->close_html_lone_element(
|
|
"<meta name=\"keywords\" content=\"$title\"")."\n".
|
|
$self->close_html_lone_element(
|
|
"<meta name=\"resource-type\" content=\"document\"")."\n".
|
|
$self->close_html_lone_element(
|
|
"<meta name=\"distribution\" content=\"global\"") . "\n" .
|
|
${generator} . ${date} . "$css_lines\n".
|
|
$self->close_html_lone_element(
|
|
"<meta http-equiv=\"Refresh\" content=\"0; url=$href\"")."\n".
|
|
$self->close_html_lone_element(
|
|
"<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"")."\n".
|
|
"$extra_head
|
|
</head>
|
|
|
|
<body $bodytext>
|
|
$after_body_open
|
|
<p>$string</p>
|
|
</body>
|
|
";
|
|
return $result;
|
|
}
|
|
|
|
sub _default_format_footnotes_sequence($)
|
|
{
|
|
my $self = shift;
|
|
|
|
my @pending_footnotes = $self->get_pending_footnotes();
|
|
my $result = '';
|
|
foreach my $pending_footnote_info_array (@pending_footnotes) {
|
|
my ($command, $footid, $docid, $number_in_doc,
|
|
$footnote_location_filename, $multi_expanded_region)
|
|
= @$pending_footnote_info_array;
|
|
my $footnote_location_href = $self->footnote_location_href($command, undef,
|
|
$docid, $footnote_location_filename);
|
|
# NOTE the @-commands in @footnote that are formatted differently depending
|
|
# on $self->in_multi_expanded() cannot know that the original context
|
|
# of the @footnote in the main document was $multi_expanded_region.
|
|
# We do not want to set multi_expanded in customizable code. However, it
|
|
# could be possible to set a shared_conversion_state based on $multi_expanded_region
|
|
# and have all the conversion functions calling $self->in_multi_expanded()
|
|
# also check the shared_conversion_state. The special situations
|
|
# with those @-commands in @footnote in multi expanded
|
|
# region do not justify this additional code and complexity. The consequences
|
|
# should only be redundant anchors HTML elements.
|
|
my $footnote_text
|
|
= $self->convert_tree_new_formatting_context($command->{'args'}->[0],
|
|
"$command->{'cmdname'} $number_in_doc $footid");
|
|
chomp ($footnote_text);
|
|
$footnote_text .= "\n";
|
|
|
|
$result .= $self->html_attribute_class('h5', ['footnote-body-heading']) . '>'.
|
|
"<a id=\"$footid\" href=\"$footnote_location_href\">($number_in_doc)</a></h5>\n"
|
|
. $footnote_text;
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
sub _default_format_footnotes_segment($)
|
|
{
|
|
my $self = shift;
|
|
my $foot_lines
|
|
= &{$self->formatting_function('format_footnotes_sequence')}($self);
|
|
return '' if ($foot_lines eq '');
|
|
my $class = $self->special_element_info('class', 'footnotes');
|
|
my $result = $self->html_attribute_class('div', [$class.'-segment']).">\n";
|
|
$result .= $self->get_conf('DEFAULT_RULE') . "\n"
|
|
if (defined($self->get_conf('DEFAULT_RULE'))
|
|
and $self->get_conf('DEFAULT_RULE') ne '');
|
|
my $footnote_heading_tree = $self->special_element_info('heading_tree',
|
|
'footnotes');
|
|
my $footnote_heading;
|
|
if (defined($footnote_heading_tree)) {
|
|
$footnote_heading
|
|
= $self->convert_tree($footnote_heading_tree,
|
|
'convert footnotes special heading');
|
|
} else {
|
|
$footnote_heading = '';
|
|
}
|
|
my $level = $self->get_conf('FOOTNOTE_END_HEADER_LEVEL');
|
|
$result .= &{$self->formatting_function('format_heading_text')}($self, undef,
|
|
[$class.'-heading'], $footnote_heading, $level)."\n";
|
|
$result .= $foot_lines;
|
|
$result .= "</div>\n";
|
|
return $result;
|
|
}
|
|
|
|
sub _default_format_special_body_about($$$)
|
|
{
|
|
my $self = shift;
|
|
my $special_type = shift;
|
|
my $element = shift;
|
|
|
|
my $about = '';
|
|
if ($self->get_conf('PROGRAM_NAME_IN_ABOUT')) {
|
|
$about .= "<p>\n";
|
|
$about .= ' '.&{$self->formatting_function('format_program_string')}($self) ."\n";
|
|
$about .= "</p>\n";
|
|
}
|
|
$about .= <<EOT;
|
|
<p>
|
|
EOT
|
|
$about .= $self->convert_tree(
|
|
$self->gdt(' The buttons in the navigation panels have the following meaning:'))
|
|
. "\n";
|
|
$about .= <<EOT;
|
|
</p>
|
|
<table border="1">
|
|
<tr>
|
|
EOT
|
|
# TRANSLATORS: direction column header in the navigation help
|
|
$about .= ' <th> ' . $self->convert_tree($self->gdt('Button')) . " </th>\n" .
|
|
# TRANSLATORS: button label column header in the navigation help
|
|
' <th> ' . $self->convert_tree($self->gdt('Name')) . " </th>\n" .
|
|
# TRANSLATORS: direction description column header in the navigation help
|
|
' <th> ' . $self->convert_tree($self->gdt('Go to')) . " </th>\n" .
|
|
# TRANSLATORS: section reached column header in the navigation help
|
|
' <th> ' . $self->convert_tree($self->gdt('From 1.2.3 go to')) . "</th>\n"
|
|
. " </tr>\n";
|
|
|
|
foreach my $button_spec (@{$self->get_conf('SECTION_BUTTONS')}) {
|
|
next if ($button_spec eq ' ' or ref($button_spec) eq 'CODE'
|
|
or ref($button_spec) eq 'SCALAR'
|
|
or (ref($button_spec) eq 'ARRAY' and scalar(@$button_spec) != 2));
|
|
my $button;
|
|
if (ref($button_spec) eq 'ARRAY') {
|
|
$button = $button_spec->[0];
|
|
} else {
|
|
$button = $button_spec;
|
|
}
|
|
$about .= " <tr>\n ".$self->html_attribute_class('td',
|
|
['button-direction-about']) .'>';
|
|
# if the button spec is an array we do not knwow what the button
|
|
# looks like, so we do not show the button but still show explanations.
|
|
if (ref($button_spec) ne 'ARRAY') {
|
|
my $button_name_string
|
|
= $self->direction_string($button, 'button', 'string');
|
|
# FIXME strip FirstInFile from $button to get active icon file?
|
|
$about .=
|
|
(($self->get_conf('ICONS') &&
|
|
$self->get_conf('ACTIVE_ICONS')->{$button}) ?
|
|
&{$self->formatting_function('format_button_icon_img')}($self,
|
|
$button_name_string, $self->get_conf('ACTIVE_ICONS')->{$button})
|
|
: ' [' . $self->direction_string($button, 'text') . '] ');
|
|
}
|
|
$about .= "</td>\n";
|
|
my $button_name
|
|
= $self->direction_string($button, 'button');
|
|
$about .=
|
|
' '.$self->html_attribute_class('td', ['name-direction-about']).'>'
|
|
.$button_name."</td>
|
|
<td>".$self->direction_string($button, 'description')."</td>
|
|
<td>".$self->direction_string($button, 'example')."</td>
|
|
</tr>
|
|
";
|
|
}
|
|
|
|
$about .= <<EOT;
|
|
</table>
|
|
|
|
<p>
|
|
EOT
|
|
$about .= $self->convert_tree($self->gdt(' where the @strong{ Example } assumes that the current position is at @strong{ Subsubsection One-Two-Three } of a document of the following structure:')) . "\n";
|
|
|
|
# where the <strong> Example </strong> assumes that the current position
|
|
# is at <strong> Subsubsection One-Two-Three </strong> of a document of
|
|
# the following structure:
|
|
$about .= <<EOT;
|
|
</p>
|
|
|
|
<ul>
|
|
EOT
|
|
my $non_breaking_space = $self->get_info('non_breaking_space');
|
|
# TRANSLATORS: example name of section for section 1
|
|
$about .= ' <li> 1. ' . $self->convert_tree($self->gdt('Section One')) . "\n" .
|
|
" <ul>\n" .
|
|
# TRANSLATORS: example name of section for section 1.1
|
|
' <li>1.1 ' . $self->convert_tree($self->gdt('Subsection One-One')) . "\n";
|
|
$about .= <<EOT;
|
|
<ul>
|
|
<li>...</li>
|
|
</ul>
|
|
</li>
|
|
EOT
|
|
# TRANSLATORS: example name of section for section 1.2
|
|
$about .= ' <li>1.2 ' . $self->convert_tree($self->gdt('Subsection One-Two')) . "\n" .
|
|
" <ul>\n" .
|
|
# TRANSLATORS: example name of section for section 1.2.1
|
|
' <li>1.2.1 ' . $self->convert_tree($self->gdt('Subsubsection One-Two-One')) . "</li>\n" .
|
|
# TRANSLATORS: example name of section for section 1.2.2
|
|
' <li>1.2.2 ' . $self->convert_tree($self->gdt('Subsubsection One-Two-Two')) . "</li>\n" .
|
|
# TRANSLATORS: example name of section for section 1.2.3
|
|
' <li>1.2.3 ' . $self->convert_tree($self->gdt('Subsubsection One-Two-Three'))
|
|
. " $non_breaking_space $non_breaking_space\n"
|
|
.
|
|
' <strong><== ' . $self->convert_tree($self->gdt('Current Position')) . " </strong></li>\n" .
|
|
# TRANSLATORS: example name of section for section 1.2.3
|
|
' <li>1.2.4 ' . $self->convert_tree($self->gdt('Subsubsection One-Two-Four')) . "</li>\n" .
|
|
" </ul>\n" .
|
|
" </li>\n" .
|
|
# TRANSLATORS: example name of section for section 1.3
|
|
' <li>1.3 ' . $self->convert_tree($self->gdt('Subsection One-Three')) . "\n";
|
|
$about .= <<EOT;
|
|
<ul>
|
|
<li>...</li>
|
|
</ul>
|
|
</li>
|
|
EOT
|
|
# TRANSLATORS: example name of section for section 1.4
|
|
$about .= ' <li>1.4 ' . $self->convert_tree($self->gdt('Subsection One-Four')) . "</li>\n";
|
|
|
|
$about .= <<EOT;
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
EOT
|
|
return $about;
|
|
}
|
|
|
|
sub _default_format_special_body_contents($$$)
|
|
{
|
|
my $self = shift;
|
|
my $special_type = shift;
|
|
my $element = shift;
|
|
|
|
return &{$self->formatting_function('format_contents')}($self, 'contents');
|
|
}
|
|
|
|
sub _default_format_special_body_shortcontents($$$)
|
|
{
|
|
my $self = shift;
|
|
my $special_type = shift;
|
|
my $element = shift;
|
|
|
|
return &{$self->formatting_function('format_contents')}($self, 'shortcontents');
|
|
}
|
|
|
|
sub _default_format_special_body_footnotes($$$)
|
|
{
|
|
my $self = shift;
|
|
my $special_type = shift;
|
|
my $element = shift;
|
|
|
|
return &{$self->formatting_function('format_footnotes_sequence')}($self);
|
|
}
|
|
|
|
sub _do_jslicenses_file {
|
|
my $self = shift;
|
|
my $destination_directory = shift;
|
|
|
|
my $setting = $self->get_conf('JS_WEBLABELS');
|
|
my $path = $self->get_conf('JS_WEBLABELS_FILE');
|
|
|
|
# Possible settings:
|
|
# 'generate' - create file at JS_WEBLABELS_FILE
|
|
# 'reference' - reference file at JS_WEBLABELS_FILE but do not create it
|
|
# 'omit' - do nothing
|
|
return if (!$setting or $setting ne 'generate');
|
|
|
|
my $doctype = $self->get_conf('DOCTYPE');
|
|
my $root_html_element_attributes = $self->_root_html_element_attributes_string();
|
|
my $a = $doctype . "\n" .
|
|
"<html${root_html_element_attributes}>".'<head><title>jslicense labels</title></head>
|
|
<body>
|
|
<table id="jslicense-labels1">
|
|
';
|
|
|
|
my $jslicenses = $self->get_info('jslicenses');
|
|
foreach my $category (sort(keys %$jslicenses)) {
|
|
foreach my $file (sort(keys %{$jslicenses->{$category}})) {
|
|
my $file_info = $jslicenses->{$category}->{$file};
|
|
$a .= "<tr>\n";
|
|
$a .= '<td><a href="'.$self->url_protect_url_text($file)."\">$file</a></td>\n";
|
|
$a .= '<td><a href="'.$self->url_protect_url_text($file_info->[1])
|
|
."\">$file_info->[0]</a></td>\n";
|
|
$a .= '<td><a href="'.$self->url_protect_url_text($file_info->[2])
|
|
."\">$file_info->[2]</a></td>\n";
|
|
$a .= "</tr>\n";
|
|
}
|
|
}
|
|
|
|
$a .= "</table>\n</body></html>\n";
|
|
|
|
if (File::Spec->file_name_is_absolute($path) or $path =~ /^[A-Za-z]*:/) {
|
|
$self->document_warn($self, sprintf(
|
|
__("cannot use absolute path or URL `%s' for JS_WEBLABELS_FILE when generating web labels file"), $path));
|
|
return;
|
|
}
|
|
my $license_file = File::Spec->catdir($destination_directory,
|
|
$path);
|
|
# sequence of bytes
|
|
my ($licence_file_path, $path_encoding)
|
|
= $self->encoded_output_file_name($license_file);
|
|
my ($fh, $error_message_licence_file)
|
|
= Texinfo::Common::output_files_open_out(
|
|
$self->output_files_information(), $self,
|
|
$licence_file_path);
|
|
if (defined($fh)) {
|
|
print $fh $a;
|
|
Texinfo::Common::output_files_register_closed(
|
|
$self->output_files_information(), $licence_file_path);
|
|
if (!close ($fh)) {
|
|
$self->document_error($self,
|
|
sprintf(__("error on closing %s: %s"),
|
|
$license_file, $!));
|
|
}
|
|
} else {
|
|
$self->document_error($self,
|
|
sprintf(__("could not open %s for writing: %s"),
|
|
$license_file, $error_message_licence_file));
|
|
}
|
|
}
|
|
|
|
# FIXME the file opening should be done in main program, only
|
|
# the formatting should be done in customization function. Frames
|
|
# are deprecated in HTML, however, and therefore there is no point
|
|
# in investing time in better code to produce them.
|
|
sub _default_format_frame_files($$)
|
|
{
|
|
my $self = shift;
|
|
my $destination_directory = shift;
|
|
|
|
my $frame_file = $self->{'frame_pages_filenames'}->{'Frame'};
|
|
my $frame_outfile;
|
|
if (defined($destination_directory) and $destination_directory ne '') {
|
|
$frame_outfile = File::Spec->catfile($destination_directory,
|
|
$frame_file);
|
|
} else {
|
|
$frame_outfile = $frame_file;
|
|
}
|
|
|
|
my $toc_frame_file = $self->{'frame_pages_filenames'}->{'Toc_Frame'};
|
|
my $toc_frame_outfile;
|
|
if (defined($destination_directory) and $destination_directory ne '') {
|
|
$toc_frame_outfile = File::Spec->catfile($destination_directory,
|
|
$toc_frame_file);
|
|
} else {
|
|
$toc_frame_outfile = $toc_frame_file;
|
|
}
|
|
# sequence of bytes
|
|
my ($frame_file_path, $frame_path_encoding)
|
|
= $self->encoded_output_file_name($frame_outfile);
|
|
my ($frame_fh, $error_message_frame) = Texinfo::Common::output_files_open_out(
|
|
$self->output_files_information(), $self, $frame_file_path);
|
|
if (defined($frame_fh)) {
|
|
my $doctype = $self->get_conf('FRAMESET_DOCTYPE');
|
|
my $root_html_element_attributes = $self->_root_html_element_attributes_string();
|
|
my $top_file = '';
|
|
my $top_element = $self->global_direction_element('Top');
|
|
if ($top_element) {
|
|
$top_file = $top_element->{'structure'}->{'unit_filename'};
|
|
}
|
|
my $title = $self->{'title_string'};
|
|
print $frame_fh <<EOT;
|
|
$doctype
|
|
<html${root_html_element_attributes}>
|
|
<head><title>$title</title></head>
|
|
<frameset cols="140,*">
|
|
<frame name="toc" src="$toc_frame_file">
|
|
<frame name="main" src="$top_file">
|
|
</frameset>
|
|
</html>
|
|
EOT
|
|
|
|
Texinfo::Common::output_files_register_closed(
|
|
$self->output_files_information(), $frame_file_path);
|
|
if (!close ($frame_fh)) {
|
|
$self->document_error($self,
|
|
sprintf(__("error on closing frame file %s: %s"),
|
|
$frame_outfile, $!));
|
|
return 0;
|
|
}
|
|
} else {
|
|
$self->document_error($self,
|
|
sprintf(__("could not open %s for writing: %s"),
|
|
$frame_outfile, $error_message_frame));
|
|
return 0;
|
|
}
|
|
# sequence of bytes
|
|
my ($toc_frame_path, $toc_frame_path_encoding)
|
|
= $self->encoded_output_file_name($toc_frame_outfile);
|
|
my ($toc_frame_fh, $toc_frame_error_message)
|
|
= Texinfo::Common::output_files_open_out(
|
|
$self->output_files_information(), $self,
|
|
$toc_frame_path);
|
|
if (defined($toc_frame_fh)) {
|
|
|
|
# this is needed to collect CSS rules.
|
|
$self->{'current_filename'} = $toc_frame_file;
|
|
my $shortcontents =
|
|
&{$self->formatting_function('format_contents')}($self, 'shortcontents');
|
|
$shortcontents =~ s/\bhref=/target="main" href=/g;
|
|
my $header = &{$self->formatting_function('format_begin_file')}($self,
|
|
$toc_frame_file, undef);
|
|
print $toc_frame_fh $header;
|
|
print $toc_frame_fh '<h2>Content</h2>'."\n";
|
|
print $toc_frame_fh $shortcontents;
|
|
print $toc_frame_fh "</body></html>\n";
|
|
|
|
$self->{'current_filename'} = undef;
|
|
|
|
Texinfo::Common::output_files_register_closed(
|
|
$self->output_files_information(), $toc_frame_path);
|
|
if (!close ($toc_frame_fh)) {
|
|
$self->document_error($self,
|
|
sprintf(__("error on closing TOC frame file %s: %s"),
|
|
$toc_frame_outfile, $!));
|
|
return 0;
|
|
}
|
|
} else {
|
|
$self->document_error($self,
|
|
sprintf(__("could not open %s for writing: %s"),
|
|
$toc_frame_outfile, $toc_frame_error_message));
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
sub _has_contents_or_shortcontents($)
|
|
{
|
|
my $self = shift;
|
|
my $global_commands = $self->get_info('global_commands');
|
|
foreach my $cmdname ('contents', 'shortcontents') {
|
|
if ($global_commands and $global_commands->{$cmdname}) {
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
# to be called before starting conversion.
|
|
# NOTE not called directly by convert_tree, which means that convert_tree
|
|
# needs to be called from a converter which would have had this function
|
|
# called already.
|
|
sub _initialize_output_state($)
|
|
{
|
|
my $self = shift;
|
|
|
|
# for diverse API used in conversion
|
|
$self->{'shared_conversion_state'} = {};
|
|
|
|
$self->{'associated_inline_content'} = {};
|
|
|
|
# even if there is no actual file, this is needed if the API is used.
|
|
$self->{'files_information'} = {};
|
|
|
|
# Needed for CSS gathering, even if nothing related to CSS is output
|
|
$self->{'document_global_context_css'} = {};
|
|
$self->{'file_css'} = {};
|
|
|
|
# direction strings
|
|
foreach my $string_type (keys(%default_translated_directions_strings)) {
|
|
# those will be determined from translatable strings
|
|
$self->{'directions_strings'}->{$string_type} = {};
|
|
};
|
|
|
|
# targets and directions
|
|
|
|
# used for diverse elements: tree units, indices, footnotes, special
|
|
# elements, contents elements...
|
|
$self->{'targets'} = {};
|
|
$self->{'seen_ids'} = {};
|
|
|
|
# to avoid infinite recursions when a section refers to itself, possibly
|
|
# indirectly
|
|
$self->{'referred_command_stack'} = [];
|
|
|
|
# for directions to special elements, only filled if special
|
|
# elements are actually used.
|
|
$self->{'special_elements_directions'} = {};
|
|
|
|
# for footnotes
|
|
$self->{'special_targets'} = {'footnote_location' => {}};
|
|
|
|
# other
|
|
|
|
$self->{'check_htmlxref_already_warned'} = {}
|
|
if ($self->get_conf('CHECK_HTMLXREF'));
|
|
}
|
|
|
|
sub convert($$)
|
|
{
|
|
my $self = shift;
|
|
my $root = shift;
|
|
|
|
my $result = '';
|
|
|
|
$self->_initialize_output_state();
|
|
|
|
# needed for CSS rules gathering
|
|
$self->{'current_filename'} = '';
|
|
|
|
# call before _prepare_conversion_tree_units, which calls _translate_names.
|
|
# Some information is not available yet.
|
|
$self->_reset_info();
|
|
|
|
my ($tree_units, $special_elements)
|
|
= $self->_prepare_conversion_tree_units($root, undef, undef);
|
|
|
|
$self->_prepare_index_entries();
|
|
$self->_prepare_footnotes();
|
|
|
|
# title
|
|
$self->{'title_titlepage'}
|
|
= &{$self->formatting_function('format_title_titlepage')}($self);
|
|
|
|
# complete information should be available.
|
|
$self->_reset_info();
|
|
|
|
if (!defined($tree_units)) {
|
|
print STDERR "\nC NO UNIT\n" if ($self->get_conf('DEBUG'));
|
|
$result = $self->_convert($root, 'convert no unit');
|
|
$result .= &{$self->formatting_function('format_footnotes_segment')}($self);
|
|
} else {
|
|
my $unit_nr = 0;
|
|
# TODO there is no rule before the footnotes special element in
|
|
# case of separate footnotes in the default formatting style.
|
|
# Not sure if it is an issue.
|
|
foreach my $tree_unit (@$tree_units, @$special_elements) {
|
|
print STDERR "\nC UNIT $unit_nr\n" if ($self->get_conf('DEBUG'));
|
|
my $tree_unit_text = $self->_convert($tree_unit, "convert unit $unit_nr");
|
|
$result .= $tree_unit_text;
|
|
$unit_nr++;
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
# This is called from the main program on the converter.
|
|
sub output_internal_links($)
|
|
{
|
|
my $self = shift;
|
|
my $out_string = '';
|
|
if ($self->{'tree_units'}) {
|
|
foreach my $tree_unit (@{$self->{'tree_units'}}) {
|
|
my $text;
|
|
my $href;
|
|
my $command = $self->tree_unit_element_command($tree_unit);
|
|
if (defined($command)) {
|
|
# Use '' for filename, to force a filename in href.
|
|
$href = $self->command_href($command, '');
|
|
my $tree = $self->command_text($command, 'tree');
|
|
if ($tree) {
|
|
$text = Texinfo::Convert::Text::convert_to_text($tree,
|
|
{Texinfo::Convert::Text::copy_options_for_convert_text($self)});
|
|
}
|
|
}
|
|
if (defined($href) or defined($text)) {
|
|
$out_string .= $href if (defined($href));
|
|
$out_string .= "\ttoc\t";
|
|
$out_string .= $text if (defined($text));
|
|
$out_string .= "\n";
|
|
}
|
|
}
|
|
}
|
|
my $index_entries_by_letter = $self->get_info('index_entries_by_letter');
|
|
if ($index_entries_by_letter) {
|
|
my %options = Texinfo::Convert::Text::copy_options_for_convert_text($self);
|
|
foreach my $index_name (sort(keys (%{$index_entries_by_letter}))) {
|
|
foreach my $letter_entry (@{$index_entries_by_letter->{$index_name}}) {
|
|
foreach my $index_entry (@{$letter_entry->{'entries'}}) {
|
|
my $main_entry_element = $index_entry->{'entry_element'};
|
|
my $in_code
|
|
= $self->{'indices_information'}->{$index_entry->{'index_name'}}
|
|
->{'in_code'};
|
|
# does not refer to the document
|
|
next if ($main_entry_element->{'extra'}
|
|
and ($main_entry_element->{'extra'}->{'seeentry'}
|
|
or $main_entry_element->{'extra'}->{'seealso'}));
|
|
my $href;
|
|
$href = $self->command_href($main_entry_element, '');
|
|
# Obtain term by converting to text
|
|
my $converter_options = {%options};
|
|
$converter_options->{'code'} = $in_code;
|
|
my $entry_reference_content_element
|
|
= Texinfo::Common::index_content_element($main_entry_element);
|
|
my @contents = ($entry_reference_content_element);
|
|
my $subentries_tree
|
|
= $self->comma_index_subentries_tree($main_entry_element);
|
|
if (defined($subentries_tree)) {
|
|
push @contents, @{$subentries_tree->{'contents'}};
|
|
}
|
|
my $index_term = Texinfo::Convert::Text::convert_to_text(
|
|
{'contents' => \@contents}, $converter_options);
|
|
if (defined($index_term) and $index_term =~ /\S/) {
|
|
$out_string .= $href if (defined($href));
|
|
$out_string .= "\t$index_name\t";
|
|
$out_string .= $index_term;
|
|
$out_string .= "\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ($out_string ne '') {
|
|
return $out_string;
|
|
} else {
|
|
return undef;
|
|
}
|
|
}
|
|
|
|
sub run_stage_handlers($$$)
|
|
{
|
|
my $converter = shift;
|
|
my $root = shift;
|
|
my $stage = shift;
|
|
|
|
my $stage_handlers = Texinfo::Config::GNUT_get_stage_handlers();
|
|
return 0 if (!defined($stage_handlers->{$stage}));
|
|
|
|
my @sorted_priorities = sort keys(%{$stage_handlers->{$stage}});
|
|
foreach my $priority (@sorted_priorities) {
|
|
foreach my $handler (@{$stage_handlers->{$stage}->{$priority}}) {
|
|
if ($converter->get_conf('DEBUG')) {
|
|
print STDERR "HANDLER($stage) , priority $priority: $handler\n";
|
|
}
|
|
my $status = &{$handler}($converter, $root, $stage);
|
|
if ($status != 0) {
|
|
if ($status < 0) {
|
|
$converter->document_error($converter,
|
|
sprintf(__("handler %s of stage %s priority %s failed"),
|
|
$handler, $stage, $priority));
|
|
} else {
|
|
# the handler is supposed to have output an error message
|
|
# already if $status > 0
|
|
if ($converter->get_conf('VERBOSE') or $converter->get_conf('DEBUG')) {
|
|
print STDERR "Handler $handler of $stage($priority) failed\n";
|
|
}
|
|
}
|
|
return $status;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
sub _reset_info()
|
|
{
|
|
my $self = shift;
|
|
|
|
# reset to be sure that there is no stale information
|
|
$self->{'converter_info'} = {};
|
|
foreach my $converter_info (keys(%available_converter_info)) {
|
|
if (exists($self->{$converter_info})) {
|
|
if (ref($self->{$converter_info}) eq '') {
|
|
# for scalar, use references in case it may change
|
|
$self->{'converter_info'}->{$converter_info} = \$self->{$converter_info};
|
|
} else {
|
|
$self->{'converter_info'}->{$converter_info} = $self->{$converter_info};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Main function for outputting a manual in HTML.
|
|
# $SELF is the output converter object of class Texinfo::Convert::HTML (this
|
|
# module), and $ROOT is the Texinfo tree from the parser.
|
|
sub output($$)
|
|
{
|
|
my $self = shift;
|
|
my $root = shift;
|
|
|
|
$self->{'current_filename'} = undef;
|
|
|
|
$self->_initialize_output_state();
|
|
|
|
# no splitting when writing to the null device or to stdout or returning
|
|
# a string
|
|
if (defined($self->get_conf('OUTFILE'))
|
|
and ($Texinfo::Common::null_device_file{$self->get_conf('OUTFILE')}
|
|
or $self->get_conf('OUTFILE') eq '-'
|
|
or $self->get_conf('OUTFILE') eq '')) {
|
|
$self->force_conf('SPLIT', 0);
|
|
$self->force_conf('MONOLITHIC', 1);
|
|
$self->force_conf('FRAMES', 0);
|
|
}
|
|
if ($self->get_conf('SPLIT')) {
|
|
$self->set_conf('NODE_FILES', 1);
|
|
}
|
|
if ($self->get_conf('FRAMES')) {
|
|
$self->set_conf('shortcontents', 1);
|
|
}
|
|
$self->set_conf('EXTERNAL_CROSSREF_SPLIT', $self->get_conf('SPLIT'));
|
|
|
|
if (not defined($self->get_conf('NODE_NAME_IN_INDEX'))) {
|
|
$self->set_conf('NODE_NAME_IN_INDEX', $self->get_conf('USE_NODES'));
|
|
}
|
|
|
|
my $handler_fatal_error_level = $self->get_conf('HANDLER_FATAL_ERROR_LEVEL');
|
|
|
|
if ($self->get_conf('HTML_MATH')
|
|
and $self->get_conf('HTML_MATH') eq 'mathjax') {
|
|
# See https://www.gnu.org/licenses/javascript-labels.html
|
|
#
|
|
# The link to the source for mathjax does not strictly follow the advice
|
|
# there: instead we link to instructions for obtaining the full source in
|
|
# its preferred form of modification.
|
|
|
|
my $mathjax_script = $self->get_conf('MATHJAX_SCRIPT');
|
|
if (! defined($mathjax_script)) {
|
|
$mathjax_script = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js';
|
|
$self->set_conf('MATHJAX_SCRIPT', $mathjax_script);
|
|
}
|
|
|
|
my $mathjax_source = $self->get_conf('MATHJAX_SOURCE');
|
|
if (! defined($mathjax_source)) {
|
|
$mathjax_source = 'http://docs.mathjax.org/en/latest/web/hosting.html#getting-mathjax-via-git';
|
|
$self->set_conf('MATHJAX_SOURCE', $mathjax_source);
|
|
}
|
|
}
|
|
|
|
if ($self->get_conf('HTML_MATH')
|
|
and not defined($self->get_conf('CONVERT_TO_LATEX_IN_MATH'))) {
|
|
$self->set_conf('CONVERT_TO_LATEX_IN_MATH', 1);
|
|
}
|
|
|
|
if ($self->get_conf('CONVERT_TO_LATEX_IN_MATH')) {
|
|
$self->{'options_latex_math'}
|
|
= { Texinfo::Convert::LaTeX::copy_options_for_convert_to_latex_math($self) };
|
|
}
|
|
|
|
if ($self->get_conf('NO_TOP_NODE_OUTPUT')
|
|
and not defined($self->get_conf('SHOW_TITLE'))) {
|
|
$self->set_conf('SHOW_TITLE', 1);
|
|
}
|
|
|
|
# set information, to have some information for run_stage_handlers.
|
|
# Some information is not available yet.
|
|
$self->_reset_info();
|
|
|
|
my $setup_status = $self->run_stage_handlers($root, 'setup');
|
|
return undef unless ($setup_status < $handler_fatal_error_level
|
|
and $setup_status > -$handler_fatal_error_level);
|
|
|
|
# the configuration has potentially been modified for
|
|
# this output file especially. Set a corresponding initial
|
|
# configuration.
|
|
$self->{'output_init_conf'} = { %{$self->{'conf'}} };
|
|
|
|
$self->{'jslicenses'} = {};
|
|
if ($self->get_conf('HTML_MATH')
|
|
and $self->get_conf('HTML_MATH') eq 'mathjax') {
|
|
# See https://www.gnu.org/licenses/javascript-labels.html
|
|
|
|
my $mathjax_script = $self->get_conf('MATHJAX_SCRIPT');
|
|
my $mathjax_source = $self->get_conf('MATHJAX_SOURCE');
|
|
|
|
$self->{'jslicenses'}->{'mathjax'} = {
|
|
$mathjax_script =>
|
|
[ 'Apache License, Version 2.0.',
|
|
'https://www.apache.org/licenses/LICENSE-2.0',
|
|
$mathjax_source ]};
|
|
}
|
|
if ($self->get_conf('INFO_JS_DIR')) {
|
|
$self->{'jslicenses'}->{'infojs'} = {
|
|
'js/info.js' =>
|
|
[ 'GNU General Public License 3.0 or later',
|
|
'http://www.gnu.org/licenses/gpl-3.0.html',
|
|
'js/info.js' ],
|
|
'js/modernizr.js' =>
|
|
[ 'Expat',
|
|
'http://www.jclark.com/xml/copying.txt',
|
|
'js/modernizr.js' ]};
|
|
}
|
|
$self->_prepare_css();
|
|
|
|
# this sets OUTFILE, to be used if not split, but also
|
|
# 'destination_directory' and 'output_filename' that are useful when split.
|
|
my ($output_file, $destination_directory, $output_filename,
|
|
$document_name) = $self->determine_files_and_directory();
|
|
my ($encoded_destination_directory, $dir_encoding)
|
|
= $self->encoded_output_file_name($destination_directory);
|
|
my $succeeded
|
|
= $self->create_destination_directory($encoded_destination_directory,
|
|
$destination_directory);
|
|
return undef unless $succeeded;
|
|
|
|
# set for init files
|
|
$self->{'document_name'} = $document_name;
|
|
$self->{'destination_directory'} = $destination_directory;
|
|
|
|
# set information, to have it available for the conversions below,
|
|
# in translate_names called by _prepare_conversion_tree_units and in
|
|
# titles formatting.
|
|
# Some information is not available yet.
|
|
$self->_reset_info();
|
|
|
|
# Get the list of "elements" to be processed, i.e. nodes or sections.
|
|
# This should return undef if called on a tree without node or sections.
|
|
my ($tree_units, $special_elements)
|
|
= $self->_prepare_conversion_tree_units($root, $destination_directory,
|
|
$document_name);
|
|
|
|
Texinfo::Structuring::split_pages($tree_units, $self->get_conf('SPLIT'));
|
|
|
|
# determine file names associated with the different pages, and setup
|
|
# the counters for special element pages.
|
|
my %files_source_info;
|
|
if ($output_file ne '') {
|
|
%files_source_info =
|
|
$self->_html_set_pages_files($tree_units, $special_elements, $output_file,
|
|
$destination_directory, $output_filename, $document_name);
|
|
}
|
|
|
|
$self->_prepare_contents_elements();
|
|
|
|
# do tree units directions.
|
|
Texinfo::Structuring::elements_directions($self, $self->{'labels'}, $tree_units);
|
|
|
|
# do element directions related to files.
|
|
# FIXME do it here or before? Here it means that
|
|
# PrevFile and NextFile can be set.
|
|
Texinfo::Structuring::elements_file_directions($tree_units);
|
|
|
|
# Associate the special elements that have no page with the main page.
|
|
# This may only happen if not split.
|
|
if ($special_elements
|
|
and $tree_units and $tree_units->[0]
|
|
and $tree_units->[0]->{'structure'}
|
|
and defined($tree_units->[0]->{'structure'}->{'unit_filename'})) {
|
|
foreach my $special_element (@$special_elements) {
|
|
if (!defined($special_element->{'structure'}->{'unit_filename'})) {
|
|
$special_element->{'structure'}->{'unit_filename'}
|
|
= $tree_units->[0]->{'structure'}->{'unit_filename'};
|
|
$self->{'file_counters'}->{$special_element->{'structure'}->{'unit_filename'}}++;
|
|
}
|
|
}
|
|
}
|
|
|
|
$self->_prepare_index_entries();
|
|
$self->_prepare_footnotes();
|
|
|
|
# only in HTML, not in Texinfo::Convert::Converter
|
|
$self->{'elements_in_file_count'} = {};
|
|
# condition could also be based on $output_file ne ''
|
|
if ($self->{'file_counters'}) {
|
|
# 'file_counters' is dynamic, decreased when the element is encountered
|
|
# 'elements_in_file_count' is not modified afterwards
|
|
foreach my $filename (keys(%{$self->{'file_counters'}})) {
|
|
$self->{'elements_in_file_count'}->{$filename}
|
|
= $self->{'file_counters'}->{$filename};
|
|
}
|
|
}
|
|
|
|
# set information, to have it ready for
|
|
# run_stage_handlers. Some information is not available yet.
|
|
$self->_reset_info();
|
|
|
|
my $structure_status = $self->run_stage_handlers($root, 'structure');
|
|
return undef unless ($structure_status < $handler_fatal_error_level
|
|
and $structure_status > -$handler_fatal_error_level);
|
|
|
|
my $default_document_language = $self->get_conf('documentlanguage');
|
|
|
|
$self->set_global_document_commands('preamble', ['documentlanguage']);
|
|
|
|
my $preamble_document_language = $self->get_conf('documentlanguage');
|
|
$self->set_conf('BODYTEXT',
|
|
'lang="' . $preamble_document_language . '"');
|
|
|
|
if ($default_document_language ne $preamble_document_language) {
|
|
$self->_translate_names();
|
|
}
|
|
|
|
# prepare title. fulltitle uses more possibility than simpletitle for
|
|
# title, including @-commands found in @titlepage only. Therefore
|
|
# simpletitle is more in line with what makeinfo in C does.
|
|
my $fulltitle;
|
|
foreach my $fulltitle_command('settitle', 'title', 'shorttitlepage', 'top') {
|
|
if ($self->{'global_commands'}->{$fulltitle_command}) {
|
|
my $command = $self->{'global_commands'}->{$fulltitle_command};
|
|
next if (!$command->{'args'}
|
|
or (!$command->{'args'}->[0]->{'contents'}
|
|
or ($command->{'extra'}
|
|
and $command->{'extra'}->{'missing_argument'})));
|
|
print STDERR "Using $fulltitle_command as title\n"
|
|
if ($self->get_conf('DEBUG'));
|
|
$fulltitle = {'contents' => $command->{'args'}->[0]->{'contents'}};
|
|
last;
|
|
}
|
|
}
|
|
if (!$fulltitle and $self->{'global_commands'}->{'titlefont'}
|
|
and $self->{'global_commands'}->{'titlefont'}->[0]->{'args'}
|
|
and defined($self->{'global_commands'}->{'titlefont'}->[0]->{'args'}->[0])
|
|
and $self->{'global_commands'}->{'titlefont'}->[0]
|
|
->{'args'}->[0]->{'contents'}
|
|
and @{$self->{'global_commands'}->{'titlefont'}->[0]
|
|
->{'args'}->[0]->{'contents'}}) {
|
|
$fulltitle = $self->{'global_commands'}->{'titlefont'}->[0];
|
|
}
|
|
# prepare simpletitle
|
|
foreach my $simpletitle_command ('settitle', 'shorttitlepage') {
|
|
if ($self->{'global_commands'}->{$simpletitle_command}) {
|
|
my $command = $self->{'global_commands'}->{$simpletitle_command};
|
|
next if (!$command->{'args'}
|
|
or !$command->{'args'}->[0]->{'contents'}
|
|
or ($command->{'extra'}
|
|
and $command->{'extra'}->{'missing_argument'}));
|
|
$self->{'simpletitle_tree'} =
|
|
{'contents' => $command->{'args'}->[0]->{'contents'}};
|
|
$self->{'simpletitle_command_name'} = $simpletitle_command;
|
|
last;
|
|
}
|
|
}
|
|
|
|
my $html_title_string;
|
|
if ($fulltitle) {
|
|
$self->{'title_tree'} = $fulltitle;
|
|
$html_title_string = $self->convert_tree_new_formatting_context(
|
|
{'type' => '_string', 'contents' => [$self->{'title_tree'}]},
|
|
'title_string');
|
|
}
|
|
if (!defined($html_title_string) or $html_title_string !~ /\S/) {
|
|
my $default_title = $self->gdt('Untitled Document');
|
|
$self->{'title_tree'} = $default_title;
|
|
$self->{'title_string'} = $self->convert_tree_new_formatting_context(
|
|
{'type' => '_string', 'contents' => [$self->{'title_tree'}]},
|
|
'title_string');
|
|
$self->line_warn($self, __(
|
|
"must specify a title with a title command or \@top"),
|
|
{'file_name' => $self->{'parser_info'}->{'input_file_name'}});
|
|
} else {
|
|
$self->{'title_string'} = $html_title_string;
|
|
}
|
|
|
|
# copying comment
|
|
if ($self->{'global_commands'}->{'copying'}) {
|
|
my $copying_comment = Texinfo::Convert::Text::convert_to_text(
|
|
{'contents' => $self->{'global_commands'}->{'copying'}->{'contents'}},
|
|
{Texinfo::Convert::Text::copy_options_for_convert_text($self)});
|
|
if ($copying_comment ne '') {
|
|
$self->{'copying_comment'}
|
|
= &{$self->formatting_function('format_comment')}($self, $copying_comment);
|
|
}
|
|
}
|
|
$self->set_global_document_commands('before', ['documentlanguage']);
|
|
|
|
if ($default_document_language ne $preamble_document_language) {
|
|
$self->_translate_names();
|
|
}
|
|
|
|
# documentdescription
|
|
if (defined($self->get_conf('documentdescription'))) {
|
|
$self->{'documentdescription_string'}
|
|
= $self->get_conf('documentdescription');
|
|
} elsif ($self->{'global_commands'}->{'documentdescription'}) {
|
|
$self->{'documentdescription_string'}
|
|
= $self->convert_tree_new_formatting_context(
|
|
{'type' => '_string',
|
|
'contents' =>
|
|
$self->{'global_commands'}->{'documentdescription'}->{'contents'}},
|
|
'documentdescription');
|
|
chomp($self->{'documentdescription_string'});
|
|
}
|
|
|
|
# set information, to have it ready for run_stage_handlers.
|
|
# Some information is not available yet.
|
|
$self->_reset_info();
|
|
|
|
my $init_status = $self->run_stage_handlers($root, 'init');
|
|
return undef unless ($init_status < $handler_fatal_error_level
|
|
and $init_status > -$handler_fatal_error_level);
|
|
|
|
if ($self->get_conf('FRAMES')) {
|
|
my $status = &{$self->formatting_function('format_frame_files')}($self,
|
|
$destination_directory);
|
|
return undef if (!$status);
|
|
}
|
|
|
|
# determine first file name
|
|
if (!$tree_units
|
|
or !defined($tree_units->[0]->{'structure'}->{'unit_filename'})) {
|
|
# no page
|
|
if ($output_file ne '') {
|
|
my $no_page_output_filename;
|
|
my $no_page_out_filepath;
|
|
if ($self->get_conf('SPLIT')) {
|
|
$no_page_output_filename = $self->top_node_filename($document_name);
|
|
if (defined($destination_directory) and $destination_directory ne '') {
|
|
$no_page_out_filepath = File::Spec->catfile($destination_directory,
|
|
$no_page_output_filename);
|
|
} else {
|
|
$no_page_out_filepath = $no_page_output_filename;
|
|
}
|
|
} else {
|
|
$no_page_out_filepath = $output_file;
|
|
$no_page_output_filename = $output_filename;
|
|
}
|
|
$self->{'out_filepaths'}->{$no_page_output_filename} = $no_page_out_filepath;
|
|
|
|
$self->{'current_filename'} = $no_page_output_filename;
|
|
} else {
|
|
$self->{'current_filename'} = $output_filename;
|
|
}
|
|
} else {
|
|
$self->{'current_filename'}
|
|
= $tree_units->[0]->{'structure'}->{'unit_filename'};
|
|
}
|
|
# title
|
|
$self->{'title_titlepage'}
|
|
= &{$self->formatting_function('format_title_titlepage')}($self);
|
|
|
|
# complete information should be available.
|
|
$self->_reset_info();
|
|
|
|
my $output = '';
|
|
|
|
if (!$tree_units or !$tree_units->[0]->{'structure'}
|
|
or !defined($tree_units->[0]->{'structure'}->{'unit_filename'})) {
|
|
my $fh;
|
|
my $encoded_no_page_out_filepath;
|
|
my $no_page_out_filepath;
|
|
if ($self->{'current_filename'} ne ''
|
|
and $self->{'out_filepaths'}
|
|
and defined($self->{'out_filepaths'}->{$self->{'current_filename'}})) {
|
|
my $path_encoding;
|
|
$no_page_out_filepath
|
|
= $self->{'out_filepaths'}->{$self->{'current_filename'}};
|
|
($encoded_no_page_out_filepath, $path_encoding)
|
|
= $self->encoded_output_file_name($no_page_out_filepath);
|
|
my $error_message;
|
|
($fh, $error_message) = Texinfo::Common::output_files_open_out(
|
|
$self->output_files_information(), $self,
|
|
$encoded_no_page_out_filepath);
|
|
if (!$fh) {
|
|
$self->document_error($self,
|
|
sprintf(__("could not open %s for writing: %s"),
|
|
$no_page_out_filepath, $error_message));
|
|
return undef;
|
|
}
|
|
}
|
|
my $body = '';
|
|
if ($tree_units and @$tree_units) {
|
|
my $unit_nr = 0;
|
|
# TODO there is no rule before the footnotes special element in
|
|
# case of separate footnotes in the default formatting style.
|
|
# Not sure if it is an issue.
|
|
foreach my $tree_unit (@$tree_units, @$special_elements) {
|
|
print STDERR "\nUNIT NO-PAGE $unit_nr\n" if ($self->get_conf('DEBUG'));
|
|
my $tree_unit_text
|
|
= $self->_convert($tree_unit, "no-page output unit $unit_nr");
|
|
$body .= $tree_unit_text;
|
|
$unit_nr++;
|
|
}
|
|
} else {
|
|
$body .= $self->get_info('title_titlepage');
|
|
print STDERR "\nNO UNIT NO PAGE\n" if ($self->get_conf('DEBUG'));
|
|
$body .= $self->_convert($root, 'no-page output no unit');
|
|
$body .= &{$self->formatting_function('format_footnotes_segment')}($self);
|
|
}
|
|
|
|
# do end file first, in case it needs some CSS
|
|
my $footer = &{$self->formatting_function('format_end_file')}($self,
|
|
$output_filename);
|
|
my $header = &{$self->formatting_function('format_begin_file')}($self,
|
|
$output_filename, undef);
|
|
$output .= $self->write_or_return($header, $fh);
|
|
$output .= $self->write_or_return($body, $fh);
|
|
$output .= $self->write_or_return($footer, $fh);
|
|
|
|
# NOTE do not close STDOUT now to avoid a perl warning.
|
|
if ($fh and $no_page_out_filepath ne '-') {
|
|
Texinfo::Common::output_files_register_closed(
|
|
$self->output_files_information(), $encoded_no_page_out_filepath);
|
|
if (!close($fh)) {
|
|
$self->document_error($self,
|
|
sprintf(__("error on closing %s: %s"),
|
|
$no_page_out_filepath, $!));
|
|
}
|
|
}
|
|
$self->{'current_filename'} = undef;
|
|
return $output if ($output_file eq '');
|
|
} else {
|
|
# output with pages
|
|
print STDERR "DO Elements with filenames\n"
|
|
if ($self->get_conf('DEBUG'));
|
|
my %files;
|
|
|
|
my $unit_nr = -1;
|
|
# Now do the output, converting each tree units and special elements in turn
|
|
$special_elements = [] if (!defined($special_elements));
|
|
foreach my $element (@$tree_units, @$special_elements) {
|
|
my $element_filename = $element->{'structure'}->{'unit_filename'};
|
|
my $out_filepath = $self->{'out_filepaths'}->{$element_filename};
|
|
$self->{'current_filename'} = $element_filename;
|
|
|
|
$unit_nr++;
|
|
# First do the special pages, to avoid outputting these if they are
|
|
# empty.
|
|
my $special_element_content;
|
|
if (defined($element->{'type'})
|
|
and $element->{'type'} eq 'special_element') {
|
|
print STDERR "\nUNIT SPECIAL\n" if ($self->get_conf('DEBUG'));
|
|
$special_element_content
|
|
.= $self->_convert($element, "output s-unit $unit_nr");
|
|
if ($special_element_content eq '') {
|
|
$self->{'file_counters'}->{$element_filename}--;
|
|
next ;
|
|
}
|
|
}
|
|
|
|
# convert body before header in case this affects the header
|
|
my $body = '';
|
|
if (defined($special_element_content)) {
|
|
$body = $special_element_content;
|
|
} else {
|
|
print STDERR "\nUNIT $unit_nr\n" if ($self->get_conf('DEBUG'));
|
|
$body = $self->_convert($element, "output unit $unit_nr");
|
|
}
|
|
|
|
# register the element but do not print anything. Printing
|
|
# only when file_counters reach 0, to be sure that all the
|
|
# elements have been converted.
|
|
if (!exists($files{$element_filename})) {
|
|
$files{$element_filename} = {'first_element' => $element,
|
|
'body' => ''};
|
|
}
|
|
$files{$element_filename}->{'body'} .= $body;
|
|
$self->{'file_counters'}->{$element_filename}--;
|
|
if ($self->{'file_counters'}->{$element_filename} == 0) {
|
|
my $file_element = $files{$element_filename}->{'first_element'};
|
|
my ($encoded_out_filepath, $path_encoding)
|
|
= $self->encoded_output_file_name($out_filepath);
|
|
my ($file_fh, $error_message)
|
|
= Texinfo::Common::output_files_open_out(
|
|
$self->output_files_information(), $self,
|
|
$encoded_out_filepath);
|
|
if (!$file_fh) {
|
|
$self->document_error($self,
|
|
sprintf(__("could not open %s for writing: %s"),
|
|
$out_filepath, $error_message));
|
|
return undef;
|
|
}
|
|
# do end file first in case it requires some CSS
|
|
my $end_file = &{$self->formatting_function('format_end_file')}($self,
|
|
$element_filename);
|
|
print $file_fh "".&{$self->formatting_function('format_begin_file')}(
|
|
$self, $element_filename, $file_element);
|
|
print $file_fh "".$files{$element_filename}->{'body'};
|
|
# end file
|
|
print $file_fh "". $end_file;
|
|
|
|
# NOTE do not close STDOUT here to avoid a perl warning
|
|
if ($out_filepath ne '-') {
|
|
Texinfo::Common::output_files_register_closed(
|
|
$self->output_files_information(), $encoded_out_filepath);
|
|
if (!close($file_fh)) {
|
|
$self->document_error($self,
|
|
sprintf(__("error on closing %s: %s"),
|
|
$out_filepath, $!));
|
|
return undef;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
delete $self->{'current_filename'};
|
|
if ($self->get_conf('INFO_JS_DIR')) {
|
|
my $jsdir = File::Spec->catdir($destination_directory,
|
|
$self->get_conf('INFO_JS_DIR'));
|
|
if (!-d $jsdir) {
|
|
if (-f $jsdir) {
|
|
$self->document_error($self,
|
|
sprintf(__("%s already exists but is not a directory"), $jsdir));
|
|
} else {
|
|
mkdir $jsdir;
|
|
}
|
|
}
|
|
# Copy JS files.
|
|
if (-d $jsdir) {
|
|
if (!$self->get_conf('TEST')) {
|
|
my $jssrcdir;
|
|
if (!$Texinfo::ModulePath::texinfo_uninstalled) {
|
|
$jssrcdir = File::Spec->catdir(
|
|
$Texinfo::ModulePath::pkgdatadir, 'js');
|
|
} else {
|
|
$jssrcdir = File::Spec->catdir(
|
|
$Texinfo::ModulePath::top_srcdir, 'js');
|
|
}
|
|
for my $f ('info.js', 'modernizr.js', 'info.css') {
|
|
my $from = File::Spec->catfile($jssrcdir, $f);
|
|
|
|
if (!copy($from, $jsdir)) {
|
|
$self->document_error($self,
|
|
sprintf(__("error on copying %s into %s"), $from, $jsdir));
|
|
}
|
|
}
|
|
} else {
|
|
# create empty files for tests to keep results stable.
|
|
for my $f ('info.js', 'modernizr.js', 'info.css') {
|
|
my $filename = File::Spec->catfile($jsdir, $f);
|
|
if (!open (FH, '>', $filename)) {
|
|
$self->document_error($self,
|
|
sprintf(__("error on creating empty %s: %s"),
|
|
$filename, $!));
|
|
}
|
|
if (!close(FH)) {
|
|
$self->document_error($self,
|
|
sprintf(__("error on closing empty %s: %s"),
|
|
$filename, $!));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
my $jslicenses = $self->get_info('jslicenses');
|
|
if ($jslicenses and scalar(%$jslicenses)) {
|
|
$self->_do_jslicenses_file($destination_directory);
|
|
}
|
|
|
|
my $finish_status = $self->run_stage_handlers($root, 'finish');
|
|
return undef unless ($finish_status < $handler_fatal_error_level
|
|
and $finish_status > -$handler_fatal_error_level);
|
|
|
|
my $extension = '';
|
|
$extension = '.'.$self->get_conf('EXTENSION')
|
|
if (defined($self->get_conf('EXTENSION'))
|
|
and $self->get_conf('EXTENSION') ne '');
|
|
# do node redirection pages
|
|
$self->{'current_filename'} = undef;
|
|
if ($self->get_conf('NODE_FILES')
|
|
and $self->{'labels'} and $output_file ne '') {
|
|
my %redirection_filenames;
|
|
foreach my $label (sort(keys (%{$self->{'labels'}}))) {
|
|
my $target_element = $self->{'labels'}->{$label};
|
|
my $label_element = Texinfo::Common::get_label_element($target_element);
|
|
my $label_contents = $label_element->{'contents'};
|
|
my $target = $self->_get_target($target_element);
|
|
# filename may not be defined in case of an @anchor or similar in
|
|
# @titlepage, and @titlepage is not used.
|
|
my $filename = $self->command_filename($target_element);
|
|
my $node_filename;
|
|
# NOTE 'node_filename' is not used for Top, TOP_NODE_FILE_TARGET
|
|
# is. The other manual must use the same convention to get it
|
|
# right. We do not do 'node_filename' as a redirection file
|
|
# either.
|
|
if ($target_element->{'extra'}
|
|
and $target_element->{'extra'}->{'normalized'}
|
|
and $target_element->{'extra'}->{'normalized'} eq 'Top'
|
|
and defined($self->get_conf('TOP_NODE_FILE_TARGET'))) {
|
|
$node_filename = $self->get_conf('TOP_NODE_FILE_TARGET');
|
|
} else {
|
|
$node_filename = $target->{'node_filename'};
|
|
}
|
|
|
|
if (defined($filename) and $node_filename ne $filename) {
|
|
my $redirection_filename
|
|
= $self->register_normalize_case_filename($node_filename);
|
|
# first condition finds conflict with tree elements
|
|
if ($self->{'elements_in_file_count'}->{$redirection_filename}
|
|
or $redirection_filenames{$redirection_filename}) {
|
|
$self->line_warn($self,
|
|
sprintf(__("\@%s `%s' file %s for redirection exists"),
|
|
$target_element->{'cmdname'},
|
|
Texinfo::Convert::Texinfo::convert_to_texinfo({'contents'
|
|
=> $label_contents}),
|
|
$redirection_filename),
|
|
$target_element->{'source_info'});
|
|
my $file_source = $files_source_info{$redirection_filename};
|
|
my $file_info_type = $file_source->{'file_info_type'};
|
|
if ($file_info_type eq 'special_file'
|
|
or $file_info_type eq 'stand_in_file') {
|
|
my $name = $file_source->{'file_info_name'};
|
|
if ($name eq 'non_split') {
|
|
# This cannot actually happen, as the @anchor/@node/@float
|
|
# with potentially conflicting name will also be in the
|
|
# non-split output document and therefore does not need
|
|
# a redirection.
|
|
$self->document_warn($self,
|
|
__("conflict with whole document file"), 1);
|
|
} elsif ($name eq 'Top') {
|
|
$self->document_warn($self,
|
|
__("conflict with Top file"), 1);
|
|
} elsif ($name eq 'user_defined') {
|
|
$self->document_warn($self,
|
|
__("conflict with user-defined file"), 1);
|
|
} elsif ($name eq 'unknown_node') {
|
|
$self->document_warn($self,
|
|
__("conflict with unknown node file"), 1);
|
|
} elsif ($name eq 'unknown') {
|
|
$self->document_warn($self,
|
|
__("conflict with file without known source"), 1);
|
|
}
|
|
} elsif ($file_info_type eq 'node') {
|
|
my $conflicting_node = $file_source->{'file_info_element'};
|
|
$self->line_warn($self,
|
|
sprintf(__p('conflict of redirection file with file based on node name',
|
|
"conflict with \@%s `%s' file"),
|
|
$conflicting_node->{'cmdname'},
|
|
Texinfo::Convert::Texinfo::convert_to_texinfo({'contents'
|
|
=> $conflicting_node->{'args'}->[0]->{'contents'}}),
|
|
),
|
|
$conflicting_node->{'source_info'}, 1);
|
|
} elsif ($file_info_type eq 'redirection') {
|
|
my $conflicting_node = $file_source->{'file_info_element'};
|
|
my $conflicting_label_contents
|
|
= $file_source->{'file_info_label_contents'};
|
|
$self->line_warn($self,
|
|
sprintf(__("conflict with \@%s `%s' redirection file"),
|
|
$conflicting_node->{'cmdname'},
|
|
Texinfo::Convert::Texinfo::convert_to_texinfo({'contents'
|
|
=> $conflicting_label_contents}),
|
|
),
|
|
$conflicting_node->{'source_info'}, 1);
|
|
} elsif ($file_info_type eq 'section') {
|
|
my $conflicting_section = $file_source->{'file_info_element'};
|
|
$self->line_warn($self,
|
|
sprintf(__p('conflict of redirection file with file based on section name',
|
|
"conflict with \@%s `%s' file"),
|
|
$conflicting_section->{'cmdname'},
|
|
Texinfo::Convert::Texinfo::convert_to_texinfo({'contents'
|
|
=> $conflicting_section->{'args'}->[0]->{'contents'}}),
|
|
),
|
|
$conflicting_section->{'source_info'}, 1);
|
|
} elsif ($file_info_type eq 'special_element') {
|
|
my $special_element = $file_source->{'file_info_element'};
|
|
my $element_variety
|
|
= $special_element->{'extra'}->{'special_element_variety'};
|
|
$self->document_warn($self,
|
|
sprintf(__("conflict with %s special element"),
|
|
$element_variety), 1);
|
|
}
|
|
next;
|
|
}
|
|
$redirection_filenames{$redirection_filename} = $target_element;
|
|
$files_source_info{$redirection_filename}
|
|
= {'file_info_type' => 'redirection',
|
|
'file_info_element' => $target_element,
|
|
'file_info_label_contents' => $label_contents};
|
|
my $redirection_page
|
|
= &{$self->formatting_function('format_node_redirection_page')}($self,
|
|
$target_element);
|
|
my $out_filename;
|
|
if (defined($destination_directory) and $destination_directory ne '') {
|
|
$out_filename = File::Spec->catfile($destination_directory,
|
|
$redirection_filename);
|
|
} else {
|
|
$out_filename = $redirection_filename;
|
|
}
|
|
my ($encoded_out_filename, $path_encoding)
|
|
= $self->encoded_output_file_name($out_filename);
|
|
my ($file_fh, $error_message)
|
|
= Texinfo::Common::output_files_open_out(
|
|
$self->output_files_information(), $self,
|
|
$encoded_out_filename);
|
|
if (!$file_fh) {
|
|
$self->document_error($self, sprintf(__(
|
|
"could not open %s for writing: %s"),
|
|
$out_filename, $error_message));
|
|
} else {
|
|
print $file_fh $redirection_page;
|
|
Texinfo::Common::output_files_register_closed(
|
|
$self->output_files_information(), $encoded_out_filename);
|
|
if (!close ($file_fh)) {
|
|
$self->document_error($self, sprintf(__(
|
|
"error on closing redirection node file %s: %s"),
|
|
$out_filename, $!));
|
|
return undef;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
#my $characters_replaced_from_class_names = quotemeta('[](),~#:/\\@+=!;.,?* ');
|
|
# FIXME not clear what character should be allowed and which ones replaced
|
|
# besides space
|
|
my $characters_replaced_from_class_names = quotemeta(' ');
|
|
sub _protect_class_name($$)
|
|
{
|
|
my $self = shift;
|
|
my $class_name = shift;
|
|
$class_name =~ s/[$characters_replaced_from_class_names]/-/g;
|
|
|
|
# API info: using the API to allow for customization would be:
|
|
# return &{$self->formatting_function('format_protect_text')}($self, $class_name);
|
|
return _default_format_protect_text($self, $class_name);
|
|
}
|
|
|
|
my $debug; # whether to print debugging output
|
|
|
|
# Convert tree element $ELEMENT, and return HTML text for the output files.
|
|
sub _convert($$;$);
|
|
sub _convert($$;$)
|
|
{
|
|
my $self = shift;
|
|
my $element = shift;
|
|
if (!defined($element)) {
|
|
cluck('BUG: _convert: element UNDEF');
|
|
return '';
|
|
}
|
|
# only used for debug
|
|
my $explanation = shift;
|
|
|
|
# to help debug and trace
|
|
my $command_type = '';
|
|
if ($element->{'cmdname'}) {
|
|
$command_type = "\@$element->{'cmdname'} ";
|
|
}
|
|
if (defined($element->{'type'})) {
|
|
$command_type .= $element->{'type'};
|
|
}
|
|
|
|
$debug = $self->get_conf('DEBUG') if !defined($debug);
|
|
# cache return value of get_conf for speed
|
|
|
|
if ($debug) {
|
|
$explanation = 'NO EXPLANATION' if (!defined($explanation));
|
|
my @contexts_names = map {defined($_->{'context_name'})
|
|
? $_->{'context_name'}: 'UNDEF'}
|
|
@{$self->{'document_context'}->[-1]->{'formatting_context'}};
|
|
print STDERR "ELEMENT($explanation) (".join('|',@contexts_names)."), ->";
|
|
print STDERR " cmd: $element->{'cmdname'}," if ($element->{'cmdname'});
|
|
print STDERR " type: $element->{'type'}" if ($element->{'type'});
|
|
my $text = $element->{'text'};
|
|
if (defined($text)) {
|
|
$text =~ s/\n/\\n/;
|
|
print STDERR " text: $text";
|
|
}
|
|
# uncomment to show perl objects
|
|
#print STDERR " $element (".join('|',@{$self->{'document_context'}->[-1]->{'formatting_context'}}).")";
|
|
print STDERR "\n";
|
|
}
|
|
|
|
if (ref($element) ne 'HASH') {
|
|
cluck "_convert: tree element not a HASH\n";
|
|
return '';
|
|
}
|
|
|
|
if (($element->{'type'}
|
|
and exists ($self->{'types_conversion'}->{$element->{'type'}})
|
|
and !defined($self->{'types_conversion'}->{$element->{'type'}}))
|
|
or ($element->{'cmdname'}
|
|
and exists($self->{'commands_conversion'}->{$element->{'cmdname'}})
|
|
and !defined($self->{'commands_conversion'}->{$element->{'cmdname'}}))) {
|
|
if ($debug) {
|
|
my $string = 'IGNORED';
|
|
$string .= " \@$element->{'cmdname'}" if ($element->{'cmdname'});
|
|
$string .= " $element->{'type'}" if ($element->{'type'});
|
|
print STDERR "$string\n";
|
|
}
|
|
return '';
|
|
}
|
|
|
|
# Process text
|
|
if (defined($element->{'text'})) {
|
|
# already converted to html, keep it as is
|
|
if ($element->{'type'} and $element->{'type'} eq '_converted') {
|
|
return $element->{'text'};
|
|
}
|
|
if ($element->{'type'} and $element->{'type'} eq 'untranslated') {
|
|
my $translated = $self->gdt($element->{'text'});
|
|
my $result = $self->_convert($translated, 'translated TEXT');
|
|
return $result;
|
|
}
|
|
my $result = &{$self->{'types_conversion'}->{'text'}} ($self,
|
|
$element->{'type'},
|
|
$element,
|
|
$element->{'text'});
|
|
print STDERR "DO TEXT => `$result'\n" if $debug;
|
|
return $result;
|
|
}
|
|
|
|
if ($element->{'extra'} and $element->{'extra'}->{'missing_argument'}
|
|
and (!$element->{'contents'} or !@{$element->{'contents'}})) {
|
|
print STDERR "MISSING_ARGUMENT\n" if $debug;
|
|
return '';
|
|
}
|
|
|
|
# commands like @deffnx have both a cmdname and a def_line type. It is
|
|
# better to consider them as a def_line type, as the whole point of the
|
|
# def_line type is to handle the same the def*x and def* line formatting.
|
|
if ($element->{'cmdname'}
|
|
and !(($element->{'type'} and $element->{'type'} eq 'def_line')
|
|
or ($element->{'type'}
|
|
and $element->{'type'} eq 'definfoenclose_command')
|
|
or ($element->{'type'}
|
|
and $element->{'type'} eq 'index_entry_command'))) {
|
|
my $command_name = $element->{'cmdname'};
|
|
if ($root_commands{$command_name}) {
|
|
$self->{'current_root_command'} = $element;
|
|
}
|
|
if (exists($self->{'commands_conversion'}->{$command_name})) {
|
|
my $convert_to_latex;
|
|
if (exists($brace_commands{$command_name})
|
|
and $brace_commands{$command_name} eq 'context') {
|
|
$self->_new_document_context($command_name);
|
|
}
|
|
if (exists($format_context_commands{$command_name})) {
|
|
push @{$self->{'document_context'}->[-1]->{'formatting_context'}},
|
|
{'context_name' => '@'.$command_name};
|
|
}
|
|
if (exists($block_commands{$command_name})) {
|
|
push @{$self->{'document_context'}->[-1]->{'block_commands'}},
|
|
$command_name;
|
|
}
|
|
if (exists ($composition_context_commands{$command_name})) {
|
|
push @{$self->{'document_context'}->[-1]->{'composition_context'}},
|
|
$command_name;
|
|
}
|
|
if ($pre_class_commands{$command_name}) {
|
|
push @{$self->{'document_context'}->[-1]->{'preformatted_classes'}},
|
|
$pre_class_commands{$command_name};
|
|
}
|
|
if ($format_raw_commands{$command_name}) {
|
|
$self->{'document_context'}->[-1]->{'raw'}++;
|
|
} elsif ($command_name eq 'verbatim') {
|
|
$self->{'document_context'}->[-1]->{'verbatim'}++;
|
|
}
|
|
if ($brace_code_commands{$command_name} or
|
|
$preformatted_code_commands{$command_name}) {
|
|
push @{$self->{'document_context'}->[-1]->{'monospace'}}, 1;
|
|
} elsif ($brace_commands{$command_name}
|
|
and $brace_commands{$command_name} eq 'style_no_code') {
|
|
push @{$self->{'document_context'}->[-1]->{'monospace'}}, 0;
|
|
} elsif ($upper_case_commands{$command_name}) {
|
|
$self->{'document_context'}->[-1]->{'formatting_context'}->[-1]
|
|
->{'upper_case'}++;
|
|
} elsif ($math_commands{$command_name}) {
|
|
$self->{'document_context'}->[-1]->{'math'}++;
|
|
$convert_to_latex = 1 if ($self->get_conf('CONVERT_TO_LATEX_IN_MATH'));
|
|
}
|
|
if ($command_name eq 'verb') {
|
|
$self->{'document_context'}->[-1]->{'formatting_context'}->[-1]
|
|
->{'space_protected'}++;
|
|
} elsif ($command_name eq 'w') {
|
|
$self->{'document_context'}->[-1]->{'formatting_context'}->[-1]
|
|
->{'no_break'}++;
|
|
}
|
|
my $result = '';
|
|
if (defined($self->{'commands_open'}->{$command_name})) {
|
|
$result .= &{$self->{'commands_open'}->{$command_name}}($self,
|
|
$command_name, $element);
|
|
}
|
|
my $content_formatted = '';
|
|
if ($element->{'contents'}) {
|
|
if ($convert_to_latex) {
|
|
$content_formatted
|
|
= Texinfo::Convert::LaTeX::convert_to_latex_math(undef,
|
|
{'contents' => $element->{'contents'}},
|
|
$self->{'options_latex_math'});
|
|
} else {
|
|
my $content_idx = 0;
|
|
foreach my $content (@{$element->{'contents'}}) {
|
|
$content_formatted
|
|
.= _convert($self, $content, "$command_type c[$content_idx]");
|
|
$content_idx++;
|
|
}
|
|
}
|
|
}
|
|
my $args_formatted;
|
|
if ($brace_commands{$command_name}
|
|
or ($line_commands{$command_name}
|
|
and $line_commands{$command_name} eq 'line')
|
|
or (($command_name eq 'item' or $command_name eq 'itemx')
|
|
and ($element->{'parent'}->{'type'}
|
|
and $element->{'parent'}->{'type'} eq 'table_term'))
|
|
or ($command_name eq 'quotation'
|
|
or $command_name eq 'smallquotation')
|
|
or $command_name eq 'float'
|
|
or $command_name eq 'cartouche') {
|
|
$args_formatted = [];
|
|
if ($element->{'args'}) {
|
|
my @args_specification;
|
|
@args_specification = @{$default_commands_args{$command_name}}
|
|
if (defined($default_commands_args{$command_name}));
|
|
my $arg_idx = 0;
|
|
foreach my $arg (@{$element->{'args'}}) {
|
|
my $arg_spec = shift @args_specification;
|
|
$arg_spec = ['normal'] if (!defined($arg_spec));
|
|
my $arg_formatted = {'tree' => $arg};
|
|
foreach my $arg_type (@$arg_spec) {
|
|
my $explanation = "$command_type A[$arg_idx]$arg_type";
|
|
if ($arg_type eq 'normal') {
|
|
if ($convert_to_latex) {
|
|
$arg_formatted->{'normal'}
|
|
= Texinfo::Convert::LaTeX::convert_to_latex_math(undef, $arg,
|
|
$self->{'options_latex_math'});
|
|
} else {
|
|
$arg_formatted->{'normal'} = $self->_convert($arg, $explanation);
|
|
}
|
|
} elsif ($arg_type eq 'monospace') {
|
|
push @{$self->{'document_context'}->[-1]->{'monospace'}}, 1;
|
|
$arg_formatted->{$arg_type} = $self->_convert($arg, $explanation);
|
|
pop @{$self->{'document_context'}->[-1]->{'monospace'}};
|
|
} elsif ($arg_type eq 'string') {
|
|
$self->_new_document_context($command_type);
|
|
$self->{'document_context'}->[-1]->{'string'}++;
|
|
$arg_formatted->{$arg_type} = $self->_convert($arg, $explanation);
|
|
$self->_pop_document_context();
|
|
} elsif ($arg_type eq 'monospacestring') {
|
|
$self->_new_document_context($command_type);
|
|
$self->{'document_context'}->[-1]->{'monospace'}->[-1] = 1;
|
|
$self->{'document_context'}->[-1]->{'string'}++;
|
|
$arg_formatted->{$arg_type} = $self->_convert($arg, $explanation);
|
|
$self->_pop_document_context();
|
|
} elsif ($arg_type eq 'monospacetext') {
|
|
$arg_formatted->{$arg_type}
|
|
= Texinfo::Convert::Text::convert_to_text($arg,
|
|
{'code' => 1,
|
|
Texinfo::Convert::Text::copy_options_for_convert_text($self)});
|
|
} elsif ($arg_type eq 'filenametext') {
|
|
# Always use encoded characters for file names
|
|
$arg_formatted->{$arg_type}
|
|
= Texinfo::Convert::Text::convert_to_text($arg,
|
|
{'code' => 1,
|
|
Texinfo::Convert::Text::copy_options_for_convert_text($self, 1)});
|
|
} elsif ($arg_type eq 'url') {
|
|
# set the encoding to UTF-8 to always have a string that is suitable
|
|
# for percent encoding.
|
|
my $text_conversion_options = {'code' => 1,
|
|
Texinfo::Convert::Text::copy_options_for_convert_text($self, 1)};
|
|
$text_conversion_options->{'enabled_encoding'} = 'utf-8';
|
|
$arg_formatted->{$arg_type}
|
|
= Texinfo::Convert::Text::convert_to_text($arg,
|
|
$text_conversion_options);
|
|
} elsif ($arg_type eq 'raw') {
|
|
$self->{'document_context'}->[-1]->{'raw'}++;
|
|
$arg_formatted->{$arg_type} = $self->_convert($arg, $explanation);
|
|
$self->{'document_context'}->[-1]->{'raw'}--;
|
|
}
|
|
}
|
|
push @$args_formatted, $arg_formatted;
|
|
$arg_idx++;
|
|
}
|
|
}
|
|
}
|
|
if (exists ($composition_context_commands{$command_name})) {
|
|
pop @{$self->{'document_context'}->[-1]->{'composition_context'}};
|
|
}
|
|
if ($pre_class_commands{$command_name}) {
|
|
pop @{$self->{'document_context'}->[-1]->{'preformatted_classes'}};
|
|
}
|
|
if ($preformatted_code_commands{$command_name}
|
|
or ($brace_commands{$command_name}
|
|
and $brace_commands{$command_name} eq 'style_no_code')
|
|
or $brace_code_commands{$command_name}) {
|
|
pop @{$self->{'document_context'}->[-1]->{'monospace'}};
|
|
} elsif ($upper_case_commands{$command_name}) {
|
|
$self->{'document_context'}->[-1]->{'formatting_context'}->[-1]
|
|
->{'upper_case'}--;
|
|
} elsif ($math_commands{$command_name}) {
|
|
$self->{'document_context'}->[-1]->{'math'}--;
|
|
}
|
|
if ($command_name eq 'verb') {
|
|
$self->{'document_context'}->[-1]->{'formatting_context'}->[-1]
|
|
->{'space_protected'}--;
|
|
} elsif ($command_name eq 'w') {
|
|
$self->{'document_context'}->[-1]->{'formatting_context'}->[-1]
|
|
->{'no_break'}--;
|
|
}
|
|
if ($format_raw_commands{$command_name}) {
|
|
$self->{'document_context'}->[-1]->{'raw'}--;
|
|
} elsif ($command_name eq 'verbatim') {
|
|
$self->{'document_context'}->[-1]->{'verbatim'}--;
|
|
}
|
|
if (exists($block_commands{$command_name})) {
|
|
pop @{$self->{'document_context'}->[-1]->{'block_commands'}};
|
|
}
|
|
if (exists($format_context_commands{$command_name})) {
|
|
pop @{$self->{'document_context'}->[-1]->{'formatting_context'}};
|
|
}
|
|
if (exists($brace_commands{$command_name})
|
|
and $brace_commands{$command_name} eq 'context') {
|
|
$self->_pop_document_context();
|
|
}
|
|
|
|
if ($element->{'cmdname'} eq 'node') {
|
|
$self->{'current_node'} = $element;
|
|
}
|
|
# args are formatted, now format the command itself
|
|
if ($args_formatted) {
|
|
if (!defined($self->{'commands_conversion'}->{$command_name})) {
|
|
print STDERR "No command_conversion for $command_name\n";
|
|
} else {
|
|
$result .= &{$self->{'commands_conversion'}->{$command_name}}($self,
|
|
$command_name, $element, $args_formatted, $content_formatted);
|
|
}
|
|
} else {
|
|
$result .= &{$self->{'commands_conversion'}->{$command_name}}($self,
|
|
$command_name, $element, undef, $content_formatted);
|
|
}
|
|
if ($command_name eq 'documentlanguage') {
|
|
$self->_translate_names();
|
|
}
|
|
return $result;
|
|
} else {
|
|
print STDERR "Command not converted: $command_name\n"
|
|
if ($self->get_conf('VERBOSE') or $self->get_conf('DEBUG'));
|
|
return '';
|
|
}
|
|
if ($root_commands{$command_name}) {
|
|
delete $self->{'current_root_command'};
|
|
}
|
|
} elsif ($element->{'type'}) {
|
|
|
|
my $result = '';
|
|
my $type_name = $element->{'type'};
|
|
if (defined($self->{'types_open'}->{$type_name})) {
|
|
$result .= &{$self->{'types_open'}->{$type_name}}($self,
|
|
$type_name, $element);
|
|
}
|
|
if ($type_name eq 'paragraph') {
|
|
$self->{'document_context'}->[-1]->{'formatting_context'}->[-1]
|
|
->{'paragraph_number'}++;
|
|
} elsif ($type_name eq 'preformatted'
|
|
or $type_name eq 'rawpreformatted') {
|
|
$self->{'document_context'}->[-1]->{'formatting_context'}->[-1]
|
|
->{'preformatted_number'}++;
|
|
} elsif ($type_name eq 'unit'
|
|
or $type_name eq 'special_element') {
|
|
$self->{'current_root_element'} = $element;
|
|
} elsif ($self->{'pre_class_types'}->{$type_name}) {
|
|
push @{$self->{'document_context'}->[-1]->{'preformatted_classes'}},
|
|
$self->{'pre_class_types'}->{$type_name};
|
|
push @{$self->{'document_context'}->[-1]->{'composition_context'}},
|
|
$type_name;
|
|
}
|
|
|
|
if ($self->{'code_types'}->{$type_name}) {
|
|
push @{$self->{'document_context'}->[-1]->{'monospace'}}, 1;
|
|
}
|
|
if ($type_name eq '_string') {
|
|
$self->{'document_context'}->[-1]->{'string'}++;
|
|
}
|
|
|
|
my $content_formatted = '';
|
|
if ($type_name eq 'definfoenclose_command') {
|
|
if ($element->{'args'}) {
|
|
$content_formatted = $self->_convert($element->{'args'}->[0]);
|
|
}
|
|
} elsif ($element->{'contents'}) {
|
|
my $content_idx = 0;
|
|
foreach my $content (@{$element->{'contents'}}) {
|
|
$content_formatted
|
|
.= _convert($self, $content, "$command_type c[$content_idx]");
|
|
$content_idx++;
|
|
}
|
|
}
|
|
|
|
if (exists($self->{'types_conversion'}->{$type_name})) {
|
|
$result .= &{$self->{'types_conversion'}->{$type_name}} ($self,
|
|
$type_name,
|
|
$element,
|
|
$content_formatted);
|
|
} elsif (defined($content_formatted)) {
|
|
$result .= $content_formatted;
|
|
}
|
|
if ($self->{'code_types'}->{$type_name}) {
|
|
pop @{$self->{'document_context'}->[-1]->{'monospace'}};
|
|
}
|
|
if ($type_name eq '_string') {
|
|
$self->{'document_context'}->[-1]->{'string'}--;
|
|
}
|
|
if ($type_name eq 'unit' or $type_name eq 'special_element') {
|
|
delete $self->{'current_root_element'};
|
|
} elsif ($self->{'pre_class_types'}->{$type_name}) {
|
|
pop @{$self->{'document_context'}->[-1]->{'preformatted_classes'}};
|
|
pop @{$self->{'document_context'}->[-1]->{'composition_context'}};
|
|
}
|
|
print STDERR "DO type ($type_name) => `$result'\n" if $debug;
|
|
return $result;
|
|
# no type, no cmdname, but contents.
|
|
} elsif ($element->{'contents'}) {
|
|
# this happens inside accents, for section/node names, for @images.
|
|
my $content_formatted = '';
|
|
my $i = 0;
|
|
foreach my $content (@{$element->{'contents'}}) {
|
|
$content_formatted .= $self->_convert($content, "$command_type C[$i]");
|
|
$i++;
|
|
}
|
|
print STDERR "UNNAMED HOLDER => `$content_formatted'\n" if $debug;
|
|
return $content_formatted;
|
|
} else {
|
|
print STDERR "UNNAMED empty\n" if $debug;
|
|
if ($self->{'types_conversion'}->{''}) {
|
|
return &{$self->{'types_conversion'}->{''}} ($self, $element);
|
|
} else {
|
|
return '';
|
|
}
|
|
}
|
|
print STDERR "DEBUG: HERE!($element)\n";
|
|
}
|
|
|
|
sub _set_variables_texi2html($)
|
|
{
|
|
my $options = shift;
|
|
my @texi2html_options = (
|
|
# added hopefully temporarily to be able to validate with W3C validator
|
|
#['DOCTYPE', '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'],
|
|
#['DOCTYPE', '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">'],
|
|
['FORMAT_MENU', 'menu'],
|
|
['NO_USE_SETFILENAME', 1],
|
|
['USE_SETFILENAME_EXTENSION', 0],
|
|
['footnotestyle', 'separate'],
|
|
['CONTENTS_OUTPUT_LOCATION', 'separate_element'],
|
|
['FORCE', 1],
|
|
['AVOID_MENU_REDUNDANCY', 1],
|
|
['USE_ACCESSKEY', 0],
|
|
['NODE_NAME_IN_MENU', 0],
|
|
['SHORT_TOC_LINK_TO_TOC', 0],
|
|
['SHOW_TITLE', 1],
|
|
['USE_UP_NODE_FOR_ELEMENT_UP', 1],
|
|
['USE_REL_REV', 0],
|
|
['USE_LINKS', 0],
|
|
['USE_NODES', 0],
|
|
['SPLIT', ''],
|
|
['PROGRAM_NAME_IN_FOOTER', 1],
|
|
['PROGRAM_NAME_IN_ABOUT', 1],
|
|
['HEADER_IN_TABLE', 1],
|
|
['MENU_ENTRY_COLON', ''],
|
|
['INDEX_ENTRY_COLON', ''],
|
|
['DO_ABOUT', undef],
|
|
['CHAPTER_HEADER_LEVEL', 1],
|
|
['BIG_RULE', '<hr style="height: 6px;">'],
|
|
['FOOTNOTE_END_HEADER_LEVEL', 3],
|
|
['FOOTNOTE_SEPARATE_HEADER_LEVEL', 1],
|
|
['SECTION_BUTTONS', ['FastBack', 'Back', 'Up', 'Forward', 'FastForward',
|
|
' ', ' ', ' ', ' ',
|
|
'Top', 'Contents', 'Index', 'About' ]],
|
|
['TOP_BUTTONS', ['Back', 'Forward', ' ',
|
|
'Contents', 'Index', 'About']],
|
|
|
|
['MISC_BUTTONS', [ 'Top', 'Contents', 'Index', 'About' ]],
|
|
['CHAPTER_BUTTONS', [ 'FastBack', 'FastForward', ' ',
|
|
' ', ' ', ' ', ' ',
|
|
'Top', 'Contents', 'Index', 'About', ]],
|
|
['SECTION_FOOTER_BUTTONS', [ 'FastBack', 'FirstInFileBack', 'FirstInFileUp',
|
|
'Forward', 'FastForward' ]],
|
|
['CHAPTER_FOOTER_BUTTONS', [ 'FastBack', 'FastForward', ' ',
|
|
' ', ' ', ' ', ' ',
|
|
'Top', 'Contents', 'Index', 'About', ]],
|
|
['NODE_FOOTER_BUTTONS', [ 'FastBack', 'Back',
|
|
'Up', 'Forward', 'FastForward',
|
|
' ', ' ', ' ', ' ',
|
|
'Top', 'Contents', 'Index', 'About' ]],
|
|
);
|
|
foreach my $option (@texi2html_options) {
|
|
#no warnings 'once';
|
|
#$defaults{$option->[0]} = $option->[1];
|
|
$options->{$option->[0]} = $option->[1];
|
|
}
|
|
}
|
|
|
|
1;
|
|
|
|
# The documentation of the customization API is in the customization_api
|
|
# Texinfo manual. POD format is not suitable for such a documentation, because
|
|
# of the module documentation style, the language limitations, and also because
|
|
# the customization API involves multiple modules as well as the main program.
|
|
|
|
__END__
|
|
# Automatically generated from maintain/template.pod
|
|
|
|
=head1 NAME
|
|
|
|
Texinfo::Convert::HTML - Convert Texinfo tree to HTML
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
my $converter
|
|
= Texinfo::Convert::HTML->converter({'parser' => $parser});
|
|
|
|
$converter->output($tree);
|
|
$converter->convert($tree);
|
|
$converter->convert_tree($tree);
|
|
$converter->output_internal_links(); # HTML only
|
|
|
|
=head1 NOTES
|
|
|
|
The Texinfo Perl module main purpose is to be used in C<texi2any> to convert
|
|
Texinfo to other formats. There is no promise of API stability.
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
Texinfo::Convert::HTML converts a Texinfo tree to HTML.
|
|
|
|
=head1 METHODS
|
|
|
|
=over
|
|
|
|
=item $converter = Texinfo::Convert::HTML->converter($options)
|
|
|
|
Initialize converter from Texinfo to HTML.
|
|
|
|
The I<$options> hash reference holds options for the converter. In
|
|
this option hash reference a L<parser object|Texinfo::Parser>
|
|
may be associated with the I<parser> key. The other options
|
|
are Texinfo customization options and a few other options that can
|
|
be passed to the converter. Most of the customization options are described in
|
|
the Texinfo manual. Those customization options, when appropriate, override
|
|
the document content. The parser should not be available directly anymore
|
|
after getting the associated information.
|
|
|
|
See L<Texinfo::Convert::Converter> for more information.
|
|
|
|
=item $converter->output($tree)
|
|
|
|
Convert a Texinfo tree I<$tree> and output the result in files as
|
|
described in the Texinfo manual.
|
|
|
|
=item $result = $converter->convert($tree)
|
|
|
|
Convert a Texinfo tree I<$tree> and return the resulting output.
|
|
|
|
=item $result = $converter->convert_tree($tree)
|
|
|
|
Convert a Texinfo tree portion I<$tree> and return the resulting
|
|
output. This function does not try to output a full document but only
|
|
portions. For a full document use C<convert>.
|
|
|
|
=item $result = $converter->output_internal_links()
|
|
X<C<output_internal_links>>
|
|
|
|
Returns text representing the links in the document. The format should
|
|
follow the C<--internal-links> option of the C<texi2any>
|
|
specification. This is only supported in (and relevant for) HTML.
|
|
|
|
=back
|
|
|
|
=head1 AUTHOR
|
|
|
|
Patrice Dumas, E<lt>pertusus@free.frE<gt>
|
|
|
|
=head1 COPYRIGHT AND LICENSE
|
|
|
|
Copyright 2010- Free Software Foundation, Inc. See the source file for
|
|
all copyright years.
|
|
|
|
This library is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3 of the License, or (at
|
|
your option) any later version.
|
|
|
|
=cut
|