blob: eb94bbb5884187e49b04c64836dfdcc58ec55e02 [file]
/* Copyright 2010-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/>. */
#include <string.h>
#include <stdio.h>
/* Avoid namespace conflicts. */
#define context perl_context
#define PERL_NO_GET_CONTEXT
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#undef context
#include "html_conversion_data.h"
#include "tree_types.h"
#include "converter_types.h"
/* bug */
#include "base_utils.h"
/* non_perl_free */
#include "xs_utils.h"
/* for builtin_command_name */
#include "builtin_commands.h"
/* for HMSF_* */
#include "utils.h"
/* next two could be interesting for debugging */
/*
#include "debug.h"
#include "convert_to_texinfo.h"
*/
#include "command_stack.h"
/* also for perl_only_* wrappers */
#include "build_perl_info.h"
/* for NAMED_STRING_ELEMENT_LIST */
#include "translations.h"
/* for no_arg_formatted_cmd direction_string_type_names
direction_string_context_names html_conversion_context_type_names
*/
#include "html_converter_types.h"
/* for htmlxref_split_type_names
html_nr_string_directions */
#include "html_prepare_converter.h"
/* to debug refcounts */
#include "api_to_perl.h"
#include "build_html_perl_info.h"
/* See the NOTE in build_perl_info.c on use of functions related to
memory allocation */
static HV *
build_htmlxref (HTMLXREF_MANUAL_LIST *htmlxref_list)
{
HV *htmlxref_hv;
size_t i;
dTHX;
htmlxref_hv = newHV ();
for (i = 0; i < htmlxref_list->number; i++)
{
HTMLXREF_MANUAL *htmlxref_manual = &htmlxref_list->list[i];
const char *manual_name = htmlxref_manual->manual;
SV *manual_name_sv = newSVpv_utf8 (manual_name, 0);
HV *htmlxref_manual_hv = newHV ();
SV *htmlxref_manual_sv = newRV_noinc ((SV *) htmlxref_manual_hv);
enum htmlxref_split_type j;
hv_store_ent (htmlxref_hv, manual_name_sv, htmlxref_manual_sv, 0);
for (j = 0; j < htmlxref_split_type_chapter+1; j++)
{
if (htmlxref_manual->urlprefix[j])
{
const char *split_type_name = htmlxref_split_type_names[j];
const char *href = htmlxref_manual->urlprefix[j];
hv_store (htmlxref_manual_hv, split_type_name,
strlen (split_type_name),
newSVpv_utf8 (href, 0), 0);
}
}
}
return htmlxref_hv;
}
void
html_pass_htmlxref (HTMLXREF_MANUAL_LIST *htmlxref_list, SV *converter_sv)
{
HV *converter_hv;
HV *htmlxref_hv;
dTHX;
converter_hv = (HV *) SvRV (converter_sv);
htmlxref_hv = build_htmlxref (htmlxref_list);
hv_store (converter_hv, "htmlxref", strlen ("htmlxref"),
newRV_noinc ((SV *) htmlxref_hv), 0);
}
SV *
build_no_arg_commands_formatting (const CONVERTER *converter)
{
HV *no_arg_commands_formatting_hv;
size_t i;
dTHX;
no_arg_commands_formatting_hv = newHV ();
for (i = 0; i < no_arg_formatted_cmd.number; i++)
{
enum command_id cmd = no_arg_formatted_cmd.list[i];
enum conversion_context cctx;
const char *command_name = builtin_command_name (cmd);
const HTML_NO_ARG_COMMAND_FORMATTING *no_arg_formatting
= &converter->html_no_arg_command_conversion[cmd];
HV *context_hv = newHV ();
hv_store (no_arg_commands_formatting_hv, command_name,
strlen (command_name), newRV_noinc ((SV *) context_hv), 0);
if (no_arg_formatting->translated_to_convert)
{
hv_store (context_hv, "translated_to_convert",
strlen ("translated_to_convert"),
newSVpv_utf8 (no_arg_formatting->translated_to_convert, 0), 0);
}
for (cctx = 0; cctx < NO_ARG_COMMAND_CONTEXT_NR; cctx++)
{
const HTML_NO_ARG_COMMAND_CONVERSION *no_arg_format
= &no_arg_formatting->context_formatting[cctx];
const char *context_name = html_conversion_context_type_names[cctx];
HV *spec_hv = newHV ();
hv_store (context_hv, context_name, strlen (context_name),
newRV_noinc ((SV *) spec_hv), 0);
#define STORE(key, sv) hv_store (spec_hv, key, strlen (key), sv, 0)
#define STORE_FIELD(field) \
if (no_arg_format->field) \
STORE(#field, newSVpv_utf8 (no_arg_format->field, 0));
STORE_FIELD(element)
if (no_arg_format->unset)
STORE("unset", newSViv (1));
STORE_FIELD(text)
STORE_FIELD(translated_converted)
#undef STORE_FIELD
#undef STORE
}
}
return newRV_noinc ((SV *) no_arg_commands_formatting_hv);
}
#define STORE(key, sv) hv_store (converter_hv, key, strlen (key), sv, 0)
void
html_pass_converter_initialization_state (const CONVERTER *converter,
HV *converter_hv)
{
SV *no_arg_commands_formatting_sv;
HV *shared_conversion_state_hv;
dTHX;
no_arg_commands_formatting_sv = build_no_arg_commands_formatting (converter);
STORE("no_arg_commands_formatting", no_arg_commands_formatting_sv);
shared_conversion_state_hv = newHV ();
STORE("shared_conversion_state",
newRV_noinc ((SV *)shared_conversion_state_hv));
}
void
html_pass_conversion_initialization (CONVERTER *converter,
SV *converter_sv)
{
HV *converter_hv;
HV *converter_info_hv;
HV *translation_cache_hv;
SV **converter_info_sv;
dTHX;
converter_hv = (HV *) SvRV (converter_sv);
if (converter->document && converter->document->hv)
STORE("document", newRV_inc ((SV *) converter->document->hv));
pass_converter_text_options (converter, converter_sv);
/* always set "converter_info" for calls to get_info in Perl.
Reuse, such that there are no stale references to trees
left.
*/
converter_info_sv = hv_fetch (converter_hv, "converter_info",
strlen ("converter_info"), 0);
if (converter_info_sv)
{
converter_info_hv = (HV *) SvRV (*converter_info_sv);
hv_clear (converter_info_hv);
if (converter_info_hv != converter->pl_info_hv)
fprintf (stderr, "REMARK: Perl and C stored converter_info differ\n");
}
else
{
converter_info_hv = newHV ();
STORE("converter_info", newRV_noinc ((SV *)converter_info_hv));
/* store in C to be sure that the caching is in the same Perl object
even if the Perl data changes */
converter->pl_info_hv = converter_info_hv;
SvREFCNT_inc (converter_info_hv);
}
/* always (re)set. For user-defined translations */
translation_cache_hv = newHV ();
STORE("translation_cache", newRV_noinc ((SV *)translation_cache_hv));
/* Conversion to LaTeX is in Perl */
if (converter->conf->CONVERT_TO_LATEX_IN_MATH.o.integer > 0)
converter->external_references_number++;
if (converter->conf->CONVERT_TO_LATEX_IN_MATH.o.integer > 0)
{
HV *options_latex_math_hv
= latex_build_options_for_convert_to_latex_math (converter);
hv_store (converter_hv, "options_latex_math",
strlen ("options_latex_math"),
newRV_noinc ((SV *)options_latex_math_hv), 0);
}
if (converter->external_references_number > 0)
{
html_pass_converter_initialization_state (converter, converter_hv);
}
}
void
html_pass_converter_setup_state (const CONVERTER *converter,
SV *converter_sv)
{
HV *converter_hv;
dTHX;
converter_hv = (HV *) SvRV (converter_sv);
if (converter->use_unicode_text)
STORE("use_unicode_text", newSViv (1));
#undef STORE
}
/* Currently unused */
SV *
build_html_files_source_info (const FILE_SOURCE_INFO_LIST *files_source_info)
{
size_t i;
HV *hv;
dTHX;
hv = newHV ();
if (files_source_info)
{
#define STORE(key, sv) hv_store (file_source_info_hv, key, strlen (key), sv, 0)
for (i = 0; i < files_source_info->number; i++)
{
const FILE_SOURCE_INFO * file_source_info = &files_source_info->list[i];
HV *file_source_info_hv;
SV *file_source_info_sv;
const char *filename = file_source_info->filename;
SV *filename_sv = newSVpv_utf8 (filename, 0);
file_source_info_hv = newHV ();
file_source_info_sv = newRV_noinc ((SV *) file_source_info_hv);
hv_store_ent (hv, filename_sv, file_source_info_sv, 0);
STORE("file_info_type", newSVpv_utf8 (file_source_info->type, 0));
if (file_source_info->name)
STORE("file_info_name", newSVpv_utf8 (file_source_info->name, 0));
/* not actually used in downstream code, but present also in perl */
if (file_source_info->path)
STORE("file_info_path", newSVpv_utf8 (file_source_info->path, 0));
else
STORE("file_info_path", newSV(0));
if (file_source_info->element)
{
SV *element_sv
= newSVsv ((SV *) file_source_info->element->sv);
STORE("file_info_element", element_sv);
}
}
#undef STORE
}
return newRV_noinc ((SV *) hv);
}
/* set document_units in converter to a handle that holds a descriptor
to be able to retrieve the output unit list C data, without any units
list data built */
void
set_document_units_handle (CONVERTER *converter, SV *converter_sv)
{
dTHX;
HV *converter_hv = (HV *) SvRV (converter_sv);
SV *units_array_sv = setup_output_units_handler (converter->document,
converter->output_units_descriptors[OUDT_units]);
hv_store (converter_hv, "document_units", strlen ("document_units"),
units_array_sv, 0);
}
HV *
build_html_elements_in_file_count (
FILE_NAME_PATH_COUNTER_LIST *output_unit_files)
{
size_t i;
HV *hv;
dTHX;
hv = newHV ();
if (output_unit_files)
{
for (i = 0; i < output_unit_files->number; i++)
{
FILE_NAME_PATH_COUNTER *output_unit_file
= &output_unit_files->list[i];
char *filename = output_unit_file->filename;
SV *filename_sv = newSVpv_utf8 (filename, 0);
hv_store_ent (hv, filename_sv,
newSViv (output_unit_file->elements_in_file_count), 0);
}
}
return hv;
}
SV *
build_replaced_substrings (NAMED_STRING_ELEMENT_LIST *replaced_substrings)
{
HV *hv;
size_t i;
dTHX;
if (!replaced_substrings)
return newSV (0);
hv = newHV ();
for (i = 0; i < replaced_substrings->number; i++)
{
NAMED_STRING_ELEMENT *named_string_elt = &replaced_substrings->list[i];
SV *name_sv = newSVpv_utf8 (named_string_elt->name, 0);
SV *value_sv = 0;
if (named_string_elt->element)
value_sv = newSVsv ((SV *) named_string_elt->element->sv);
else if (named_string_elt->string)
value_sv = newSVpv_utf8 (named_string_elt->string, 0);
if (value_sv)
hv_store_ent (hv, name_sv, value_sv, 0);
}
return newRV_noinc ((SV *) hv);
}
void
build_pending_footnotes (AV *av, HTML_PENDING_FOOTNOTE_STACK *stack)
{
dTHX;
if (stack->top > 0)
{
size_t i;
for (i = 0; i < stack->top; i++)
{
HTML_PENDING_FOOTNOTE *pending_footnote = stack->stack[i];
AV *pending_footnote_av = newAV ();
SV *sv = newRV_noinc ((SV *) pending_footnote_av);
av_push (av, sv);
av_push (pending_footnote_av,
newSVsv ((SV *) pending_footnote->command->sv));
av_push (pending_footnote_av,
newSVpv_utf8 (pending_footnote->footid, 0));
av_push (pending_footnote_av,
newSVpv_utf8 (pending_footnote->docid, 0));
av_push (pending_footnote_av,
newSViv (pending_footnote->number_in_doc));
av_push (pending_footnote_av,
newSVpv_utf8 (pending_footnote->footnote_location_filename, 0));
if (pending_footnote->multi_expanded_region)
av_push (pending_footnote_av,
newSVpv_utf8 (pending_footnote->multi_expanded_region, 0));
else
av_push (pending_footnote_av, newSV (0));
}
}
}
void
build_simpletitle (const CONVERTER *converter, HV *converter_info_hv)
{
dTHX;
hv_store (converter_info_hv, "simpletitle_tree",
strlen ("simpletitle_tree"),
newSVsv ((SV *) converter->simpletitle_tree->sv), 0);
hv_store (converter_info_hv, "simpletitle_command_name",
strlen ("simpletitle_command_name"),
newSVpv (builtin_command_name (converter->simpletitle_cmd), 0), 0);
}
void
pass_jslicenses (const JSLICENSE_CATEGORY_LIST *jslicenses,
HV *converter_info_hv)
{
HV *jslicenses_hv;
size_t i;
dTHX;
jslicenses_hv = newHV ();
for (i = 0; i < jslicenses->number; i++)
{
size_t j;
JSLICENSE_FILE_INFO_LIST *jslicences_files_info = &jslicenses->list[i];
SV *category_sv = newSVpv_utf8 (jslicences_files_info->category, 0);
HV *jslicences_files_info_hv = newHV ();
hv_store_ent (jslicenses_hv, category_sv,
newRV_noinc ((SV *)jslicences_files_info_hv), 0);
for (j = 0; j < jslicences_files_info->number; j++)
{
JSLICENSE_FILE_INFO *jslicense_file_info
= &jslicences_files_info->list[j];
SV *filename_sv = newSVpv_utf8 (jslicense_file_info->filename, 0);
AV *jslicence_file_info_av = newAV ();
hv_store_ent (jslicences_files_info_hv, filename_sv,
newRV_noinc ((SV *)jslicence_file_info_av), 0);
av_push (jslicence_file_info_av,
newSVpv_utf8 (jslicense_file_info->license, 0));
av_push (jslicence_file_info_av,
newSVpv_utf8 (jslicense_file_info->url, 0));
av_push (jslicence_file_info_av,
newSVpv_utf8 (jslicense_file_info->source, 0));
}
}
hv_store (converter_info_hv, "jslicenses", strlen ("jslicenses"),
newRV_noinc ((SV *) jslicenses_hv), 0);
}
SV *
pass_sv_converter_info (const CONVERTER *converter,
const char *converter_info, SV *converter_sv)
{
HV *converter_hv;
HV *converter_info_hv;
SV **info_sv;
SV *new_sv = 0;
dTHX;
converter_hv = (HV *) SvRV (converter_sv);
/* do not find the cache in Perl data but in C to be more robust to
changes in Perl objects */
converter_info_hv = converter->pl_info_hv;
/* The information is cached in the same place as in Perl code.
Either Perl code or XS/C code is used, so this is for consistency
not really for interoperability */
info_sv = hv_fetch (converter_info_hv, converter_info,
strlen (converter_info), 0);
if (info_sv && SvOK (*info_sv))
{
return newSVsv (*info_sv);
}
/* the linear search is not very efficient, but done only once for
each defined information */
if (!strcmp (converter_info, "non_breaking_space"))
new_sv
= newSVpv_utf8 (converter->special_character[SC_non_breaking_space].string, 0);
else if (!strcmp (converter_info, "paragraph_symbol"))
new_sv
= newSVpv_utf8 (converter->special_character[SC_paragraph_symbol].string, 0);
else if (!strcmp (converter_info, "line_break_element"))
new_sv
= newSVpv_utf8 (converter->line_break_element.string, 0);
else if (!strcmp (converter_info, "document"))
{
SV **document_sv = hv_fetch (converter_hv, "document",
strlen ("document"), 0);
if (document_sv && SvOK (*document_sv))
{
new_sv = newSVsv (*document_sv);
}
}
else if (!strcmp (converter_info, "document_name"))
{
if (converter->document_name)
new_sv = newSVpv_utf8 (converter->document_name, 0);
}
else if (!strcmp (converter_info, "destination_directory"))
{
if (converter->destination_directory)
new_sv = newSVpv_utf8 (converter->destination_directory, 0);
}
else if (!strcmp (converter_info, "expanded_formats"))
{
/* add expanded_formats to converter_info */
SV **expanded_formats_sv
= hv_fetch (converter_hv, "expanded_formats",
strlen ("expanded_formats"), 0);
if (expanded_formats_sv && SvOK (*expanded_formats_sv))
{
new_sv = newSVsv (*expanded_formats_sv);
}
}
else if (!strcmp (converter_info, "jslicenses"))
{
pass_jslicenses (&converter->jslicenses, converter_info_hv);
info_sv = hv_fetch (converter_info_hv, converter_info,
strlen (converter_info), 0);
/* probably always true */
if (info_sv && SvOK (*info_sv))
{
return newSVsv (*info_sv);
}
}
else if (!strcmp (converter_info, "copying_comment"))
{
if (converter->copying_comment)
new_sv = newSVpv_utf8 (converter->copying_comment, 0);
}
else if (!strcmp (converter_info, "documentdescription_string"))
{
if (converter->documentdescription_string)
new_sv = newSVpv_utf8 (converter->documentdescription_string, 0);
}
else if (!strcmp (converter_info, "title_titlepage"))
{
if (converter->title_titlepage)
new_sv = newSVpv_utf8 (converter->title_titlepage, 0);
}
else if (!strcmp (converter_info, "title_string"))
{
new_sv = newSVpv_utf8 (converter->title_string, 0);
}
else if (!strcmp (converter_info, "title_tree"))
{
if (converter->added_title_tree)
build_texinfo_tree (converter->title_tree, 1);
new_sv = newSVsv ((SV *) converter->title_tree->sv);
}
else if (!strcmp (converter_info, "simpletitle_tree")
|| !strcmp (converter_info, "simpletitle_command_name"))
{
if (converter->simpletitle_tree)
{
build_simpletitle (converter, converter_info_hv);
info_sv = hv_fetch (converter_info_hv, converter_info,
strlen (converter_info), 0);
/* probably always true */
if (info_sv && SvOK (*info_sv))
{
return newSVsv (*info_sv);
}
}
}
else
{
char *bug_msg;
xasprintf (&bug_msg, "%s not an available converter info",
converter_info);
bug (bug_msg);
non_perl_free (bug_msg);
}
if (new_sv)
{
hv_store (converter_info_hv, converter_info,
strlen (converter_info), new_sv, 0);
SV *result = newSVsv (new_sv);
return result;
}
return newSV (0);
}