blob: 859aaf6034ac90b5b2fe4beccdae2ff50228e840 [file]
/* Copyright 2010-2025 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/>. */
#include <config.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include "html_conversion_data.h"
#include "text.h"
#include "element_types.h"
#include "tree_types.h"
#include "option_types.h"
#include "options_data.h"
#include "document_types.h"
#include "converter_types.h"
#include "option_types.h"
#include "types_data.h"
#include "html_converter_types.h"
/* fatal isascii_alpha */
#include "base_utils.h"
#include "tree.h"
#include "hashmap.h"
#include "builtin_commands.h"
#include "command_stack.h"
#include "errors.h"
/* xasprintf get_label_element output_conversions ENCODING_CONVERSION
encode_with_iconv output_unit_type_names get_cmd_global_uniq_command
*/
#include "utils.h"
#include "manipulate_tree.h"
#include "customization_options.h"
#include "extra.h"
#include "debug.h"
/* retrieve_output_units output_unit_texi */
#include "output_unit.h"
#include "convert_to_texinfo.h"
/* translate_string NAMED_STRING_ELEMENT_LIST */
#include "translations.h"
/* convert_to_text */
#include "convert_to_text.h"
/* normalize_transliterate_texinfo_contents */
#include "node_name_normalization.h"
/* converter_encoded_output_file_name
output_files_open_out
output_files_register_closed */
#include "convert_utils.h"
/* call_latex_convert_to_latex_math */
#include "call_perl_function.h"
#include "call_html_perl_function.h"
/* for unregister_document_merge_with_document */
#include "document.h"
/* txi_paths_info create_destination_directory
set_global_document_commands clear_tree_added_elements
register_normalize_case_filename converter_translated_command_tree */
#include "converter.h"
#include "html_conversion_state.h"
#include "format_html.h"
#include "convert_html.h"
/* html_nr_string_directions */
#include "html_prepare_converter.h"
#include "html_conversion_api.h"
const char *html_conversion_context_type_names[] = {
#define cctx_type(name) #name,
HCC_CONTEXT_TYPES_LIST
#undef cctx_type
};
const char *html_stage_handler_stage_type_names[] = {
#define html_hsht_type(name) #name,
HTML_STAGE_HANDLER_STAGE_TYPE
#undef html_hsht_type
};
/* string translation and tree conversion */
char *
format_translate_message (CONVERTER *self,
const char *message, const char *lang,
const char *message_context)
{
const FORMATTING_REFERENCE *formatting_reference
= &self->current_formatting_references[FR_format_translate_message];
return call_formatting_function_format_translate_message (self,
formatting_reference,
message, lang, message_context);
}
/* return string to be freed by the caller */
static char *
html_custom_translate_string (CONVERTER *self, const char *string,
const char *lang,
const char *translation_context)
{
const FORMATTING_REFERENCE *formatting_reference
= &self->formatting_references[FR_format_translate_message];
/* there is no place where FRS_status_ignore could be set, but
it does not hurt to consider it possible */
if (formatting_reference->status
&& formatting_reference->status != FRS_status_ignored
&& formatting_reference->status != FRS_status_none
/* this function may not be defined in Perl, thus this condition */
&& formatting_reference->sv_reference)
{
char *translated_string
= format_translate_message (self, string, lang, translation_context);
if (translated_string)
return translated_string;
}
return 0;
}
char *
html_translate_string (CONVERTER *self, const char *string,
const char *lang,
const char *translation_context)
{
char *result = html_custom_translate_string (self, string, lang,
translation_context);
if (!result)
return translate_string (string, lang, translation_context);
else
return result;
}
TRANSLATION_TREE *
html_cache_translate_string (CONVERTER *self, const char *string,
LANG_TRANSLATION *lang_translation,
const char *translation_context)
{
const char *lang;
char *translated_string;
if (lang_translation && lang_translation->lang)
lang = lang_translation->lang;
translated_string = html_custom_translate_string (self, string, lang,
translation_context);
if (translated_string)
{
const char *translation_context_str;
LANG_TRANSLATION_TREE_LIST *translations;
char *translated_context_string;
TRANSLATION_TREE *result;
LANG_TRANSLATION *lang_translation;
uintptr_t string_nr;
int found;
if (!lang)
lang = "";
lang_translation = get_lang_translation (&self->translation_cache, lang,
TXI_CONVERT_STRINGS_NR);
translations = lang_translation->translations;
if (translation_context)
translation_context_str = translation_context;
else
translation_context_str = "";
xasprintf (&translated_context_string, "%s-%s",
string, translation_context_str);
string_nr = (uintptr_t) c_hashmap_value (translations->hash,
translated_context_string, &found);
if (found)
{
free (translated_context_string);
result = translations->list[string_nr -1];
if (!strcmp (result->translation, translated_string))
{
free (translated_string);
return result;
}
if (result->tree)
{
destroy_element_and_children (result->tree);
result->tree = 0;
}
free (result->translation);
result->translation = translated_string;
return result;
}
result = add_translation_tree (translations, translated_context_string);
result->translation = translated_string;
free (translated_context_string);
return result;
}
return cache_translate_string (string, lang_translation,
translation_context);
}
/* same as gdt_tree with html_translate_string called instead of translate_string */
ELEMENT *
html_gdt_tree (const char *string, CONVERTER *self,
LANG_TRANSLATION *lang_translation,
NAMED_STRING_ELEMENT_LIST *replaced_substrings,
const char *translation_context)
{
int debug_level = 0;
const OPTIONS *options = self->conf;
TRANSLATION_TREE *translated_string_tree;
ELEMENT *result_tree;
if (options && options->DEBUG.o.integer >= 0)
debug_level = options->DEBUG.o.integer;
translated_string_tree
= html_cache_translate_string (self, string, lang_translation,
translation_context);
if (!translated_string_tree->tree)
{
DOCUMENT *translation_document;
DOCUMENT *document = self->document;
const char *translated_string = translated_string_tree->translation;
if (!translated_string)
translated_string = string;
translation_document
= replace_convert_substrings (translated_string, replaced_substrings,
debug_level);
translated_string_tree->tree
= unregister_document_merge_with_document (translation_document,
document);
}
result_tree = copy_tree (translated_string_tree->tree, 0);
if (replaced_substrings)
{
substitute (result_tree, replaced_substrings);
}
return result_tree;
}
/* same as cdt_tree with html_gdt_tree called instead of gdt_tree */
ELEMENT *
html_cdt_tree (const char *string, CONVERTER *self,
NAMED_STRING_ELEMENT_LIST *replaced_substrings,
const char *translation_context)
{
return html_gdt_tree (string, self, self->current_lang_translations,
replaced_substrings, translation_context);
}
char *
html_cdt_string (const char *string, CONVERTER *self,
NAMED_STRING_ELEMENT_LIST *replaced_substrings,
const char *translation_context)
{
const TRANSLATION_TREE *translated_string_tree;
const char *translated_string;
char *result;
translated_string_tree
= html_cache_translate_string (self, string,
self->current_lang_translations,
translation_context);
translated_string = translated_string_tree->translation;
if (!translated_string)
translated_string = string;
result = replace_substrings (translated_string, replaced_substrings);
return result;
}
ELEMENT *
html_pcdt_tree (const char *translation_context, const char *string,
CONVERTER *self,
NAMED_STRING_ELEMENT_LIST *replaced_substrings)
{
return html_cdt_tree (string, self, replaced_substrings,
translation_context);
}
void
add_tree_to_build (CONVERTER *self, ELEMENT *e)
{
if (self->external_references_number > 0)
add_to_element_list (&self->tree_to_build, e);
}
void
remove_tree_to_build (CONVERTER *self, ELEMENT *e)
{
if (self->external_references_number > 0)
replace_remove_list_element (&self->tree_to_build, e, 0);
}
void
html_translate_convert_tree_append (const char *string,
CONVERTER *self,
NAMED_STRING_ELEMENT_LIST *replaced_substrings,
const char *translation_context,
TEXT *result, const char *explanation)
{
ELEMENT *translation_tree = html_cdt_tree (string, self,
replaced_substrings, translation_context);
add_tree_to_build (self, translation_tree);
html_convert_tree_append (self, translation_tree, result, explanation);
remove_tree_to_build (self, translation_tree);
destroy_element_and_children (translation_tree);
}
/* EXPLANATION is used for debugging */
/* returned string to be freed by the caller */
char *
html_convert_tree_explanation (CONVERTER *self, const ELEMENT *tree,
const char *explanation)
{
TEXT result;
text_init (&result);
html_convert_tree_append (self, tree, &result, explanation);
return result.text;
}
/* follows the common API for converters */
/* returned string to be freed by the caller */
char *
html_convert_tree (CONVERTER *self, const ELEMENT *tree)
{
return html_convert_tree_explanation (self, tree, 0);
}
/* Call convert_tree out of the main conversion flow.
*/
char *
html_convert_tree_new_formatting_context (CONVERTER *self, const ELEMENT *tree,
const char *context_string,
const char *multiple_pass,
const char *document_global_context,
enum command_id block_cmd)
{
const char *multiple_pass_str = "";
char *result;
char *explanation;
char *context_string_str;
html_new_document_context (self, context_string,
document_global_context, block_cmd);
xasprintf (&context_string_str, "C(%s)", context_string);
if (multiple_pass)
{
html_set_multiple_conversions (self, multiple_pass);
multiple_pass_str = "|M";
}
if (self->conf->DEBUG.o.integer > 0)
fprintf (stderr, "C|new_fmt_ctx %s%s\n", context_string_str,
multiple_pass_str);
xasprintf (&explanation, "new_fmt_ctx %s", context_string_str);
result = html_convert_tree_explanation (self, tree, explanation);
free (explanation);
if (multiple_pass)
html_unset_multiple_conversions (self);
free (context_string_str);
html_pop_document_context (self);
return result;
}
/* NOTE these switches are not done in perl, so the only perl functions
that can be called are perl functions that do not call formatting/conversion
functions or the formatting/conversion functions for HTML will be used. */
char *
html_convert_css_string (CONVERTER *self, const ELEMENT *element,
const char *context_str)
{
char *css_string_context_str;
char *context_string_str;
char *explanation;
char *result;
void (* saved_current_format_protect_text) (const char *text, TEXT *result);
FORMATTING_REFERENCE *saved_formatting_references
= self->current_formatting_references;
COMMAND_CONVERSION_FUNCTION *saved_commands_conversion_function
= self->current_commands_conversion_function;
TYPE_CONVERSION_FUNCTION *saved_types_conversion_function
= self->current_types_conversion_function;
saved_current_format_protect_text = self->current_format_protect_text;
self->current_formatting_references
= &self->css_string_formatting_references[0];
self->current_commands_conversion_function
= &self->css_string_command_conversion_function[0];
self->current_types_conversion_function
= &self->css_string_type_conversion_function[0];
self->current_format_protect_text
= &html_default_css_string_format_protect_text;
if (context_str)
xasprintf (&css_string_context_str, "CSS string %s", context_str);
else
css_string_context_str = "CSS string ";
xasprintf (&context_string_str, "C(%s)", css_string_context_str);
xasprintf (&explanation, "new_fmt_ctx %s", context_string_str);
html_new_document_context (self, css_string_context_str, 0, 0);
html_set_string_context (self);
result = html_convert_tree_explanation (self, element, explanation);
html_pop_document_context (self);
free (explanation);
free (context_string_str);
if (context_str)
free (css_string_context_str);
self->current_formatting_references = saved_formatting_references;
self->current_commands_conversion_function
= saved_commands_conversion_function;
self->current_types_conversion_function = saved_types_conversion_function;
self->current_format_protect_text = saved_current_format_protect_text;
return result;
}
/* return string to be freed by the caller */
char *
html_convert_string_tree_new_formatting_context (CONVERTER *self,
ELEMENT *tree, const char *context_string,
const char *multiple_pass)
{
ELEMENT *tree_root_string = new_element (ET__string);
char *result;
add_to_contents_as_array (tree_root_string, tree);
add_tree_to_build (self, tree_root_string);
result = html_convert_tree_new_formatting_context (self, tree_root_string,
context_string, multiple_pass, 0, 0);
remove_tree_to_build (self, tree_root_string);
destroy_element (tree_root_string);
return result;
}
/* reset translated data and translate no args commands */
void
html_clear_direction_string_type (const CONVERTER *self,
char ***type_directions_strings)
{
int i;
int nr_string_directions = html_nr_string_directions (self);
int nr_dir_str_contexts = TDS_context_string + 1;
for (i = 0; i < nr_string_directions; i++)
{
char **direction_strings = type_directions_strings[i];
int j;
/* NULL only happens for customized_directions_strings */
if (direction_strings != NULL)
{
for (j = 0; j < nr_dir_str_contexts; j++)
{
free (direction_strings[j]);
direction_strings[j] = 0;
}
}
}
}
void
html_reset_translated_special_unit_info_tree (CONVERTER *self)
{
STRING_LIST *special_unit_varieties = &self->special_unit_varieties;
int j;
for (j = 0; translated_special_unit_info[j].tree_type != SUIT_type_none; j++)
{
size_t i;
enum special_unit_info_tree tree_type
= translated_special_unit_info[j].tree_type;
for (i = 0; i < special_unit_varieties->number; i++)
{
if (self->special_unit_info_tree[tree_type][i])
{
remove_tree_to_build (self,
self->special_unit_info_tree[tree_type][i]);
destroy_element_and_children (
self->special_unit_info_tree[tree_type][i]);
}
self->special_unit_info_tree[tree_type][i] = 0;
}
}
}
static void
reset_unset_no_arg_commands_formatting_context (CONVERTER *self,
enum command_id cmd, enum conversion_context reset_context,
enum conversion_context ref_context, int translate)
{
HTML_NO_ARG_COMMAND_CONVERSION *no_arg_command_context;
HTML_NO_ARG_COMMAND_CONVERSION *conversion_contexts
= self->html_no_arg_command_conversion[cmd];
no_arg_command_context = &conversion_contexts[reset_context];
if (ref_context >= 0)
{
if (no_arg_command_context->unset)
{
HTML_NO_ARG_COMMAND_CONVERSION *no_arg_ref
= &conversion_contexts[ref_context];
if (no_arg_ref->text)
{
free (no_arg_command_context->text);
no_arg_command_context->text = strdup (no_arg_ref->text);
}
if (no_arg_ref->translated_tree)
no_arg_command_context->translated_tree
= no_arg_ref->translated_tree;
if (no_arg_ref->translated_converted)
{
free (no_arg_command_context->translated_converted);
no_arg_command_context->translated_converted
= strdup (no_arg_ref->translated_converted);
}
if (no_arg_ref->translated_to_convert)
{
free (no_arg_command_context->translated_to_convert);
no_arg_command_context->translated_to_convert
= strdup (no_arg_ref->translated_to_convert);
}
}
}
if (translate
&& no_arg_command_context->translated_tree
&& !no_arg_command_context->translated_converted)
{
char *translation_result = 0;
char *explanation;
char *context;
ELEMENT *tree_built = 0;
ELEMENT *translated_tree = no_arg_command_context->translated_tree;
if (self->external_references_number > 0 && !translated_tree->hv)
{
add_to_element_list (&self->tree_to_build, translated_tree);
tree_built = translated_tree;
}
xasprintf (&explanation, "Translated NO ARG @%s ctx %s",
builtin_command_data[cmd].cmdname,
html_conversion_context_type_names[reset_context]);
xasprintf (&context, "Tr %s ctx %s",
builtin_command_data[cmd].cmdname,
html_conversion_context_type_names[reset_context]);
if (reset_context == HCC_type_normal)
{
translation_result
= html_convert_tree_explanation (self, translated_tree,
explanation);
}
else if (reset_context == HCC_type_preformatted)
{
enum command_id preformatted_cmd = CM_example;
/* there does not seems to be anything simpler... */
html_new_document_context (self, context, 0, 0);
html_open_command_update_context (self, preformatted_cmd);
translation_result
= html_convert_tree_explanation (self, translated_tree,
explanation);
html_convert_command_update_context (self, preformatted_cmd);
html_pop_document_context (self);
}
else if (reset_context == HCC_type_string)
{
html_new_document_context (self, context, 0, 0);
html_set_string_context (self);
translation_result
= html_convert_tree_explanation (self, translated_tree,
explanation);
html_pop_document_context (self);
}
else if (reset_context == HCC_type_css_string)
{
translation_result = html_convert_css_string (self, translated_tree,
context);
}
free (explanation);
free (context);
if (no_arg_command_context->text)
free (no_arg_command_context->text);
no_arg_command_context->text = translation_result;
if (tree_built)
replace_remove_list_element (&self->tree_to_build, tree_built, 0);
}
}
void
html_complete_no_arg_commands_formatting (CONVERTER *self, enum command_id cmd,
int translate)
{
reset_unset_no_arg_commands_formatting_context (self, cmd, HCC_type_normal,
-1, translate);
reset_unset_no_arg_commands_formatting_context (self, cmd,
HCC_type_preformatted,
HCC_type_normal, translate);
reset_unset_no_arg_commands_formatting_context (self, cmd, HCC_type_string,
HCC_type_preformatted, translate);
reset_unset_no_arg_commands_formatting_context (self, cmd, HCC_type_css_string,
HCC_type_string, translate);
}
void
html_translate_names (CONVERTER *self)
{
size_t j;
const STRING_LIST *special_unit_varieties = &self->special_unit_varieties;
text_set_language (self->convert_text_options,
self->conf->documentlanguage.o.string);
self->current_lang_translations =
switch_lang_translations (&translation_cache,
self->conf->documentlanguage.o.string,
self->current_lang_translations,
TXI_CONVERT_STRINGS_NR);
if (self->conf->DEBUG.o.integer > 0)
{
fprintf (stderr, "\nC|TRANSLATE_NAMES encoding_name: %s"
" documentlanguage: %s\n",
self->conf->OUTPUT_ENCODING_NAME.o.string,
self->conf->documentlanguage.o.string);
}
/* reset strings such that they are translated when needed. */
for (j = 0; j < TDS_TRANSLATED_MAX_NR; j++)
{
html_clear_direction_string_type (self, self->directions_strings[j]);
}
/* reset trees such that they are created based on Texinfo code string
translation on-demand */
html_reset_translated_special_unit_info_tree (self);
/* delete the tree and formatted results for special elements
such that they are redone with the new tree when needed. */
for (j = 0; j < special_unit_varieties->number; j++)
{
const char *special_unit_variety = special_unit_varieties->list[j];
int special_unit_direction_index
= html_special_unit_variety_direction_index (self, special_unit_variety);
if (special_unit_direction_index >= 0)
{
const OUTPUT_UNIT *special_unit
= self->global_units_directions[special_unit_direction_index];
if (special_unit)
{
const ELEMENT *command = special_unit->uc.special_unit_command;
if (command)
{
HTML_TARGET *target_info
= html_get_target (self, command);
if (target_info)
{
/* the tree is a reference to special_unit_info_tree, so it should
not be freed, but it needs to be reset to trigger the creation of the
special_unit_info_tree tree when needed */
clear_tree_added_elements (self, &target_info->tree);
free (target_info->command_text[HTT_string]);
target_info->command_text[HTT_string] = 0;
free (target_info->command_text[HTT_text]);
target_info->command_text[HTT_text] = 0;
free (target_info->command_description[HTT_string]);
target_info->command_description[HTT_string] = 0;
free (target_info->command_description[HTT_text]);
target_info->command_description[HTT_text] = 0;
}
}
}
}
}
/* self->no_arg_formatted_cmd_translated is used here to hold the translated
commands, and the information is kept if it is also used to pass
translated commands results to Perl */
{
COMMAND_ID_LIST *translated_cmds
= &self->no_arg_formatted_cmd_translated;
/* in general this is done in build_html_translated_names. Still need
to do it here if build_html_translated_names is never called */
if (translated_cmds->number)
{
memset (translated_cmds->list, 0, translated_cmds->number
* sizeof (enum command_id));
}
translated_cmds->number = 0;
for (j = 0; j < no_arg_formatted_cmd.number; j++)
{
enum command_id cmd = no_arg_formatted_cmd.list[j];
enum conversion_context cctx;
int add_cmd = 0;
for (cctx = 0; cctx < NO_ARG_COMMAND_CONTEXT_NR; cctx++)
{
/* beware that Perl can be called through html_cdt_*
here, possibly in the middle of being called from Perl.
TODO there could be something up with the order of processing
of the translated commands, in link with going through Perl
or not, if translation of @-commands depends on @-commands
also translated
*/
HTML_NO_ARG_COMMAND_CONVERSION *format_spec
= &self->html_no_arg_command_conversion[cmd][cctx];
if (format_spec->translated_converted
&& !format_spec->unset)
{
add_cmd = 1;
free (format_spec->text);
format_spec->text
= html_cdt_string (format_spec->translated_converted, self,
0, 0);
}
else if (cctx == HCC_type_normal)
{
ELEMENT *translated_tree = 0;
if (format_spec->translated_to_convert)
{/* it is very unlikely to have small strings to add,
but in case there are it should be ok */
translated_tree =
html_cdt_tree (format_spec->translated_to_convert,
self, 0, 0);
}
else
translated_tree = converter_translated_command_tree (self, cmd,
&html_cdt_tree);
if (translated_tree)
{
add_cmd = 1;
if (format_spec->translated_tree)
destroy_element_and_children (
format_spec->translated_tree);
format_spec->translated_tree = translated_tree;
}
}
}
if (add_cmd)
{
translated_cmds->list[translated_cmds->number] = cmd;
translated_cmds->number++;
}
}
for (j = 0; j < translated_cmds->number; j++)
{
enum command_id cmd = translated_cmds->list[j];
html_complete_no_arg_commands_formatting (self, cmd, 1);
}
/* not passed to Perl in that case, unset to avoid spurious error
messages */
if (self->external_references_number <= 0)
{
memset (translated_cmds->list, 0, translated_cmds->number
* sizeof (enum command_id));
translated_cmds->number = 0;
}
}
if (self->conf->DEBUG.o.integer > 0)
fprintf (stderr, "END TRANSLATE_NAMES\n\n");
self->modified_state |= HMSF_translations;
}
/* last preparations of conversion. At this point conversion of
Texinfo tree is possible */
static const enum command_id conf_for_documentlanguage[]
= {CM_documentlanguage, 0};
int
html_run_stage_handlers (CONVERTER *self,
enum html_stage_handler_stage_type stage)
{
size_t i;
HTML_STAGE_HANDLER_INFO_LIST *stage_handlers
= &self->html_stage_handlers[stage];
if (stage_handlers->number > 0)
{
const char *stage_name = html_stage_handler_stage_type_names[stage];
for (i = 0; i < stage_handlers->number; i++)
{
int call_status;
HTML_STAGE_HANDLER_INFO *stage_handler
= &stage_handlers->list[i];
if (self->conf->DEBUG.o.integer > 0)
fprintf (stderr, "RUN handler %zu: stage %s, priority %s\n",
i +1, stage_name, stage_handler->priority);
if (stage_handler->sv)
{
int error_status = 0;
call_status = call_stage_handler (self, stage_handler->sv,
stage_name, &error_status);
if (error_status == 1)
{
message_list_document_error (&self->error_messages,
self->conf, 0,
"handler %d of stage %s priority %s: non-numeric status",
(int) i+1, stage_name, stage_handler->priority);
call_status = self->conf->HANDLER_FATAL_ERROR_LEVEL.o.integer
+1;
}
if (call_status != 0)
{
if (call_status < 0)
{
message_list_document_error (&self->error_messages,
self->conf, 0,
"handler %d of stage %s priority %s failed",
(int) i+1, stage_name, stage_handler->priority);
}
else
{
/* the handler is supposed to have output an error message
already if $status > 0 */
if (self->conf->DEBUG.o.integer > 0
|| self->conf->VERBOSE.o.integer > 0)
{
fprintf (stderr,
"FAIL handler %zu: stage %s, priority %s\n",
i +1, stage_name, stage_handler->priority);
}
}
return call_status;
}
}
}
}
return 0;
}
static const enum command_id simpletitle_cmds[] =
{CM_settitle, CM_shorttitlepage, 0};
void
html_prepare_simpletitle (CONVERTER *self)
{
int i;
for (i = 0; simpletitle_cmds[i]; i++)
{
enum command_id cmd = simpletitle_cmds[i];
const ELEMENT *command
= get_cmd_global_uniq_command (&self->document->global_commands, cmd);
if (command && command->e.c->contents.list[0]->e.c->contents.number > 0)
{
self->simpletitle_tree = command->e.c->contents.list[0];
self->simpletitle_cmd = cmd;
break;
}
}
}
void
html_free_direction_icons_array (CONVERTER *self, char ***direction_icons)
{
if (*direction_icons)
{
size_t i;
for (i = 0; self->main_units_direction_names[i]; i++)
free ((*direction_icons)[i]);
free (*direction_icons);
*direction_icons = 0;
}
}
static int
compare_direction_icon (const void *a, const void *b)
{
const DIRECTION_ICON *dicon_a = (const DIRECTION_ICON *) a;
const DIRECTION_ICON *dicon_b = (const DIRECTION_ICON *) b;
return strcmp (dicon_a->direction_name, dicon_b->direction_name);
}
static void
prepare_direction_icons_list (CONVERTER *self,
DIRECTION_ICON_LIST *direction_icons,
char ***direction_icon_names,
size_t icons_nr)
{
/* if the converter has been properly reset after each call to output or
convert, freeing should not be useful */
html_free_direction_icons_array (self, direction_icon_names);
/* there are always directions, so should always be true */
if (icons_nr > 0)
{
DIRECTION_ICON searched;
size_t i;
*direction_icon_names = (char **) malloc (icons_nr * sizeof (char *));
qsort (direction_icons->icons_list, direction_icons->number,
sizeof (DIRECTION_ICON), compare_direction_icon);
for (i = 0; self->main_units_direction_names[i]; i++)
{
const DIRECTION_ICON *icon;
searched.direction_name = (char *)self->main_units_direction_names[i];
icon = (const DIRECTION_ICON *) bsearch (&searched,
direction_icons->icons_list, direction_icons->number,
sizeof (DIRECTION_ICON), compare_direction_icon);
if (icon && icon->name && strlen (icon->name))
(*direction_icon_names)[i] = strdup (icon->name);
else
(*direction_icon_names)[i] = 0;
}
}
}
void
html_prepare_direction_icons (CONVERTER *self)
{
/* consistent with main_units_direction_names size */
size_t icons_nr = self->special_unit_varieties.number
+ NON_SPECIAL_DIRECTIONS_NR;
if (self->conf->ICONS.o.integer > 0)
{
if (self->conf->ACTIVE_ICONS.o.icons->number > 0)
prepare_direction_icons_list (self, self->conf->ACTIVE_ICONS.o.icons,
&self->html_active_icons, icons_nr);
if (self->conf->PASSIVE_ICONS.o.icons->number > 0)
prepare_direction_icons_list (self, self->conf->PASSIVE_ICONS.o.icons,
&self->html_passive_icons, icons_nr);
}
}
/* setup a page (+global context) in case there are no files, ie called
with convert or output with an empty string as filename. */
void
html_setup_output_simple_page (CONVERTER *self, const char *output_filename)
{
NAME_NUMBER *page_name_number;
self->page_css.number = 1+1;
self->page_css.space = self->page_css.number;
self->page_css.list = (CSS_LIST *)
malloc (self->page_css.space * sizeof (CSS_LIST));
memset (self->page_css.list, 0,
self->page_css.number * sizeof (CSS_LIST));
self->html_files_information.number = 1+1;
self->html_files_information.list = (FILE_ASSOCIATED_INFO *)
malloc (self->html_files_information.number
* sizeof (FILE_ASSOCIATED_INFO));
memset (self->html_files_information.list, 0,
self->html_files_information.number * sizeof (FILE_ASSOCIATED_INFO));
self->pending_closes.number = 1+1;
self->pending_closes.list = (STRING_STACK *)
malloc (self->pending_closes.number * sizeof (STRING_STACK));
memset (self->pending_closes.list, 0,
self->pending_closes.number * sizeof (STRING_STACK));
self->page_name_number.number = 1;
self->page_name_number.list = (NAME_NUMBER *)
malloc (self->page_name_number.number * sizeof (NAME_NUMBER));
page_name_number = &self->page_name_number.list[0];
page_name_number->number = 1;
page_name_number->name = output_filename;
}
void
html_prepare_title_titlepage (CONVERTER *self, const char *output_file,
const char *output_filename)
{
const OUTPUT_UNIT_LIST *output_units = retrieve_output_units
(self->document, self->output_units_descriptors[OUDT_units]);
if (strlen (output_file))
{
self->current_filename.filename = output_units->list[0]->unit_filename;
self->current_filename.file_number
= self->output_unit_file_indices[0]+1;
}
else
{
/* case of convert() call. Need to setup the page here */
if (self->page_name_number.number <= 0)
html_setup_output_simple_page (self, output_filename);
self->current_filename.filename = output_filename;
self->current_filename.file_number = 1;
}
self->title_titlepage = html_format_title_titlepage (self);
memset (&self->current_filename, 0, sizeof (FILE_NUMBER_NAME));
}
static const enum command_id fulltitle_cmds[] =
{CM_settitle, CM_title, CM_shorttitlepage, 0};
int
html_prepare_converted_output_info (CONVERTER *self, const char *output_file,
const char *output_filename)
{
int i;
ELEMENT *fulltitle_tree = 0;
char *html_title_string = 0;
char *default_document_language = 0;
char *preamble_document_language = 0;
int init_handler_status;
int handler_fatal_error_level
= self->conf->HANDLER_FATAL_ERROR_LEVEL.o.integer;
int structure_handler_status
= html_run_stage_handlers (self, HSHT_type_structure);
if (structure_handler_status < handler_fatal_error_level
&& structure_handler_status > -handler_fatal_error_level)
{}
else
return 0;
if (self->conf->documentlanguage.o.string)
default_document_language = strdup (self->conf->documentlanguage.o.string);
set_global_document_commands (self, CL_preamble, conf_for_documentlanguage);
if (self->conf->documentlanguage.o.string)
preamble_document_language = strdup (self->conf->documentlanguage.o.string);
if (! (!default_document_language && !preamble_document_language)
&& (!default_document_language || !preamble_document_language
|| strcmp (default_document_language, preamble_document_language)))
html_translate_names (self);
html_prepare_direction_icons (self);
/*
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 did.
*/
html_prepare_simpletitle (self);
for (i = 0; fulltitle_cmds[i]; i++)
{
enum command_id cmd = fulltitle_cmds[i];
const ELEMENT *command
= get_cmd_global_uniq_command (&self->document->global_commands, cmd);
if (command && command->e.c->contents.list[0]->e.c->contents.number > 0)
{
fulltitle_tree = command->e.c->contents.list[0];
break;
}
}
if (!fulltitle_tree
&& self->document->global_commands.top)
{
/* arguments_line type element */
const ELEMENT *arguments_line
= self->document->global_commands.top->e.c->contents.list[0];
ELEMENT *line_arg = arguments_line->e.c->contents.list[0];
if (line_arg->e.c->contents.number > 0)
fulltitle_tree = line_arg;
}
if (!fulltitle_tree
&& self->document->global_commands.titlefont.number > 0
&& self->document->global_commands.titlefont.list[0]
->e.c->contents.number > 0
&& self->document->global_commands.titlefont.list[0]
->e.c->contents.list[0]->e.c->contents.number > 0)
{
fulltitle_tree = self->document->global_commands.titlefont.list[0];
}
if (fulltitle_tree)
{
self->title_tree = fulltitle_tree;
html_title_string
= html_convert_string_tree_new_formatting_context (self,
fulltitle_tree, "title_string", 0);
if (html_title_string[strspn (html_title_string, whitespace_chars)]
== '\0')
{
free (html_title_string);
html_title_string = 0;
}
}
if (!html_title_string)
{
ELEMENT *default_title = html_cdt_tree ("Untitled Document",
self, 0, 0);
SOURCE_INFO cmd_source_info;
self->title_tree = default_title;
html_title_string
= html_convert_string_tree_new_formatting_context (self,
default_title, "title_string", 0);
self->added_title_tree = 1;
if (self->document->global_info.input_file_name)
{
/* setup a source info with file only */
memset (&cmd_source_info, 0, sizeof (SOURCE_INFO));
cmd_source_info.file_name
= self->document->global_info.input_file_name;
message_list_line_error_ext (&self->error_messages,
(self->conf && self->conf->DEBUG.o.integer > 0),
MSG_warning, 0, &cmd_source_info,
"must specify a title with a title command or @top");
}
else
{
message_list_document_warn (&self->error_messages, self->conf, 0,
"must specify a title with a title command or @top");
}
}
self->title_string = html_title_string;
/* copying comment */
if (self->document->global_commands.copying)
{
char *copying_comment;
ELEMENT *tmp = new_element (ET_NONE);
tmp->e.c->contents = self->document->global_commands.copying->e.c->contents;
copying_comment = convert_to_text (tmp, self->convert_text_options);
tmp->e.c->contents.list = 0;
destroy_element (tmp);
if (copying_comment && strlen (copying_comment) > 0)
{
self->copying_comment = html_format_comment (self, copying_comment);
}
free (copying_comment);
}
/* documentdescription */
if (self->conf->documentdescription.o.string)
self->documentdescription_string
= strdup (self->conf->documentdescription.o.string);
else if (self->document->global_commands.documentdescription)
{
ELEMENT *tmp = new_element (ET_NONE);
char *documentdescription_string;
size_t documentdescription_string_len;
tmp->e.c->contents
= self->document->global_commands.documentdescription->e.c->contents;
documentdescription_string
= html_convert_string_tree_new_formatting_context (self,
tmp, "documentdescription", 0);
tmp->e.c->contents.list = 0;
destroy_element (tmp);
documentdescription_string_len = strlen (documentdescription_string);
if (documentdescription_string_len > 0
&& documentdescription_string[documentdescription_string_len -1]
== '\n')
documentdescription_string[documentdescription_string_len -1] = '\0';
self->documentdescription_string = documentdescription_string;
}
init_handler_status = html_run_stage_handlers (self, HSHT_type_init);
if (init_handler_status < handler_fatal_error_level
&& init_handler_status > -handler_fatal_error_level)
{}
else
{
free (default_document_language);
free (preamble_document_language);
return 0;
}
html_prepare_title_titlepage (self, output_file, output_filename);
set_global_document_commands (self, CL_before, conf_for_documentlanguage);
if (! (!default_document_language && !preamble_document_language)
&& (!default_document_language || !preamble_document_language
|| strcmp (default_document_language, preamble_document_language)))
html_translate_names (self);
destroy_text_options (self->convert_text_options);
self->convert_text_options
= copy_converter_options_for_convert_text (self);
free (default_document_language);
free (preamble_document_language);
return 1;
}
/* conversion */
void
destroy_args_formatted (HTML_ARGS_FORMATTED *args_formatted)
{
if (args_formatted)
{
size_t i;
for (i = 0; i < args_formatted->number; i++)
{
int j;
HTML_ARG_FORMATTED *arg_formatted = &args_formatted->args[i];
if (arg_formatted->arg_tree)
{
for (j = 0; j < AFT_type_raw+1; j++)
free (arg_formatted->formatted[j]);
}
}
free (args_formatted->args);
}
free (args_formatted);
}
#define ADD(x) text_append (result, x)
char *
debug_print_html_contexts (const CONVERTER *self)
{
size_t i;
TEXT contexts_str;
text_init (&contexts_str);
text_append (&contexts_str, "[");
const HTML_DOCUMENT_CONTEXT_STACK *document_context_stack
= &self->html_document_context;
const HTML_DOCUMENT_CONTEXT *top_document_ctx
= html_top_document_context (self);
const HTML_FORMATTING_CONTEXT_STACK *formatting_context_stack
= &top_document_ctx->formatting_context;
for (i = 0; i < document_context_stack->top; i++)
{
const HTML_DOCUMENT_CONTEXT *document_context
= &document_context_stack->stack[i];
if (i != 0)
text_append (&contexts_str, "|");
if (document_context->context)
text_append (&contexts_str, document_context->context);
else
text_append (&contexts_str, "UNDEF");
}
text_append (&contexts_str, "](");
for (i = 0; i < formatting_context_stack->top; i++)
{
const HTML_FORMATTING_CONTEXT *formatting_context
= &formatting_context_stack->stack[i];
if (i != 0)
text_append (&contexts_str, "|");
if (formatting_context->context_name)
text_append (&contexts_str, formatting_context->context_name);
else
text_append (&contexts_str, "UNDEF");
}
text_append (&contexts_str, ")");
/*
text_append (&contexts_str, "{");
for (i = 0; i < top_document_ctx->block_commands.top; i++)
{
enum command_id cmd = top_document_ctx->block_commands.stack[i];
if (i != 0)
text_append (&contexts_str, "|");
text_append (&contexts_str, builtin_command_name (cmd));
}
text_append (&contexts_str, "}");
*/
return contexts_str.text;
}
/* EXPLANATION is used for debugging */
void
html_convert_tree_append (CONVERTER *self, const ELEMENT *element,
TEXT *result, const char *explanation)
{
/* for debugging, for explanations */
TEXT command_type;
char *debug_str;
const char *command_name = 0;
enum command_id cmd = CM_NONE;
text_init (&command_type);
if (! (type_data[element->type].flags & TF_text))
{
cmd = element_builtin_cmd (element);
command_name = element_command_name (element);
if (command_name)
text_printf (&command_type, "@%s ", command_name);
}
if (element->type)
text_append (&command_type, type_data[element->type].name);
if (self->conf->DEBUG.o.integer > 0)
{
TEXT debug_str;
char *contexts_str = debug_print_html_contexts (self);
const char *explanation_str = explanation;
if (!explanation)
explanation_str = "NO EXPLANATION";
text_init (&debug_str);
text_printf (&debug_str, "C|ELEMENT(%s) %s, ->", explanation_str,
contexts_str);
free (contexts_str);
if (command_name)
text_printf (&debug_str, " cmd: %s,", command_name);
if (element->type)
text_printf (&debug_str, " type: %s",
type_data[element->type].name);
if (type_data[element->type].flags & TF_text)
{
if (element->e.text->end > 0)
{
char *text = debug_protect_eol (element->e.text->text);
text_printf (&debug_str, " text: %s", text);
free (text);
}
else
text_append_n (&debug_str, " text(EMPTY)", 12);
}
text_append (&debug_str, "\n");
/*
text_printf (&debug_str, "DETAILS: %s",
print_element_debug_details (element, 0));
*/
fprintf (stderr, "%s", debug_str.text);
free (debug_str.text);
}
/* Process text */
if (type_data[element->type].flags & TF_text)
{
TEXT text_result;
/* NOTE C only text types cannot be ignored here */
if (self->current_types_conversion_function[element->type].status
== FRS_status_ignored)
{
if (self->conf->DEBUG.o.integer > 0)
{
fprintf (stderr, "IGNORED %s\n", command_type.text);
}
goto out;
}
text_init (&text_result);
text_append (&text_result, "");
/* already converted to html, keep it as is, assume it cannot be NULL */
if (element->type == ET__converted)
text_append_n (&text_result, element->e.text->text,
element->e.text->end);
else
{
(*(self->current_types_conversion_function[ET_text].type_conversion))
(self, ET_text, element, element->e.text->text,
&text_result);
}
if (self->conf->DEBUG.o.integer > 0)
{
fprintf (stderr, "C|DO TEXT => `%s'\n", text_result.text);
}
ADD(text_result.text);
free (text_result.text);
goto out;
}
/* ignored if ignored both as type and command */
if ((element->type
&& (self->current_types_conversion_function[element->type].status
== FRS_status_ignored
/* type unknown in Perl. They cannot be customized as ignored
but correspond to @-commands only. We always consider them
to be ignored as type, in the next condition it is checked
if they are ignored as commands */
|| type_data[element->type].flags & TF_c_only))
&& (!cmd
|| self->current_commands_conversion_function[cmd].status
== FRS_status_ignored))
{
if (self->conf->DEBUG.o.integer > 0)
{
fprintf (stderr, "IGNORED %s\n", command_type.text);
}
goto out;
}
if (cmd
&& (element->type != ET_definfoenclose_command
&& element->type != ET_index_entry_command))
{
enum command_id data_cmd = element_builtin_data_cmd (element);
/* C only debug message */
/*
if (self->conf->DEBUG.o.integer > 0)
fprintf (stderr, "COMMAND: %s %s\n",
builtin_command_data[data_cmd].cmdname,
builtin_command_data[cmd].cmdname);
*/
if (builtin_command_data[data_cmd].flags & CF_root)
{
self->current_root_command = element;
self->modified_state |= HMSF_current_root;
}
if (self->current_commands_conversion_function[cmd].command_conversion)
{
TEXT content_formatted;
HTML_ARGS_FORMATTED *args_formatted = 0;
int convert_to_latex
= html_open_command_update_context (self, data_cmd);
if (self->command_open_function[cmd].command_open)
{
(*self->command_open_function[cmd].command_open)
(self, data_cmd, element, result);
}
text_init (&content_formatted);
text_append (&content_formatted, "");
if (element->e.c->contents.number > 0
&& (builtin_command_data[data_cmd].flags & CF_root
|| builtin_command_data[data_cmd].flags & CF_block
|| data_cmd == CM_tab || data_cmd == CM_headitem
|| data_cmd == CM_item))
{
if (convert_to_latex)
{ /* displaymath */
ELEMENT *tmp = new_element (ET_NONE);
char *latex_content;
add_tree_to_build (self, tmp);
tmp->e.c->contents = element->e.c->contents;
latex_content = call_latex_convert_to_latex_math (self,
tmp);
remove_tree_to_build (self, tmp);
tmp->e.c->contents.list = 0;
destroy_element (tmp);
if (latex_content)
{
text_append (&content_formatted, latex_content);
free (latex_content);
}
}
else
{
size_t idx;
text_append (&content_formatted, "");
for (idx = 0; idx < element->e.c->contents.number; idx++)
{
const ELEMENT *content
= element->e.c->contents.list[idx];
char *explanation;
xasprintf (&explanation, "%s c[%zu]", command_type.text,
idx);
html_convert_tree_append (self, content,
&content_formatted,
explanation);
free (explanation);
}
}
}
if ((builtin_command_data[data_cmd].flags & CF_brace
/* could be 0 for brace commands without braces */
&& element->e.c->contents.number > 0)
|| (builtin_command_data[data_cmd].flags & CF_line
&& builtin_command_data[data_cmd].data == LINE_line)
|| ((cmd == CM_item || cmd == CM_itemx)
&& element->e.c->contents.number > 0
&& element->e.c->contents.list[0]->type == ET_line_arg)
|| (cmd == CM_quotation || cmd == CM_smallquotation)
|| cmd == CM_float
|| cmd == CM_cartouche)
{
const ELEMENT_LIST *arguments_list;
size_t arg_idx;
if (element->e.c->contents.list[0]->type == ET_arguments_line)
arguments_list
= &element->e.c->contents.list[0]->e.c->contents;
else
arguments_list = &element->e.c->contents;
TEXT formatted_arg;
text_init (&formatted_arg);
args_formatted = (HTML_ARGS_FORMATTED *)
malloc (sizeof (HTML_ARGS_FORMATTED));
args_formatted->number = arguments_list->number;
args_formatted->args = (HTML_ARG_FORMATTED *)
malloc (args_formatted->number * sizeof (HTML_ARG_FORMATTED));
memset (args_formatted->args, 0,
args_formatted->number * sizeof (HTML_ARG_FORMATTED));
for (arg_idx = 0; arg_idx < arguments_list->number; arg_idx++)
{
char *explanation;
unsigned long arg_flags = 0;
const ELEMENT *arg = arguments_list->list[arg_idx];
HTML_ARG_FORMATTED *arg_formatted
= &args_formatted->args[arg_idx];
if (arg->e.c->contents.number <= 0)
{
continue;
}
/* NOTE that commands with F_AFT_none as only flag do not
have their flag reset to F_AFT_normal here, such that
their argument is not converter here */
if (arg_idx < MAX_COMMAND_ARGS_NR
/* could check html_command_args_flags[cmd].status,
but it is probably faster not to */
&& html_command_args_flags[cmd].flags[arg_idx])
arg_flags = html_command_args_flags[cmd].flags[arg_idx];
else
arg_flags = F_AFT_normal;
arg_formatted->arg_tree = arg;
if (arg_flags & F_AFT_normal)
{
text_reset (&formatted_arg);
if (convert_to_latex)
{
char *latex_content
= call_latex_convert_to_latex_math (self,
arg);
if (latex_content)
{
text_append (&formatted_arg, latex_content);
free (latex_content);
}
else /* most probably only happens if
call_latex_convert_to_latex_math fails
in some way, otherwise an empty string
should be returned */
text_append (&formatted_arg, "");
}
else
{
xasprintf (&explanation, "%s A[%zu]normal",
command_type.text, arg_idx);
html_convert_tree_append (self, arg,
&formatted_arg,
explanation);
free (explanation);
}
arg_formatted->formatted[AFT_type_normal]
= strdup (formatted_arg.text);
}
if (arg_flags & F_AFT_monospace)
{
HTML_DOCUMENT_CONTEXT *top_document_ctx
= html_top_document_context (self);
text_reset (&formatted_arg);
xasprintf (&explanation, "%s A[%zu]monospace",
command_type.text, arg_idx);
push_integer_stack_integer (
&top_document_ctx->monospace, 1);
html_convert_tree_append (self, arg, &formatted_arg,
explanation);
pop_integer_stack
(&top_document_ctx->monospace);
free (explanation);
arg_formatted->formatted[AFT_type_monospace]
= strdup (formatted_arg.text);
}
if (arg_flags & F_AFT_string)
{
HTML_DOCUMENT_CONTEXT *string_document_ctx;
text_reset (&formatted_arg);
html_new_document_context (self, command_type.text,
0, 0);
string_document_ctx = html_top_document_context (self);
string_document_ctx->string_ctx++;
xasprintf (&explanation, "%s A[%zu]string",
command_type.text, arg_idx);
html_convert_tree_append (self, arg, &formatted_arg,
explanation);
free (explanation);
html_pop_document_context (self);
arg_formatted->formatted[AFT_type_string]
= strdup (formatted_arg.text);
}
if (arg_flags & F_AFT_monospacestring)
{
HTML_DOCUMENT_CONTEXT *string_document_ctx;
text_reset (&formatted_arg);
html_new_document_context (self, command_type.text,
0, 0);
string_document_ctx = html_top_document_context (self);
string_document_ctx->string_ctx++;
push_integer_stack_integer (
&string_document_ctx->monospace, 1);
xasprintf (&explanation, "%s A[%zu]monospacestring",
command_type.text, arg_idx);
html_convert_tree_append (self, arg, &formatted_arg,
explanation);
free (explanation);
pop_integer_stack
(&string_document_ctx->monospace);
html_pop_document_context (self);
arg_formatted->formatted[AFT_type_monospacestring]
= strdup (formatted_arg.text);
}
if (arg_flags & F_AFT_monospacetext)
{
char *text;
self->convert_text_options->code_state++;
text = convert_to_text (arg,
self->convert_text_options);
self->convert_text_options->code_state--;
arg_formatted->formatted[AFT_type_monospacetext]
= text;
}
if (arg_flags & F_AFT_filenametext)
{
char *text;
self->convert_text_options->code_state++;
/* Always use encoded characters for file names */
text_set_options_encoding_if_not_ascii (self,
self->convert_text_options);
text = convert_to_text (arg,
self->convert_text_options);
text_reset_options_encoding
(self->convert_text_options);
self->convert_text_options->code_state--;
arg_formatted->formatted[AFT_type_filenametext] = text;
}
if (arg_flags & F_AFT_url)
{
char *text;
self->convert_text_options->code_state++;
/* set the encoding to UTF-8 to always have a string that is suitable
for percent encoding. */
text_set_options_encoding (
self->convert_text_options, "utf-8");
text = convert_to_text (arg,
self->convert_text_options);
text_reset_options_encoding
(self->convert_text_options);
self->convert_text_options->code_state--;
arg_formatted->formatted[AFT_type_url] = text;
}
if (arg_flags & F_AFT_raw)
{
HTML_DOCUMENT_CONTEXT *top_document_ctx
= html_top_document_context (self);
text_reset (&formatted_arg);
top_document_ctx->raw_ctx++;
xasprintf (&explanation, "%s A[%zu]raw",
command_type.text, arg_idx);
html_convert_tree_append (self, arg, &formatted_arg,
explanation);
free (explanation);
top_document_ctx->raw_ctx--;
arg_formatted->formatted[AFT_type_raw]
= strdup (formatted_arg.text);
}
}
free (formatted_arg.text);
}
html_convert_command_update_context (self, data_cmd);
if (element->e.c->cmd == CM_node)
{
self->current_node = element;
self->modified_state |= HMSF_current_node;
}
/* args are formatted, now format the command itself */
if (self->current_commands_conversion_function[cmd].command_conversion)
{
(*self->current_commands_conversion_function[cmd].command_conversion)
(self, cmd, element, args_formatted,
content_formatted.text, result);
}
else if (args_formatted)
fprintf (stderr, "No command_conversion for %s\n",
command_name);
if (args_formatted)
destroy_args_formatted (args_formatted);
if (cmd == CM_documentlanguage)
{
html_translate_names (self);
}
free (content_formatted.text);
goto out;
}
else
{
if (self->conf->DEBUG.o.integer > 0
|| self->conf->VERBOSE.o.integer > 0)
fprintf (stderr, "Command not converted: %s\n", command_name);
if (builtin_command_data[data_cmd].flags & CF_root)
{
self->current_root_command = 0;
self->modified_state |= HMSF_current_root;
}
goto out;
}
}
else if (element->type)
{
enum element_type type = element->type;
TEXT type_result;
TEXT content_formatted;
text_init (&type_result);
text_append (&type_result, "");
html_open_type_update_context (self, type);
if (self->type_open_function[type].type_open)
(*self->type_open_function[type].type_open)
(self, type, element, &type_result);
text_init (&content_formatted);
if (type == ET_definfoenclose_command)
{
if (element->e.c->contents.number > 0)
{
html_convert_tree_append (self, element->e.c->contents.list[0],
&content_formatted,
"DEFINFOENCLOSE_ARG");
}
}
else if (element->e.c->contents.number > 0
&& type != ET_untranslated_def_line_arg)
{
size_t content_idx;
text_append (&content_formatted, "");
for (content_idx = 0; content_idx < element->e.c->contents.number;
content_idx++)
{
const ELEMENT *content = element->e.c->contents.list[content_idx];
char *explanation;
xasprintf (&explanation, "%s c[%zu]", command_type.text,
content_idx);
html_convert_tree_append (self, content, &content_formatted,
explanation);
free (explanation);
}
}
html_convert_type_update_context (self, type);
if (self->current_types_conversion_function[type].type_conversion)
{
(*self->current_types_conversion_function[type].type_conversion)
(self, type, element, content_formatted.text, &type_result);
}
else if (content_formatted.end > 0)
{
text_append (&type_result, content_formatted.text);
}
free (content_formatted.text);
if (self->conf->DEBUG.o.integer > 0)
{
fprintf (stderr, "C|DO type (%s) => `%s'\n", type_data[type].name,
type_result.text);
}
ADD(type_result.text);
free (type_result.text);
goto out;
}
else if (element->e.c->contents.number > 0)
{
/* no type, no cmdname, but contents. */
/* this happens inside accents, for section/node names, for @images. */
TEXT content_formatted;
text_init (&content_formatted);
text_append (&content_formatted, "");
size_t content_idx;
for (content_idx = 0; content_idx < element->e.c->contents.number;
content_idx++)
{
const ELEMENT *content = element->e.c->contents.list[content_idx];
char *explanation;
xasprintf (&explanation, " C[%zu]", content_idx);
html_convert_tree_append (self, content, &content_formatted,
explanation);
free (explanation);
}
if (self->conf->DEBUG.o.integer > 0)
fprintf (stderr, "UNNAMED HOLDER => `%s'\n", content_formatted.text);
ADD(content_formatted.text);
free (content_formatted.text);
goto out;
}
else
{
if (self->conf->DEBUG.o.integer > 0)
fprintf (stderr, "UNNAMED empty\n");
if (self->current_types_conversion_function[0].type_conversion)
{
(*self->current_types_conversion_function[0].type_conversion)
(self, 0, element, "", result);
}
goto out;
}
debug_str = print_element_debug (element, 0);
fprintf (stderr, "DEBUG: HERE!(%p:%s)\n", element, debug_str);
free (debug_str);
out:
free (command_type.text);
}
#undef ADD
void
convert_output_unit (CONVERTER *self, const OUTPUT_UNIT *output_unit,
const char *explanation, TEXT *result)
{
TEXT content_formatted;
/* store this to be able to show only what was added in debug message */
size_t input_result_end = result->end;
enum output_unit_type unit_type = output_unit->unit_type;
if (self->output_unit_conversion_function[unit_type].status
== FRS_status_ignored)
{
if (self->conf->DEBUG.o.integer > 0)
{
fprintf (stderr, "IGNORED OU %s\n",
output_unit_type_names[unit_type]);
}
return;
}
if (self->conf->DEBUG.o.integer > 0)
{
char *output_unit_txi = output_unit_texi (output_unit);
fprintf (stderr, "C|UNIT(%s) -> ou: %s '%s'\n", explanation,
output_unit_type_names[unit_type],
output_unit_txi);
free (output_unit_txi);
}
self->current_output_unit = output_unit;
text_init (&content_formatted);
text_append (&content_formatted, "");
if (output_unit->unit_contents.number > 0)
{
size_t content_idx;
for (content_idx = 0; content_idx < output_unit->unit_contents.number;
content_idx++)
{
const ELEMENT *content = output_unit->unit_contents.list[content_idx];
char *content_explanation;
xasprintf (&content_explanation, "%s c[%zu]",
output_unit_type_names[unit_type], content_idx);
html_convert_tree_append (self, content, &content_formatted,
content_explanation);
free (content_explanation);
}
}
if (self->output_unit_conversion_function[unit_type].status)
{
(*(self->output_unit_conversion_function[unit_type].output_unit_conversion))
(self, unit_type, output_unit,
content_formatted.text, result);
}
else
{
text_append (result, content_formatted.text);
}
free (content_formatted.text);
self->current_output_unit = 0;
if (self->conf->DEBUG.o.integer > 0)
fprintf (stderr, "DOUNIT (%s) => `%s'\n", output_unit_type_names[unit_type],
result->text + input_result_end);
}
/* wrapper to avoid code repetition and use similar functions as in perl */
void
convert_convert_output_unit_internal (CONVERTER *self, TEXT *result,
const OUTPUT_UNIT *output_unit,
size_t unit_nr,
const char *debug_str,
const char *explanation_str)
{
char *explanation;
if (self->conf->DEBUG.o.integer > 0)
fprintf (stderr, "\n%s %zu\n", debug_str, unit_nr);
xasprintf (&explanation, "%s %zu", explanation_str, unit_nr);
convert_output_unit (self, output_unit, explanation, result);
free (explanation);
}
char *
html_convert_convert (CONVERTER *self, const ELEMENT *root)
{
TEXT result;
size_t unit_nr = 0;
size_t i;
const OUTPUT_UNIT_LIST *output_units = retrieve_output_units
(self->document, self->output_units_descriptors[OUDT_units]);
const OUTPUT_UNIT_LIST *special_units = retrieve_output_units
(self->document, self->output_units_descriptors[OUDT_special_units]);
text_init (&result);
self->current_filename.filename = "";
self->current_filename.file_number = 1;
for (i = 0; i < output_units->number; i++)
{
const OUTPUT_UNIT *output_unit = output_units->list[i];
convert_convert_output_unit_internal (self, &result, output_unit,
unit_nr, "C UNIT", "convert unit");
unit_nr++;
}
if (special_units && special_units->number)
{
for (i = 0; i < special_units->number; i++)
{
const OUTPUT_UNIT *special_unit = special_units->list[i];
convert_convert_output_unit_internal (self, &result,
special_unit, unit_nr, "C UNIT", "convert unit");
unit_nr++;
}
}
self->current_filename.filename = 0;
return result.text;
}
int
convert_output_output_unit_internal (CONVERTER *self,
const ENCODING_CONVERSION *conversion,
TEXT *text,
const OUTPUT_UNIT *output_unit,
size_t unit_nr)
{
FILE_NAME_PATH_COUNTER *unit_file = 0;
size_t file_index;
int empty_body = 0; /* set if body is empty and it is a special unit */
char *output_unit_filename = output_unit->unit_filename;
self->current_filename.filename = output_unit_filename;
text_reset (text);
text_append (text, "");
if (output_unit->unit_type == OU_special_unit)
{
char *debug_str;
const char *special_unit_variety = output_unit->special_unit_variety;
file_index = self->special_unit_file_indices[output_unit->index];
self->current_filename.file_number = file_index +1;
unit_file = &self->output_unit_files.list[file_index];
xasprintf (&debug_str, "UNIT SPECIAL %s", special_unit_variety);
convert_convert_output_unit_internal (self, text,
output_unit, unit_nr, debug_str, "output s-unit");
free (debug_str);
if (!strcmp (text->text, ""))
empty_body = 1;
}
else
{
file_index = self->output_unit_file_indices[output_unit->index];
self->current_filename.file_number = file_index +1;
unit_file = &self->output_unit_files.list[file_index];
convert_convert_output_unit_internal (self, text, output_unit,
unit_nr, "UNIT", "output unit");
}
unit_file->counter--;
/* register the output but do not print anything. Printing
only when file_counters reach 0, to be sure that all the
elements have been converted before headers are done. */
if (!empty_body)
{
if (!unit_file->first_unit)
{
unit_file->first_unit = output_unit;
text_init (&unit_file->body);
}
text_append (&unit_file->body, text->text);
}
else
{
if (!unit_file->first_unit
|| unit_file->body.end == 0)
{
return 1;
}
}
if (unit_file->counter == 0)
{
const OUTPUT_UNIT *file_output_unit = unit_file->first_unit;
char *file_end;
char *file_beginning;
const char *out_filepath = unit_file->filepath;
char *path_encoding;
char *open_error_message;
int overwritten_file;
/* cast to remove const since the argument cannot
be const even though the string is not modified */
char *encoded_out_filepath
= converter_encoded_output_file_name (self->conf,
&self->document->global_info,
(char *)out_filepath, &path_encoding, 0);
/* overwritten_file being set cannot happen */
FILE *file_fh = output_files_open_out (&self->output_files_information,
encoded_out_filepath, &open_error_message,
&overwritten_file, 0);
free (path_encoding);
if (!file_fh)
{
message_list_document_error (&self->error_messages, self->conf, 0,
"could not open %s for writing: %s",
out_filepath, open_error_message);
free (open_error_message);
free (encoded_out_filepath);
return 0;
}
/* do end file first in case it requires some CSS */
file_end = html_format_end_file (self, output_unit_filename,
output_unit);
file_beginning = html_format_begin_file (self, output_unit_filename,
file_output_unit);
text_reset (text);
if (file_beginning)
{
text_append (text, file_beginning);
free (file_beginning);
}
if (unit_file->body.end)
{
text_append (text, unit_file->body.text);
}
if (file_end)
{
text_append (text, file_end);
free (file_end);
}
if (text->end)
{
char *result;
size_t res_len;
size_t write_len;
if (conversion)
{
result = encode_with_iconv (conversion->iconv, text->text, 0);
res_len = strlen (result);
}
else
{
result = text->text;
res_len = text->end;
}
write_len = fwrite (result, sizeof (char), res_len, file_fh);
if (conversion)
free (result);
if (write_len != res_len)
{ /* register error message instead? */
fprintf (stderr, "ERROR: write to %s failed (%zu/%zu)\n",
encoded_out_filepath, write_len, res_len);
free (encoded_out_filepath);
return 0;
}
}
/* Do not close STDOUT now such that the file descriptor is not reused
by open, which uses the lowest-numbered file descriptor not open,
for another filehandle. Closing STDOUT is handled by the caller. */
if (strcmp (out_filepath, "-"))
{
output_files_register_closed (&self->output_files_information,
encoded_out_filepath);
if (fclose (file_fh))
{
message_list_document_error (&self->error_messages, self->conf, 0,
"error on closing %s: %s",
out_filepath, strerror (errno));
free (encoded_out_filepath);
return 0;
}
}
free (encoded_out_filepath);
}
return 1;
}
char *
html_convert_output (CONVERTER *self, const ELEMENT *root,
const char *output_file, const char *destination_directory,
const char *output_filename, const char *document_name)
{
int status = 1;
TEXT result;
TEXT text; /* reused for all the output units */
const OUTPUT_UNIT_LIST *output_units = retrieve_output_units
(self->document, self->output_units_descriptors[OUDT_units]);
const OUTPUT_UNIT_LIST *special_units = retrieve_output_units
(self->document, self->output_units_descriptors[OUDT_special_units]);
char *encoded_destination_directory;
char *dir_encoding;
int succeeded;
/* cast to remove const since the argument cannot
be const even though the string is not modified */
encoded_destination_directory
= converter_encoded_output_file_name (self->conf,
&self->document->global_info,
(char *)destination_directory,
&dir_encoding, 0);
free (dir_encoding);
succeeded = create_destination_directory (self,
encoded_destination_directory,
destination_directory);
free (encoded_destination_directory);
if (!succeeded)
return 0;
text_init (&result);
text_init (&text);
/* set self->date_in_header to format it only once */
if (self->conf->DATE_IN_HEADER.o.integer > 0)
{
html_default_format_date_in_header (self, &text);
self->date_in_header = strdup (text.text);
text_reset (&text);
}
text_append (&result, "");
if (!strlen (output_file))
{
char *file_end;
char *file_beginning;
size_t unit_nr = 0;
size_t i;
self->current_filename.filename = output_filename;
self->current_filename.file_number = 1;
text_append (&text, "");
for (i = 0; i < output_units->number; i++)
{
const OUTPUT_UNIT *output_unit = output_units->list[i];
convert_convert_output_unit_internal (self, &text, output_unit,
unit_nr, "UNIT NO-PAGE", "no-page output unit");
unit_nr++;
}
if (special_units && special_units->number)
{
for (i = 0; i < special_units->number; i++)
{
const OUTPUT_UNIT *special_unit = special_units->list[i];
convert_convert_output_unit_internal (self, &text,
special_unit, unit_nr, "UNIT NO-PAGE",
"no-page output unit");
unit_nr++;
}
}
/* do end file first, in case it needs some CSS */
file_end = html_format_end_file (self, output_filename, 0);
file_beginning = html_format_begin_file (self, output_filename, 0);
if (file_beginning)
{
text_append (&result, file_beginning);
free (file_beginning);
}
text_append (&result, text.text);
if (file_end)
{
text_append (&result, file_end);
free (file_end);
}
self->current_filename.filename = 0;
}
else
{
size_t unit_nr = 0;
size_t i;
const ENCODING_CONVERSION *conversion = 0;
if (self->conf->OUTPUT_ENCODING_NAME.o.string
&& strcmp (self->conf->OUTPUT_ENCODING_NAME.o.string, "utf-8"))
{
conversion
= get_encoding_conversion (
self->conf->OUTPUT_ENCODING_NAME.o.string,
&output_conversions);
}
if (self->conf->DEBUG.o.integer > 0)
fprintf (stderr, "DO Units with filenames\n");
for (i = 0; i < output_units->number; i++)
{
const OUTPUT_UNIT *output_unit = output_units->list[i];
status = convert_output_output_unit_internal (self, conversion,
&text, output_unit, unit_nr);
if (!status)
{
/*
fprintf (stderr, " FAILED U(%d %d): %s\n", i, unit_nr,
output_unit_texi (output_unit));
*/
goto out;
}
unit_nr++;
}
if (special_units && special_units->number)
{
for (i = 0; i < special_units->number; i++)
{
const OUTPUT_UNIT *special_unit = special_units->list[i];
status = convert_output_output_unit_internal (self, conversion,
&text, special_unit, unit_nr);
if (!status)
goto out;
unit_nr++;
}
}
memset (&self->current_filename, 0, sizeof (FILE_NUMBER_NAME));
}
out:
free (text.text);
if (status)
return result.text;
else
{
free (result.text);
return 0;
}
}
/* This function cleans up the conversion state that is relevant during
conversion. Other information is removed when calling reset_parser
later on and should not be freed here */
void
html_conversion_finalization (CONVERTER *self)
{
size_t i;
for (i = 0; i < self->html_files_information.number; i++)
{
free (self->html_files_information.list[i].info);
}
free (self->html_files_information.list);
/* should not be possible with default code, as
close_registered_sections_level(..., 0)
is called at the end of processing or at the end of each file.
However, it could happen if the conversion functions are user
defined.
*/
for (i = 0; i < self->pending_closes.number; i++)
{
STRING_STACK *file_pending_closes = &self->pending_closes.list[i];
if (file_pending_closes->top > 0)
{
FILE_NAME_PATH_COUNTER *file_counter
= &self->output_unit_files.list[i];
const char *page_name = file_counter->filename;
message_list_document_warn (&self->error_messages, self->conf, 0,
"%s: %zu registered opened sections not closed",
page_name, file_pending_closes->top);
clear_string_stack (file_pending_closes);
}
}
if (self->pending_inline_content.top > 0)
{
char *inline_content = html_get_pending_formatted_inline_content (self);
message_list_document_warn (&self->error_messages, self->conf, 0,
"%zu registered inline contents: %s",
self->pending_inline_content.top, inline_content);
free (inline_content);
}
for (i = 0; i < self->associated_inline_content.number; i++)
{
HTML_ASSOCIATED_INLINE_CONTENT *associated_content
= &self->associated_inline_content.list[i];
if (associated_content->inline_content.space > 0)
{
char *inline_content = associated_content->inline_content.text;
if (associated_content->element)
{
char *element_str
= print_element_debug (associated_content->element, 0);
message_list_document_warn (&self->error_messages, self->conf, 0,
"left inline content associated to %s: '%s'", element_str,
inline_content);
free (element_str);
}
else if (associated_content->hv)
{
message_list_document_warn (&self->error_messages, self->conf, 0,
"left inline content of %p: '%s'", associated_content->hv,
inline_content);
}
else
message_list_document_warn (&self->error_messages, self->conf, 0,
"left inline content associated: '%s'", inline_content);
free (associated_content->inline_content.text);
}
}
self->associated_inline_content.number = 0;
html_pop_document_context (self);
/* could change to 0 in releases? */
if (1)
{
if (self->html_document_context.top > 0)
fprintf (stderr, "BUG: document context top > 0: %zu\n",
self->html_document_context.top);
if (self->document_global_context)
fprintf (stderr, "BUG: document_global_context: %d\n",
self->document_global_context);
if (self->multiple_conversions)
fprintf (stderr, "BUG: multiple_conversions: %d\n",
self->multiple_conversions);
}
}
void
html_check_transfer_state_finalization (CONVERTER *self)
{
/* could change to 0 in releases? */
if (1)
{
/* check that all the state changes have been transmitted */
/*
if (self->tree_to_build.number > 0)
fprintf (stderr, "BUG: tree_to_build: %zu\n",
self->tree_to_build.number);
*/
if (self->no_arg_formatted_cmd_translated.number)
fprintf (stderr, "BUG: no_arg_formatted_cmd_translated: %zu\n",
self->no_arg_formatted_cmd_translated.number);
}
}
/* code run after the conversion when called as output */
/* return 0 on success, -1 on write or close error, -2 if file_fh is 0,
which should mean a failure to open */
/* It should be made sure by the caller that the closed FILE_FH cannot
be STDOUT. If it becomes possible, an argument to avoid closing the
file descriptor should be added */
static int
file_error_or_write_close (CONVERTER *self, const char *out_filepath,
const char *encoded_out_filepath,
FILE *file_fh,
const ENCODING_CONVERSION *conversion,
char *page,
const char *open_error_message)
{
if (!file_fh)
{
message_list_document_error (&self->error_messages,
self->conf, 0,
"could not open %s for writing: %s",
out_filepath, open_error_message);
return -2;
}
else
{
char *result;
size_t res_len;
size_t write_len;
if (conversion)
{
result = encode_with_iconv (conversion->iconv,
page, 0);
res_len = strlen (result);
}
else
{
result = page;
res_len = strlen (page);
}
write_len = fwrite (result, sizeof (char),
res_len, file_fh);
if (conversion)
free (result);
if (write_len != res_len)
{ /* register error message instead? */
fprintf (stderr,
"ERROR: write to %s failed (%zu/%zu)\n",
encoded_out_filepath, write_len, res_len);
return -1;
}
output_files_register_closed
(&self->output_files_information,
encoded_out_filepath);
if (fclose (file_fh))
{
message_list_document_error (
&self->error_messages, self->conf, 0,
"error on closing %s: %s",
out_filepath, strerror (errno));
return -1;
}
}
return 0;
}
static void
do_jslicenses_file (CONVERTER *self)
{
const char *destination_directory = self->destination_directory;
const char *setting = self->conf->JS_WEBLABELS.o.string;
const char *path = self->conf->JS_WEBLABELS_FILE.o.string;
char *formatted_jlicenses;
int path_not_ok = 0;
char *license_file;
char *path_encoding;
char *open_error_message;
int overwritten_file;
char *encoded_out_filepath;
FILE *file_fh;
const ENCODING_CONVERSION *conversion = 0;
/* Possible settings:
'generate' - create file at JS_WEBLABELS_FILE
'reference' - reference file at JS_WEBLABELS_FILE but do not create it
'omit' - do nothing */
if (!setting || strcmp (setting, "generate") || !path || !strlen (path))
return;
if (file_name_is_absolute (path) || !strcmp (path, "-"))
path_not_ok = 1;
else
{
const char *p = path;
while (isascii_alpha (*p))
p++;
if (*p == ':')
path_not_ok = 1;
}
if (path_not_ok)
{
message_list_document_warn (&self->error_messages, self->conf, 0,
"cannot use absolute path or URL `%s' for JS_WEBLABELS_FILE when generating web labels file",
path);
return;
}
formatted_jlicenses
= html_default_format_jslicense_file (self, &self->jslicenses);
if (destination_directory && strlen (destination_directory))
xasprintf (&license_file, "%s/%s", destination_directory, path);
else
license_file = strdup (path);
encoded_out_filepath
= converter_encoded_output_file_name (self->conf,
&self->document->global_info, license_file,
&path_encoding, 0);
file_fh = output_files_open_out (&self->output_files_information,
encoded_out_filepath, &open_error_message,
&overwritten_file, 0);
free (path_encoding);
if (overwritten_file)
{
message_list_document_warn (&self->error_messages, self->conf, 0,
"overwriting output file with js licences: %s",
license_file);
}
if (file_fh)
{
if (self->conf->OUTPUT_ENCODING_NAME.o.string
&& strcmp (self->conf->OUTPUT_ENCODING_NAME.o.string, "utf-8"))
{
conversion
= get_encoding_conversion (
self->conf->OUTPUT_ENCODING_NAME.o.string,
&output_conversions);
}
}
file_error_or_write_close (self, license_file,
encoded_out_filepath, file_fh,
conversion, formatted_jlicenses,
open_error_message);
free (open_error_message);
free (encoded_out_filepath);
free (license_file);
free (formatted_jlicenses);
}
/* Set FOPEN_RBIN and ROPEN_WBIN. */
#ifndef O_BINARY
# ifdef _O_BINARY
# define O_BINARY _O_BINARY
# else
# define O_BINARY 0
# endif
#endif /* O_BINARY */
#if O_BINARY /* MS-Windows */
# define FOPEN_RBIN "rb"
# define FOPEN_WBIN "wb"
#else
# define FOPEN_RBIN "r"
# define FOPEN_WBIN "w"
#endif
/* Copy FROM to TO. FROM_FILE_NAME and TO_FILE_NAME are unencoded strings
used for error messages. */
static void
copy_file_to (CONVERTER *self,
const char *from, const char *to,
const char *from_file_name, const char *to_file_name)
{
FILE *src = 0, *dest = 0;
src = fopen (from, FOPEN_RBIN);
if (!src)
{
message_list_document_error (&self->error_messages,
self->conf, 0,
"error while opening %s for reading: %s",
from_file_name, strerror (errno));
return;
}
dest = fopen (to, FOPEN_WBIN);
if (!dest)
{
message_list_document_error (&self->error_messages,
self->conf, 0,
"cannot open %s for writing: %s",
to_file_name, strerror (errno));
fclose (src);
return;
}
#define bufsize 512
char buf[bufsize];
size_t nread, nwritten;
do
{
nread = fread (buf, sizeof(char), sizeof(buf), src);
nwritten = fwrite (buf, sizeof(char), nread/2, dest);
if (nwritten != nread)
{
message_list_document_error (&self->error_messages,
self->conf, 0,
"error writing %s: %s",
to_file_name, strerror (errno));
fclose (src);
fclose (dest);
return;
}
}
while (nread == bufsize);
#undef bufsize
if (ferror (src))
{
message_list_document_error (&self->error_messages,
self->conf, 0,
"error reading %s: %s",
from_file_name, strerror (errno));
fclose (src);
fclose (dest);
return;
}
fclose (src);
if (fclose (dest) != 0)
{
message_list_document_error (&self->error_messages,
self->conf, 0,
"error closing %s: %s",
to_file_name, strerror (errno));
return;
}
}
static const char *js_files[4] = {"info.js", "modernizr.js", "info.css", 0};
void
html_do_js_files (CONVERTER *self)
{
const char *destination_directory = self->destination_directory;
if (self->conf->INFO_JS_DIR.o.string)
{
const char *info_js_dir = self->conf->INFO_JS_DIR.o.string;
char *jsdir;
char *dir_encoding;
int succeeded;
char *encoded_jsdir;
if (destination_directory && strlen (destination_directory))
{
xasprintf (&jsdir, "%s/%s", destination_directory, info_js_dir);
}
else
jsdir = strdup (info_js_dir);
encoded_jsdir = converter_encoded_output_file_name (self->conf,
&self->document->global_info,
jsdir, &dir_encoding, 0);
free (dir_encoding);
succeeded = create_destination_directory (self, encoded_jsdir, jsdir);
if (succeeded)
{
int i;
if (self->conf->TEST.o.integer <= 0)
{
/* txi_paths_info paths are byte strings */
char *jssrcdir;
if (!txi_paths_info.texinfo_uninstalled)
{
xasprintf (&jssrcdir, "%s/%s",
txi_paths_info.p.installed.converterdatadir,
"js");
}
else
{
if (txi_paths_info.p.uninstalled.t2a_srcdir)
xasprintf (&jssrcdir, "%s/../%s",
txi_paths_info.p.uninstalled.t2a_srcdir,
"js");
else
jssrcdir = strdup ("js");
}
for (i = 0; js_files[i]; i++)
{
char *from, *to;
char *from_file_name, *to_file_name;
xasprintf (&from, "%s/%s", jssrcdir, js_files[i]);
xasprintf (&to, "%s/%s", encoded_jsdir, js_files[i]);
xasprintf (&to_file_name, "%s/%s", jsdir, js_files[i]);
const char *encoding
= self->conf->COMMAND_LINE_ENCODING.o.string;
if (encoding)
{
int status;
char *decoded_jssrcdir;
decoded_jssrcdir = decode_string (jssrcdir,
encoding, &status, 0);
xasprintf (&from_file_name,
"%s/%s", decoded_jssrcdir, js_files[i]);
free (decoded_jssrcdir);
}
else
{
from_file_name = strdup (from);
}
copy_file_to (self, from, to, from_file_name, to_file_name);
free (to);
free (from);
free (to_file_name);
free (from_file_name);
}
free (jssrcdir);
}
else
{
/* create empty files for tests to keep results stable. */
for (i = 0; js_files[i]; i++)
{
char *to;
FILE *FH;
xasprintf (&to, "%s/%s", encoded_jsdir, js_files[i]);
FH = fopen (to, "w");
if (!FH)
{
char *to_file_name;
xasprintf (&to_file_name, "%s/%s", jsdir, js_files[i]);
message_list_document_error (&self->error_messages,
self->conf, 0,
"error on creating empty %s: %s",
to_file_name, strerror (errno));
free (to_file_name);
}
else
{
if (fclose (FH) == EOF)
{
char *to_file_name;
xasprintf (&to_file_name, "%s/%s", jsdir, js_files[i]);
message_list_document_error (&self->error_messages,
self->conf, 0,
"error on closing empty %s: %s",
to_file_name, strerror (errno));
free (to_file_name);
}
}
free (to);
}
}
}
free (encoded_jsdir);
free (jsdir);
}
if (self->jslicenses.number > 0)
do_jslicenses_file (self);
}
void
html_set_file_source_info (FILE_SOURCE_INFO *file_source_info,
const char *file_info_type,
const char *file_info_name,
const ELEMENT *file_info_element,
const char *filepath)
{
file_source_info->type = file_info_type;
file_source_info->name = file_info_name;
file_source_info->element = file_info_element;
if (filepath)
file_source_info->path = strdup (filepath);
else
file_source_info->path = 0;
}
FILE_SOURCE_INFO *
html_add_to_files_source_info (FILE_SOURCE_INFO_LIST *files_source_info,
const char *filename,
const char *file_info_type,
const char *file_info_name,
const ELEMENT *file_info_element,
const char *filepath)
{
FILE_SOURCE_INFO *new_file_source_info;
if (files_source_info->number == files_source_info->space)
{
files_source_info->list = realloc (files_source_info->list,
(files_source_info->space += 5) * sizeof (FILE_SOURCE_INFO));
if (!files_source_info->list)
fatal ("realloc failed");
}
new_file_source_info =
&files_source_info->list[files_source_info->number];
new_file_source_info->filename = strdup (filename);
html_set_file_source_info (new_file_source_info, file_info_type,
file_info_name, file_info_element, filepath);
files_source_info->number++;
return new_file_source_info;
}
FILE_SOURCE_INFO *
html_find_file_source_info (FILE_SOURCE_INFO_LIST *files_source_info,
const char *filename)
{
size_t i;
for (i = 0; i < files_source_info->number; i++)
{
FILE_SOURCE_INFO *file_source_info = &files_source_info->list[i];
if (!strcmp (file_source_info->filename, filename))
return file_source_info;
}
return 0;
}
/* return string to be freed by the caller */
char *
html_prepare_node_redirection_page (CONVERTER *self, const ELEMENT *element,
const char *filename)
{
char *result;
self->current_filename.filename = filename;
self->current_filename.file_number = 0;
result = html_format_node_redirection_page (self, element, filename);
self->current_filename.filename = 0;
return result;
}
int
html_node_redirections (CONVERTER *self,
const char *output_file, const char *destination_directory)
{
FILE_SOURCE_INFO_LIST *files_source_info = &self->files_source_info;
int redirection_files_done = 0;
if (self->document->identifiers_target.number > 0
&& self->conf->NODE_FILES.o.integer > 0
&& strlen (output_file) > 0)
{
const LABEL_LIST *label_targets = &self->document->labels_list;
size_t i;
const ENCODING_CONVERSION *conversion = 0;
STRING_LIST redirection_files;
const char *added_translit_extension = 0;
int add_translit_redirection = 0;
memset (&redirection_files, 0, sizeof (STRING_LIST));
if (self->conf->ADD_TRANSLITERATED_REDIRECTION_FILES.o.integer > 0
|| self->conf->TRANSLITERATE_FILE_NAMES.o.integer > 0)
{
add_translit_redirection = 1;
added_translit_extension = "";
if (self->conf->EXTENSION.o.string)
added_translit_extension = self->conf->EXTENSION.o.string;
}
if (self->conf->OUTPUT_ENCODING_NAME.o.string
&& strcmp (self->conf->OUTPUT_ENCODING_NAME.o.string, "utf-8"))
{
conversion
= get_encoding_conversion (
self->conf->OUTPUT_ENCODING_NAME.o.string,
&output_conversions);
}
for (i = 0; i < label_targets->number; i++)
{
const FILE_NUMBER_NAME *target_filename;
const ELEMENT *label_element;
const ELEMENT *target_element;
char *node_filename;
LABEL *label = &label_targets->list[i];
const char *normalized;
char *node_redirection_filename = 0;
size_t j;
if (!label->identifier || label->reference)
continue;
target_element = label->element;
label_element = get_label_element (target_element);
/* filename may not be defined in case of an @anchor or similar in
@titlepage, and @titlepage is not used. */
target_filename = html_command_filename (self, target_element);
if (!target_filename || !target_filename->filename) {
continue;
}
/* 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. */
normalized = lookup_extra_string (target_element, AI_key_normalized);
if (normalized && !strcmp (normalized, "Top")
&& self->conf->TOP_NODE_FILE_TARGET.o.string)
{
node_filename = strdup (self->conf->TOP_NODE_FILE_TARGET.o.string);
}
else
{
TARGET_FILENAME *target_filename
= html_standard_label_id_file (self, normalized, label_element,
self->conf->EXTERNAL_CROSSREF_EXTENSION.o.string,
html_default_options->options->EXTENSION.o.string);
free (target_filename->target);
xasprintf (&node_filename, "%s.%s", target_filename->filename,
target_filename->extension);
free (target_filename->filename);
free (target_filename);
}
if (strcmp (target_filename->filename, node_filename))
{
size_t file_idx
= register_normalize_case_filename (self, node_filename);
const FILE_NAME_PATH_COUNTER *output_unit_file
= &self->output_unit_files.list[file_idx];
node_redirection_filename = output_unit_file->filename;
int redirection_filename_total_count
= output_unit_file->elements_in_file_count;
FILE_SOURCE_INFO *file_source_info
= html_find_file_source_info (files_source_info,
node_redirection_filename);
if (file_source_info
/* first condition finds conflict with tree elements */
&& (redirection_filename_total_count > 0
|| !strcmp (file_source_info->type, "redirection")))
{
const char *file_info_type = file_source_info->type;
char *label_texi
= convert_contents_to_texinfo (label_element);
message_list_command_warn (&self->error_messages,
(self->conf && self->conf->DEBUG.o.integer > 0),
target_element, 0,
"@%s `%s' file %s for redirection exists",
element_command_name (target_element),
label_texi, node_redirection_filename);
free (label_texi);
if (!strcmp (file_info_type, "special_file")
|| !strcmp (file_info_type, "stand_in_file"))
{
const char *name = file_source_info->name;
if (!strcmp (name, "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. */
message_list_document_warn (&self->error_messages,
self->conf, 1, "conflict with whole document file");
else if (!strcmp (name, "Top"))
message_list_document_warn (&self->error_messages,
self->conf, 1, "conflict with Top file");
else if (!strcmp (name, "user_defined"))
message_list_document_warn (&self->error_messages,
self->conf, 1, "conflict with user-defined file");
else if (!strcmp (name, "unknown_node"))
message_list_document_warn (&self->error_messages,
self->conf, 1, "conflict with unknown node file");
else if (!strcmp (name, "unknown"))
message_list_document_warn (&self->error_messages,
self->conf, 1,
"conflict with file without known source");
}
else if (!strcmp (file_info_type, "node"))
{
const ELEMENT *conflicting_node
= file_source_info->element;
const ELEMENT *label_element
= get_label_element (conflicting_node);
char *node_texi
= convert_contents_to_texinfo (label_element);
pmessage_list_command_warn (&self->error_messages,
(self->conf && self->conf->DEBUG.o.integer > 0),
conflicting_node, 1,
"conflict of redirection file with file based on node name",
"conflict with @%s `%s' file",
element_command_name (conflicting_node),
node_texi);
free (node_texi);
}
else if (!strcmp (file_info_type, "redirection"))
{
const ELEMENT *conflicting_node
= file_source_info->element;
const ELEMENT *label_element
= get_label_element (conflicting_node);
char *node_texi
= convert_contents_to_texinfo (label_element);
message_list_command_warn (&self->error_messages,
(self->conf && self->conf->DEBUG.o.integer > 0),
conflicting_node, 1,
"conflict with @%s `%s' redirection file",
element_command_name (conflicting_node),
node_texi);
free (node_texi);
}
else if (!strcmp (file_info_type, "section"))
{
const ELEMENT *conflicting_section
= file_source_info->element;
/* arguments_line type element */
const ELEMENT *arguments_line
= conflicting_section->e.c->contents.list[0];
const ELEMENT *line_arg
= arguments_line->e.c->contents.list[0];
char *section_texi
= convert_contents_to_texinfo (line_arg);
pmessage_list_command_warn (&self->error_messages,
(self->conf && self->conf->DEBUG.o.integer > 0),
conflicting_section, 1,
"conflict of redirection file with file based on section name",
"conflict with @%s `%s' file",
element_command_name (conflicting_section),
section_texi);
free (section_texi);
}
else if (!strcmp (file_info_type, "special_unit"))
{
const ELEMENT *unit_command
= file_source_info->element;
const OUTPUT_UNIT *special_unit
= unit_command->e.c->associated_unit;
message_list_document_warn (&self->error_messages,
self->conf, 1,
"conflict with %s special element",
special_unit->special_unit_variety);
}
}
else
{
add_string (node_redirection_filename, &redirection_files);
}
}
if (add_translit_redirection && strcmp (normalized, "Top"))
{
/* based on converter.c node_information_filename */
int in_test = (self->conf->TEST.o.integer > 0);
char *translit_filename;
char *translit_basename
= normalize_transliterate_texinfo_contents (label_element,
in_test, in_test,
(self->conf->USE_UNIDECODE.o.integer == 0));
id_to_filename (self, &translit_basename);
if (strlen(added_translit_extension))
xasprintf (&translit_filename, "%s.%s",
translit_basename, added_translit_extension);
else
translit_filename = strdup (translit_basename);
free (translit_basename);
if (!node_redirection_filename)
{
size_t file_idx
= register_normalize_case_filename (self, node_filename);
const FILE_NAME_PATH_COUNTER *output_unit_file
= &self->output_unit_files.list[file_idx];
node_redirection_filename = output_unit_file->filename;
}
if (strcmp (translit_filename, node_redirection_filename)
&& strcmp (translit_filename, target_filename->filename))
{
size_t file_idx
= register_normalize_case_filename (self, translit_filename);
const FILE_NAME_PATH_COUNTER *output_unit_file
= &self->output_unit_files.list[file_idx];
char *translit_redirection_filename
= output_unit_file->filename;
int redirection_filename_total_count
= output_unit_file->elements_in_file_count;
FILE_SOURCE_INFO *file_source_info
= html_find_file_source_info (files_source_info,
translit_redirection_filename);
if (!(file_source_info
/* first condition finds conflict with tree elements */
&& (redirection_filename_total_count > 0
|| !strcmp (file_source_info->type,
"redirection"))))
{
add_string (translit_redirection_filename,
&redirection_files);
}
}
free (translit_filename);
}
free (node_filename);
for (j = 0; j < redirection_files.number; j++)
{
char *redirection_page;
char *out_filepath;
char *path_encoding;
char *open_error_message;
int overwritten_file;
int status;
char *redirection_filename = redirection_files.list[j];
html_add_to_files_source_info (files_source_info,
redirection_filename, "redirection", 0,
target_element, 0);
redirection_page
= html_prepare_node_redirection_page (self, target_element,
redirection_filename);
if (destination_directory && strlen (destination_directory))
{
xasprintf (&out_filepath, "%s/%s", destination_directory,
redirection_filename);
}
else
out_filepath = strdup (redirection_filename);
char *encoded_out_filepath
= converter_encoded_output_file_name (self->conf,
&self->document->global_info, out_filepath,
&path_encoding, 0);
/* overwritten_file being set cannot happen */
FILE *file_fh
= output_files_open_out (&self->output_files_information,
encoded_out_filepath, &open_error_message,
&overwritten_file, 0);
free (path_encoding);
status
= file_error_or_write_close (self, out_filepath,
encoded_out_filepath, file_fh,
conversion, redirection_page,
open_error_message);
free (encoded_out_filepath);
free (out_filepath);
free (redirection_page);
free (open_error_message);
/* NOTE failure to open a file does not stop the processing */
if (status == -1)
return -1;
else if (status >= 0)
redirection_files_done++;
}
clear_strings_list (&redirection_files);
}
free_strings_list (&redirection_files);
}
return redirection_files_done;
}
int
html_finish_output (CONVERTER *self, const char *output_file,
const char *destination_directory)
{
int finish_handler_status;
int handler_fatal_error_level
= self->conf->HANDLER_FATAL_ERROR_LEVEL.o.integer;
int node_redirections_status;
html_do_js_files (self);
finish_handler_status = html_run_stage_handlers (self, HSHT_type_finish);
if (finish_handler_status < handler_fatal_error_level
&& finish_handler_status > -handler_fatal_error_level)
{}
else
return 0;
node_redirections_status = html_node_redirections (self, output_file,
destination_directory);
if (node_redirections_status < 0)
return 0;
return 1;
}