blob: dfaf5e174424fd9000511f2990765681015001b8 [file]
# highlight_syntax.pm: interface to source-highlight for syntax highlighting
#
# Copyright (C) 2021-2026 Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License,
# or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
use 5.006;
use strict;
# To check if there is no erroneous autovivification
#no autovivification qw(fetch delete exists store strict);
use IPC::Open3;
use Symbol;
use Texinfo::Commands;
# also for __(
use Texinfo::Common;
use Texinfo::Convert::Text;
use Texinfo::Convert::NodeNameNormalization;
my %languages_extensions = (
'texinfo' => 'texi',
'perl' => 'pl',
);
# No list of languages and no check on language if undef.
# Otherwise a hash reference with possible languages as keys.
my $highlighted_languages_list;
# the list of @-commands that can be highlighted.
# Each of these commands should have a command formatting function
# registered.
my @highlighted_commands_names = ('example');
# The @-commands highlighting is done in two steps
# 1) in highlight_process, before the start of the HTML conversion,
# * the highlighted commands are collected by going through the
# Texinfo tree
# * each collected Texinfo tree element programming language is
# determined and the tree elements are converted to text if a
# programming language is found.
# The text goes through a program that returns highlighted HTML
# code.
# - with 'source-highlight' a file is setup by language, the
# program is called once per language with line counts
# and separators used to separate the fragments.
# - otherwise the program is called for each of the Texinfo tree
# element with the language in the command-line, text is
# passed on the standard input and the highlighted code fragment
# is read from the standard output.
# The Texinfo tree element is associated to the highlighted
# HTML code fragment in this step.
# 2) During the conversion, the command formatting function associated
# to each of the Texinfo tree command elements that could be highlighted
# is called. In these functions (currently only one,
# highlight_preformatted_command), the highlighted HTML code fragment
# associated to the tree element is found and output.
texinfo_register_handler('setup', \&highlight_setup);
texinfo_register_handler('structure', \&highlight_process);
texinfo_register_command_formatting('example', \&highlight_preformatted_command);
# normally this is done in preformatted type, but preformatted
# types conversion output in example is discarded in
# highlight_preformatted_command, so register a replacement.
# Register inline pending content when opening an example block.
texinfo_register_command_opening('example',
\&highlight_open_inline_container_type);
# Run highlighting command to set $highlighted_languages_list to list
# of recognized languages. This is used to avoid calling the highlighter
# with an unrecognized language.
sub highlight_setup($$) {
my ($self, $document) = @_;
my $document_root = $document->tree();
$highlighted_languages_list = undef;
my $highlight_syntax = $self->get_conf('HIGHLIGHT_SYNTAX');
return 1 if (!defined($highlight_syntax) or $highlight_syntax !~ /\S/);
my $cmd;
my $highlight_type;
if ($highlight_syntax eq 'highlight') {
$highlight_type = 'highlight';
$cmd = 'highlight --list-scripts=lang';
} elsif ($highlight_syntax eq 'pygments') {
$highlight_type = 'pygments';
$cmd = 'pygmentize -L lexers';
} elsif ($highlight_syntax eq 'source-highlight') {
$highlight_type = 'source-highlight';
$cmd = 'source-highlight --lang-list';
} else {
# $highlight_syntax will be used as command, with language substituted
return 0;
}
%$highlighted_languages_list = ();
# NOTE open failure triggers a warning message if run with -w if the
# file is not found. This message can be catched with $SIG{__WARN__}.
# This message is along:
# Can't exec "source-highlight": No such file or directory at ./init/highlight_syntax.pm line XX
# This message is redundant with the message registered below with
# document_error, using $!. $! is set to: No such file or directory
# Tried to show both messages, but through the $self->document_*()
# facility, either by getting the warning message in the main context or by
# register the warning message, but failed. So simply silence the redundant
# message.
# does not store the message from within the sub but syntactically
# needed.
my $message;
local $SIG{__WARN__} = sub {
$message = shift;
# this shows the message
#warn "$message";
# not sure why, but this does not work, the warning is not actually
# registered, as if it was done in a scope that is later destroyed.
#$self->converter_document_warn(sprintf(__('%s: %s'), $cmd, $message));
};
my $status = open(HIGHLIGHT_LANG_LIST, '-|', $cmd);
$SIG{__WARN__} = undef;
if (not($status)) {
$self->converter_document_error(sprintf(__('%s: %s'), $cmd, $!));
return 1;
}
my $line;
if ($highlight_type eq 'highlight') {
my $in_languages;
while (defined($line = <HIGHLIGHT_LANG_LIST>)) {
chomp($line);
#print STDERR "LL $line\n";
if (!$in_languages) {
if ($line =~ /^.+: [a-z]/) {
$in_languages = 1;
} else {
next;
}
}
#print STDERR "$line\n";
if ($line =~ /^.+: ([a-z0-9+_\/-]+)( \( (([a-z0-9+_\/-]+ )+)\))?$/) {
my $main_language = $1;
my $other_languages = $3;
$highlighted_languages_list->{$main_language} = 1;
} else {
last;
}
}
# read the remaining lines, otherwise there is an error when closing
# the pipe.
while (defined($line = <HIGHLIGHT_LANG_LIST>)) {};
#use Data::Dumper;
#print STDERR Data::Dumper->Dump([$highlighted_languages_list]);
#exit 1;
} elsif ($highlight_type eq 'pygments') {
while (defined($line = <HIGHLIGHT_LANG_LIST>)) {
chomp($line);
if ($line =~ /^\* (.+):$/) {
my @languages = split (/, /, $1);
if (scalar(@languages) == 0) {
$self->converter_document_warn(sprintf(__(
'%s: %s: cannot parse language line'), $cmd, $line))
} else {
my $main_language = shift @languages;
$highlighted_languages_list->{$main_language} = 1;
}
}
}
#use Data::Dumper;
#print STDERR Data::Dumper->Dump([$highlighted_languages_list]);
#exit 1;
} else { # $highlight_type eq 'source-highlight'
while (defined($line = <HIGHLIGHT_LANG_LIST>)) {
chomp($line);
if ($line =~ /^([A-Za-z0-9_\-]+) =/) {
my $language = $1;
$highlighted_languages_list->{$language} = 1;
} else {
$self->converter_document_warn(sprintf(__(
'%s: %s: cannot parse language line'), $cmd, $line))
}
}
}
if (!close(HIGHLIGHT_LANG_LIST)) {
$self->converter_document_warn(sprintf(__(
"%s: closing communication failed (%s): %s: %s"),
$highlight_type, $?, $cmd, $!));
}
if (scalar(keys(%$highlighted_languages_list)) == 0) {
# important if $cmd returns no output to have a message. If there
# is some output, there will already be some line parse error messages.
$self->converter_document_warn(sprintf(__(
'%s: no highlighted language found'), $cmd));
# the remaining will be skipped, but no error is returned
}
return 0;
}
# Determine the Texinfo tree element $COMMAND converted language command
# argument, or set it with HIGHLIGHT_SYNTAX_DEFAULT_LANGUAGE (if set).
# Determine the normalized language name. If there is a list of known
# languages, check if the language is known.
# Returns the (normalized language, converted language) pair. Both
# could be undef.
sub _get_language($$) {
my ($self, $command) = @_;
my $language;
my $converted_language;
my $cmdname = $command->{'cmdname'};
if ($cmdname eq 'example') {
my $arguments_line = $command->{'contents'}->[0];
if (exists($arguments_line->{'contents'})) {
$converted_language
= Texinfo::Convert::NodeNameNormalization::convert_to_normalized(
$arguments_line->{'contents'}->[0]);
if ($converted_language eq '') {
$converted_language = undef;
}
}
}
if (not defined($converted_language) and defined($self)) {
my $default_highlight_language
= $self->get_conf('HIGHLIGHT_SYNTAX_DEFAULT_LANGUAGE');
if (defined($default_highlight_language)) {
$converted_language = $default_highlight_language;
}
}
if (defined($converted_language)
and (!defined($highlighted_languages_list)
or exists($highlighted_languages_list->{$converted_language}))) {
return ($converted_language, $converted_language);
} else {
return (undef, $converted_language);
}
}
# Convert element argument to simple text.
sub _convert_element($$) {
my ($self, $element) = @_;
my @contents = @{$element->{'contents'}};
while (scalar(@contents) > 0
and exists($contents[0]->{'type'})
# ignorable_spaces_after_command cannot actually happen with
# @example
and ($contents[0]->{'type'} eq 'ignorable_spaces_after_command'
or $contents[0]->{'type'} eq 'arguments_line')) {
shift @contents;
}
if (scalar(@contents) > 0 and exists($contents[-1]->{'cmdname'})
and $contents[-1]->{'cmdname'} eq 'end') {
pop @contents;
}
return "\n" if (!scalar(@contents));
Texinfo::Convert::Text::set_options_code(
$self->{'convert_text_options'});
my $text = Texinfo::Convert::Text::convert_to_text(
{'contents' => \@contents},
$self->{'convert_text_options'});
Texinfo::Convert::Text::reset_options_code(
$self->{'convert_text_options'});
# make sure that the text ends with a newline
chomp ($text);
$text .= "\n";
return $text;
}
# The end of the string was randomly generated once for all.
# Used for 'source-highlight' only.
my $range_separator
= '_______________________________________ highlight texinfo _GT Haib0aik zei4YieH';
# Keys are commands names (currently only 'example').
# Associates a command name and element to the resulting highlighted text
# in 'result'.
# Also holds per language counters:
# input_languages_counters: number of collected Texinfo tree elements
# for a command name and language
# retrieved_languages_counters: number of highlighted HTML fragments
# retrieved for a command name and language.
# output_languages_counters: number of highlighted HTML fragments
# output in the converted output.
my %highlighted_cmds;
# Initialize %highlighted_cmds for $CMDNAME command name and $LANGUAGE
# language and increase the input counter.
sub _add_collected_command_language($$) {
my ($cmdname, $language) = @_;
$highlighted_cmds{$cmdname} = {'input_languages_counters' => {},
'results' => {},
'retrieved_languages_counters' => {},
'output_languages_counters' => {}}
if (not exists($highlighted_cmds{$cmdname}));
# initialize per language counters
if (not exists($highlighted_cmds{$cmdname}
->{'input_languages_counters'}->{$language})) {
$highlighted_cmds{$cmdname}->{'input_languages_counters'}->{$language} = 0;
$highlighted_cmds{$cmdname}
->{'retrieved_languages_counters'}->{$language} = 0;
$highlighted_cmds{$cmdname}->{'output_languages_counters'}->{$language} = 0;
}
$highlighted_cmds{$cmdname}->{'input_languages_counters'}->{$language} += 1;
}
# Replace any instances of %l in $HIGHLIGHT_CMD with $LANGUAGE, and any
# instances of %% with %.
sub _substitute_language {
my ($highlight_cmd, $language) = @_;
my $result;
while ($highlight_cmd =~ s/^([^%]*)%(.)//) {
$result .= $1;
if ($2 eq '%') {
$result .= '%';
} elsif ($2 eq 'l') {
$result .= $language;
} else {
# unrecognized placeholder
$result .= '%';
$result .= $2;
}
}
$result .= $highlight_cmd;
return $result;
}
# collect all the highlighted commands Texinfo tree elements with an
# associated language, convert to text, call the highliting command.
# Store the highlighted HTML code in %highlighted_cmds 'results', associated
# to the Texinfo tree element.
sub highlight_process($$) {
my ($self, $document) = @_;
my $document_root = $document->tree();
# Reset in case multiple manuals are processed.
%highlighted_cmds = ();
return 0 if (defined($self->get_conf('OUTFILE'))
and Texinfo::Common::is_null_device_name($self->get_conf('OUTFILE')));
return 0 if (defined($highlighted_languages_list)
and !scalar(keys(%$highlighted_languages_list)));
my $highlight_syntax = $self->get_conf('HIGHLIGHT_SYNTAX');
return 1 if (!defined($highlight_syntax) or $highlight_syntax !~ /\S/);
my $verbose = $self->get_conf('VERBOSE');
my $collected_commands
= Texinfo::Common::collect_commands_in_tree($document_root,
\@highlighted_commands_names);
if ($highlight_syntax ne 'source-highlight') {
# Pass each fragment to a command.
foreach my $cmdname (@highlighted_commands_names) {
if (scalar(@{$collected_commands->{$cmdname}}) > 0) {
foreach my $element (@{$collected_commands->{$cmdname}}) {
my ($language, $converted_language)
= _get_language($self, $element);
if (!defined($language)) {
if (defined($converted_language) and $verbose) {
warn "# highlight_syntax: language not found:"
." $converted_language\n";
}
next;
}
_add_collected_command_language($cmdname, $language);
my $text = _convert_element($self, $element);
my $cmd;
if ($highlight_syntax eq 'highlight') {
# With %l placeholder:
#$highlight_cmd = 'highlight -f --style-outfile=html --inline-css '
# .'--syntax=%l';
$cmd = 'highlight -f --style-outfile=html --inline-css '
.'--syntax='.$language;
} elsif ($highlight_syntax eq 'pygments') {
# With %l placeholder:
#$highlight_cmd = 'pygmentize -f html -O noclasses=True -l %l';
$cmd = 'pygmentize -f html -O noclasses=True -l '.$language;
} else {
$cmd = _substitute_language($highlight_syntax, $language);
}
my ($wtr, $rdr, $err);
$err = gensym();
my $pid = IPC::Open3::open3($wtr, $rdr, $err, $cmd);
if (! $pid) {
$self->converter_document_error(sprintf(__('%s: %s'), $cmd, $!));
return 1;
}
binmode($wtr, ':utf8');
binmode($rdr, ':utf8');
# not so sure here. Use locale?
binmode($err, ':utf8');
print $wtr $text;
if (!close($wtr)) {
$self->converter_document_error(
sprintf(__('%s: error closing input: %s'), $cmd, $!));
close($rdr);
close($err);
return 1;
}
my @outlines = <$rdr>;
my @errlines = <$err>;
my $status = 0;
if (!close($rdr)) {
$self->converter_document_error(
sprintf(__('%s: error closing output: %s'), $cmd, $!));
$status = 1;
}
if (!close($err)) {
$self->converter_document_error(
sprintf(__('%s: error closing errors: %s'), $cmd, $!));
$status = 1;
}
waitpid($pid, 0);
if (@errlines) {
$status = 1;
my $errline = shift @errlines;
chomp($errline);
$self->converter_document_error(sprintf(__('%s: errors: %s'),
$cmd, $errline));
foreach my $error_line (@errlines) {
chomp($error_line);
$self->converter_document_error(sprintf(__(' %s'),
$error_line), 1);
}
}
return 1 if ($status);
my $cmdname = $element->{'cmdname'};
$highlighted_cmds{$cmdname}->{'results'}->{$element}
= join('', @outlines);
$highlighted_cmds{$cmdname}->{'retrieved_languages_counters'}
->{$language}++;
}
}
}
return 0;
}
# case of 'source-highlight'.
# Collect per language elements and output all the texts to be highlighted
# in a file per language.
my %languages = ();
foreach my $cmdname (@highlighted_commands_names) {
if (scalar(@{$collected_commands->{$cmdname}}) > 0) {
foreach my $element (@{$collected_commands->{$cmdname}}) {
my ($language, $converted_language)
= _get_language($self, $element);
if (defined($language)) {
_add_collected_command_language($cmdname, $language);
$languages{$language} = {'counter' => 0, 'commands' => [],
'line_ranges' => []}
if (not exists($languages{$language}));
$languages{$language}->{'counter'}++;
my $counter = $languages{$language}->{'counter'};
$languages{$language}->{'commands'}->[$counter-1] = $element;
} elsif (defined($converted_language) and $verbose) {
warn "# highlight_syntax: language not found: $converted_language\n";
}
}
}
}
my $document_name = $self->get_info('document_name');
my $highlight_basename = "${document_name}_highlight";
my $highlight_out_dir = $self->get_info('destination_directory');
if (! -d $highlight_out_dir) {
if (!mkdir $highlight_out_dir) {
$self->converter_document_warn(
sprintf(__('highlight_syntax.pm: could not make directory %s: %s'),
$highlight_out_dir, $!));
}
}
foreach my $language (keys(%languages)) {
my $suffix;
if (defined($languages_extensions{$language})) {
$suffix = $languages_extensions{$language};
} else {
$suffix = $language
}
my $language_base = ${highlight_basename} . "_${language}";
$languages{$language}->{'basefile'} = $language_base . "_input.$suffix";
$languages{$language}->{'html_file'} = $language_base . '_output.html';
my $input_language_path_name
= join('/', ($highlight_out_dir, $languages{$language}->{'basefile'}));
my $html_result_path_name
= join('/', ($highlight_out_dir, $languages{$language}->{'html_file'}));
# expand @example texts in an input file for highlight source
# program
my ($encoded_input_language_path_name, $input_language_path_encoding)
= $self->encoded_output_file_name($input_language_path_name);
unless (open(HIGHLIGHT_LANG_IN, ">$encoded_input_language_path_name")) {
$self->converter_document_warn(
sprintf(__('highlight_syntax.pm: could not open %s: %s'),
$input_language_path_name, $!));
return 1;
}
my $output_encoding;
if (defined($self->get_conf('OUTPUT_ENCODING_NAME'))) {
my $encoding_name = $self->get_conf('OUTPUT_ENCODING_NAME');
$output_encoding
= Texinfo::Common::processing_output_encoding($encoding_name);
binmode(HIGHLIGHT_LANG_IN, ":encoding($output_encoding)");
}
print HIGHLIGHT_LANG_IN "Automatically generated\n\n";
my $highlight_lang_in_line_nr = 2;
my $counter = 0;
foreach my $element (@{$languages{$language}->{'commands'}}) {
my $text = _convert_element($self, $element);
# count the number of record separator $/
my $buffer = $text;
my $text_lines_nr = ( $buffer =~ s|$/||g );
print HIGHLIGHT_LANG_IN "_______________________ $counter\n";
print HIGHLIGHT_LANG_IN $text;
print HIGHLIGHT_LANG_IN "_______________________ $counter\n";
$languages{$language}->{'line_ranges'}->[$counter]
= [$highlight_lang_in_line_nr+1 +1,
$highlight_lang_in_line_nr + $text_lines_nr+1];
$highlight_lang_in_line_nr += 2 + $text_lines_nr;
$counter++;
}
if (! close(HIGHLIGHT_LANG_IN)) {
$self->converter_document_warn(
sprintf(__('highlight_syntax.pm: error on closing %s: %s'),
$input_language_path_name, $!));
return 1;
}
# call source highlighting program
my $version_option='';
$version_option='--gen-version ' if ($self->get_conf('TEST'));
my @option_line_ranges = ();
foreach my $line_range (@{$languages{$language}->{'line_ranges'}}) {
push @option_line_ranges, '"'.$line_range->[0].'-'.$line_range->[1].'"';
}
my $option_line_range_str = join(',', @option_line_ranges);
my $cmd = "source-highlight ${version_option}"
."--src-lang=$language --out-format=html5 "
."-i '$input_language_path_name' -o '$html_result_path_name' "
."--line-range=$option_line_range_str --range-separator='$range_separator'";
warn "# highlight_syntax: exec ($language): $cmd\n" if ($verbose);
my $encoding = $self->get_conf('COMMAND_LINE_ENCODING');
my $encoded_cmd;
if (defined($encoding)) {
$encoded_cmd = encode($encoding, $cmd);
} else {
$encoded_cmd = $cmd;
}
if (system($encoded_cmd)) {
$self->converter_document_error(
sprintf(__('highlight_syntax.pm: command did not succeed: %s'),
$cmd));
return 1;
}
my $language_fragments_nr = $languages{$language}->{'counter'};
# extract highlighted fragments
my ($encoded_html_result_path_name, $html_result_path_encoding)
= $self->encoded_output_file_name($html_result_path_name);
unless (open(HIGHLIGHT_LANG_OUT, $encoded_html_result_path_name)) {
$self->converter_document_warn(
sprintf(__('highlight_syntax.pm: could not open %s: %s'),
$html_result_path_name, $!));
return 1;
}
binmode(HIGHLIGHT_LANG_OUT, ":encoding($output_encoding)")
if (defined($output_encoding));
my $got_count = 0;
my $line;
my $text;
my $separators_count = 0;
while (defined($line = <HIGHLIGHT_LANG_OUT>)) {
#print STDERR "$encoded_html_result_path_name: while $line";
if ($line =~ /$range_separator/) {
$separators_count++;
if (defined($text)) {
$got_count++;
my $element = $languages{$language}->{'commands'}->[$got_count-1];
my $cmdname = $element->{'cmdname'};
$highlighted_cmds{$cmdname}->{'results'}->{$element} = $text;
$highlighted_cmds{$cmdname}->{'retrieved_languages_counters'}
->{$language}++;
$text = undef;
}
#print STDERR "$language $got_count $language_fragments_nr \n";
if ($got_count < $language_fragments_nr) {
$text = '';
}
} else {
if (defined($text)) {
$text .= $line;
}
}
}
if ($separators_count != $language_fragments_nr +1) {
$self->converter_document_warn(sprintf(__(
'highlight_syntax.pm: %s: %d separators; expected %d, the number of fragments +1'),
$language, $separators_count, $language_fragments_nr+1));
}
if (defined($text) and $text ne '') {
my $element = $languages{$language}->{'commands'}->[$got_count-1];
my $cmdname = $element->{'cmdname'};
$self->converter_document_warn(sprintf(__(
'highlight_syntax.pm: %s: end of @%s item %d not found'),
$language, $cmdname, $got_count));
}
# note that this check is not the most detailed that could be done, a check
# by command could also be done. Since for now there is only @example
# it is useless, and even if there were other commands, the failure is
# for a language, not a command, so it should not be needed either.
if ($got_count != $languages{$language}->{'counter'}) {
$self->converter_document_warn(sprintf(__(
'highlight_syntax.pm: %s: retrieved %d items in HTML; expected %d'),
$language, $got_count, $language_fragments_nr));
}
if (!close(HIGHLIGHT_LANG_OUT)) {
$self->converter_document_warn(
sprintf(__('highlight_syntax.pm: error on closing %s: %s'),
$html_result_path_name, $!));
}
}
return 0;
}
sub highlight_open_inline_container_type($$$) {
my ($self, $cmdname, $command) = @_;
if (defined($highlighted_languages_list)
and !scalar(keys(%$highlighted_languages_list))) {
my $default_open = $self->default_command_open($cmdname);
if (defined($default_open)) {
return &{$default_open}($self, $cmdname, $command);
} else {
return '';
}
}
if (exists($highlighted_cmds{$cmdname})
and exists($highlighted_cmds{$cmdname}->{'results'})) {
my ($language, $converted_language)
= _get_language($self, $command);
if (exists($highlighted_cmds{$cmdname}->{'results'}->{$command})
and defined($highlighted_cmds{$cmdname}->{'results'}->{$command})) {
# only replace the example and inside preformatted if the code leading
# to get_associated_formatted_inline_content being called in
# example highlighted text formatting will be called
if (defined($language)) {
my $pending_formatted = $self->get_pending_formatted_inline_content();
if (defined($pending_formatted)) {
$self->associate_pending_formatted_inline_content($command,
$pending_formatted);
}
}
}
}
return '';
}
my %small_block_associated_command
= %Texinfo::Common::small_block_associated_command;
sub highlight_preformatted_command($$$$$) {
my ($self, $cmdname, $command, $args, $content) = @_;
# if no commands were registered nor converted, do not
# warn if the language is known. It means that there was
# no highlighting or some error.
if (exists($highlighted_cmds{$cmdname})
and exists($highlighted_cmds{$cmdname}->{'results'})) {
my ($language, $converted_language)
= _get_language($self, $command);
if (exists($highlighted_cmds{$cmdname}->{'results'}->{$command})
and defined($highlighted_cmds{$cmdname}->{'results'}->{$command})) {
if (not defined($language)) {
$self->converter_document_warn(sprintf(__(
'highlight_syntax.pm: output has HTML item for @%s but no language %s'),
$cmdname, $command));
} else {
$highlighted_cmds{$cmdname}->{'output_languages_counters'}
->{$language}++;
if ($self->in_string()) {
$content = '' if (!defined($content));
return $content;
}
# need to do all the formatting done for content inside
# of @example as it is discarded. So need to do the preformatted
# type formatting, from _convert_preformatted_type() and
# _preformatted_class().
my $result_content
= $highlighted_cmds{$cmdname}->{'results'}->{$command};
####### begin sync with _convert_preformatted_type
# NOTE specific formatting for specific contexts is not done here.
# do it here, it should have been correctly registered with example
# and not preformatted through highlight_open_inline_container_type.
$result_content
= $self->get_associated_formatted_inline_content($command)
. $result_content;
# a newline immediately after a <pre> is ignored.
$result_content =~ s/^\n/\n\n/;
# following code replaces the next line
# $pre_class = _preformatted_class($self) if (!defined($pre_class));
####### begin sync with _preformatted_class
my $pre_class;
my $preformatted_classes_stack = $self->preformatted_classes_stack();
# Since we are formatting @example itself, it is not on the preformatted
# class stack anymore, so we readd.
my @pre_classes = @{$preformatted_classes_stack};
# Code similar to code setting %pre_class_commands; using
# $pre_class_commands{$cmdname} as in the HTML converter to
# put on the preformatted classes stack cannot be used directly,
# as it is private.
if (exists($small_block_associated_command{$cmdname})) {
push @pre_classes, $small_block_associated_command{$cmdname};
} else {
push @pre_classes, $cmdname;
}
foreach my $class (@pre_classes) {
$pre_class = $class unless (defined($pre_class)
and exists($Texinfo::Commands::preformatted_code_commands{$pre_class})
and !(exists($Texinfo::Commands::preformatted_code_commands{$class})
or $class eq 'menu'));
}
$pre_class = $pre_class.'-preformatted';
####### end sync with _preformatted_class
my $preformatted_result_content = $self->html_attribute_class('pre',
[$pre_class]).">".$result_content."</pre>";
####### end sync with _convert_preformatted_type
###### begin sync with _convert_preformatted_command
my @classes;
# this is mainly for classes as there are purprosely no classes
# for small*
my $main_cmdname;
if (exists($small_block_associated_command{$cmdname})) {
$main_cmdname = $small_block_associated_command{$cmdname};
push @classes, $cmdname;
} else {
$main_cmdname = $cmdname;
}
if ($cmdname eq 'example') {
# arguments_line type element
my $arguments_line = $command->{'contents'}->[0];
if (exists($arguments_line->{'contents'})) {
for my $example_arg (@{$arguments_line->{'contents'}}) {
# convert or remove all @-commands, using simple ascii and unicode
# characters
my $converted_arg
= Texinfo::Convert::NodeNameNormalization::convert_to_normalized(
$example_arg);
if ($converted_arg ne '') {
push @classes, 'user-' . $converted_arg;
}
}
}
} elsif ($main_cmdname eq 'lisp') {
push @classes, $main_cmdname;
$main_cmdname = 'example';
}
unshift @classes, $main_cmdname;
return $self->html_attribute_class('div', \@classes).">\n"
.$preformatted_result_content.'</div>'."\n";
####### end sync with _convert_preformatted_command
}
# no error nor verbose message if there was no retrieved information
# for that language
} elsif (defined($language)
and $highlighted_cmds{$cmdname}->{'retrieved_languages_counters'}
->{$language}) {
my $cmd_language_input_count
= $highlighted_cmds{$cmdname}->{'input_languages_counters'}
->{$language};
my $cmd_language_retrieved_count
= $highlighted_cmds{$cmdname}->{'retrieved_languages_counters'}
->{$language};
# Output an message only if the counters are equal, meaning language
# was processed without failure.
# If they are not equal there should have been a message already.
if ($cmd_language_input_count == $cmd_language_retrieved_count) {
$self->converter_document_warn(sprintf(__(
'highlight_syntax.pm: output has no HTML item for @%s %s %s'),
$cmdname, $language, $command));
} elsif ($self->get_conf('VERBOSE') or $self->get_conf('DEBUG')) {
warn "highlight_syntax.pm: output has no HTML item for \@$cmdname $language $command\n";
}
}
}
# fall back on default conversion
return &{$self->default_command_conversion($cmdname)}($self, $cmdname,
$command, $args, $content);
}
1;