| /* 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/>. */ |
| |
| /* functions in this file correspond to Texinfo::Convert::Converter */ |
| |
| #include <config.h> |
| |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdio.h> |
| #include <stddef.h> |
| #include <inttypes.h> |
| #include <unistr.h> |
| #include <unictype.h> |
| #include <errno.h> |
| /* mkdir stat */ |
| #include <sys/stat.h> |
| |
| #include "html_conversion_data.h" |
| #include "text.h" |
| #include "command_ids.h" |
| #include "element_types.h" |
| #include "types_data.h" |
| #include "tree_types.h" |
| #include "option_types.h" |
| #include "options_data.h" |
| #include "document_types.h" |
| #include "converter_types.h" |
| #include "options_defaults.h" |
| #include "converters_options.h" |
| /* fatal isascii_alnum isascii_alpha */ |
| #include "base_utils.h" |
| #include "tree.h" |
| #include "extra.h" |
| /* for ACCENTS_STACK find_innermost_accent_contents |
| fatal xasprintf texinfo_input_file_basename ... */ |
| #include "utils.h" |
| #include "customization_options.h" |
| #include "errors.h" |
| #include "builtin_commands.h" |
| /* also for cmd_text data */ |
| #include "convert_to_text.h" |
| /* for error message for debugging */ |
| /* convert_to_texinfo */ |
| #include "convert_to_texinfo.h" |
| #include "node_name_normalization.h" |
| /* cdt_tree expand_today free_tree_added_elements... */ |
| #include "convert_utils.h" |
| /* for NAMED_STRING_ELEMENT_LIST new_named_string_element_list ... */ |
| #include "translations.h" |
| #include "manipulate_tree.h" |
| #include "unicode.h" |
| #include "manipulate_indices.h" |
| #include "output_unit.h" |
| #include "document.h" |
| #include "api_to_perl.h" |
| #include "html_converter_api.h" |
| #include "plaintexinfo_converter_api.h" |
| #include "rawtext_converter_api.h" |
| #include "convert_indices.h" |
| #include "converter.h" |
| |
| /* table used to dispatch format specific functions. |
| Same purpose as inherited methods in Texinfo::Convert::Converter */ |
| /* Should be kept in sync with enum converter_format |
| and TXI_CONVERSION_FORMAT_NR */ |
| CONVERTER_FORMAT_DATA converter_format_data[] = { |
| {"html", "Texinfo::Convert::HTML", &html_format_setup, 0, |
| &html_converter_defaults, |
| &html_converter_initialize, &html_output, &html_convert, |
| &html_convert_tree, 0, &html_free_converter, &html_element_cdt_tree}, |
| {"rawtext", "Texinfo::Convert::Text", 0, &rawtext_converter, |
| 0, 0, &rawtext_output, |
| &rawtext_convert, &rawtext_convert_tree, 0, 0, 0}, |
| {"plaintexinfo", "Texinfo::Convert::PlainTexinfo", 0, 0, |
| &plaintexinfo_converter_defaults, 0, &plaintexinfo_output, |
| &plaintexinfo_convert, &plaintexinfo_convert_tree, 0, 0, 0}, |
| }; |
| |
| /* associate lower case no brace accent command to the upper case |
| corresponding commands */ |
| enum command_id no_brace_command_accent_upper_case[][2] = { |
| {CM_aa, CM_AA}, |
| {CM_ae, CM_AE}, |
| {CM_dh, CM_DH}, |
| {CM_l, CM_L}, |
| {CM_o, CM_O}, |
| {CM_oe, CM_OE}, |
| {CM_th, CM_TH}, |
| {0, 0}, |
| }; |
| |
| /* can be used in converters */ |
| enum command_id default_upper_case_commands[] = { |
| CM_sc, 0, |
| }; |
| |
| /* In sync with Convert/Converter.pm %xml_accent_entities and |
| %xml_accent_text_with_entities */ |
| COMMAND_ACCENT_ENTITY_INFO xml_accent_text_entities[] = { |
| {CM_DOUBLE_QUOTE, {"uml", "aeiouyAEIOU"}}, |
| {CM_TILDE, {"tilde", "nNaoAO"}}, |
| {CM_CIRCUMFLEX, {"circ", "aeiouAEIOU"}}, |
| {CM_BACKQUOTE, {"grave", "aeiouAEIOU"}}, |
| {CM_APOSTROPHE, {"acute", "aeiouyAEIOUY"}}, |
| {CM_COMMA, {"cedil", "cC"}}, |
| {CM_ringaccent, {"ring", "aA"}}, |
| {CM_ogonek, {"ogon", 0}}, |
| {CM_dotless, {"nodot", "i"}}, |
| {CM_dotaccent, {"dot", 0}}, |
| {CM_EQUALS, {"macr", 0}}, |
| {CM_u, {"breve", 0}}, |
| {CM_v, {"caron", 0}}, |
| {CM_H, {"dblac", 0}}, |
| {0, {0, 0}} |
| }; |
| |
| static CONVERTER **converter_list; |
| static size_t converter_number; |
| static size_t converter_space; |
| |
| const char *xml_text_entity_no_arg_commands_formatting[BUILTIN_CMD_NUMBER]; |
| |
| /* Needs to be called before initialization of HTML converter. */ |
| |
| void |
| setup_converter_generic (void) |
| { |
| int i; |
| /* conversion specific information */ |
| |
| for (i = 0; i < BUILTIN_CMD_NUMBER; i++) |
| { |
| if (xml_text_entity_no_arg_commands[i]) |
| xml_text_entity_no_arg_commands_formatting[i] |
| = xml_text_entity_no_arg_commands[i]; |
| else if (nobrace_symbol_text[i]) |
| xml_text_entity_no_arg_commands_formatting[i] = nobrace_symbol_text[i]; |
| else if (text_brace_no_arg_commands[i]) |
| xml_text_entity_no_arg_commands_formatting[i] |
| = text_brace_no_arg_commands[i]; |
| } |
| |
| /* For translation in document string. */ |
| if (0) |
| { |
| /* TRANSLATORS: expansion of @error{} as Texinfo code */ |
| (void) gdt_noop("error@arrow{}"); |
| } |
| } |
| |
| |
| |
| enum converter_format |
| find_format_name_converter_format (const char *format) |
| { |
| int i; |
| |
| for (i = 0; i < TXI_CONVERSION_FORMAT_NR; i++) |
| if (!strcmp (converter_format_data[i].default_format, format)) |
| return i; |
| |
| return COF_none; |
| } |
| |
| enum converter_format |
| find_perl_converter_class_converter_format (const char *class_name) |
| { |
| int i; |
| |
| if (class_name) |
| { |
| for (i = 0; i < TXI_CONVERSION_FORMAT_NR; i++) |
| if (!strcmp (converter_format_data[i].perl_converter_class, class_name)) |
| return i; |
| } |
| |
| return COF_none; |
| } |
| |
| /* Initialize converter data (independent of customization) */ |
| /* Corresponds to code run directly in the module namespace when doing |
| use/require $module in Perl */ |
| void |
| setup_converter_format (enum converter_format converter_format) |
| { |
| /* initialization of the library for the generic converter */ |
| setup_converter_generic (); |
| |
| if (converter_format != COF_none) |
| { |
| /* initialization of the library for a specific output format */ |
| if (converter_format_data[converter_format].format_setup) |
| { |
| void (* init_format_setup) (enum converter_format format) |
| = converter_format_data[converter_format].format_setup; |
| init_format_setup (converter_format); |
| } |
| } |
| } |
| |
| CONVERTER * |
| retrieve_converter (size_t converter_descriptor) |
| { |
| if (converter_descriptor <= converter_number |
| && converter_list[converter_descriptor -1] != 0) |
| return converter_list[converter_descriptor -1]; |
| return 0; |
| } |
| |
| static void |
| set_generic_converter_options (OPTIONS *options) |
| { |
| set_converter_cmdline_options_defaults (options); |
| set_converter_customization_options_defaults (options); |
| set_unique_at_command_options_defaults (options); |
| set_multiple_at_command_options_defaults (options); |
| set_converter_common_regular_options_defaults (options); |
| } |
| |
| /* to do as in Perl, if IF_SET_IN_LIST, only set options that have |
| OF_set_in_list set. Otherwise set all the options. |
| The case with IF_SET_IN_LIST set corresponds to the converter, |
| where only user-defined command options are set, the other case |
| corresponds to the HTML converter case, where all the options are |
| set. */ |
| /* NOTE the values set here are actually used only if set in |
| command_init, which is the only function that use these values, |
| so it could also have been possible to set the values only if |
| set. However, it is better to do as in Perl to avoid surprises. |
| */ |
| void |
| set_commands_options_value (COMMAND_OPTION_VALUE *commands_init_conf, |
| OPTION **sorted_options, int if_set_in_list) |
| { |
| size_t i; |
| |
| for (i = 0; i < TXI_COMMAND_OPTIONS_NR; i++) |
| { |
| const COMMAND_OPTION_NUMBER_CMD *option_nr_cmd |
| = &txi_options_command_map[i]; |
| const OPTION *option = sorted_options[option_nr_cmd->option_number -1]; |
| |
| if (if_set_in_list && !(option->flags & OF_set_in_list)) |
| continue; |
| |
| if (option->type == GOT_integer) |
| { |
| commands_init_conf[option_nr_cmd->cmd].type = option->type; |
| commands_init_conf[option_nr_cmd->cmd].v.value |
| = option->o.integer; |
| } |
| else if (option->type == GOT_char) |
| { |
| commands_init_conf[option_nr_cmd->cmd].type = option->type; |
| free (commands_init_conf[option_nr_cmd->cmd].v.string); |
| |
| if (option->o.string) |
| { |
| commands_init_conf[option_nr_cmd->cmd].v.string |
| = strdup (option->o.string); |
| } |
| else |
| commands_init_conf[option_nr_cmd->cmd].v.string = 0; |
| } |
| commands_init_conf[option_nr_cmd->cmd].set = 1; |
| } |
| } |
| |
| /* initialize the converter */ |
| /* corresponds to setting %all_converters_defaults in Perl */ |
| static void |
| init_generic_converter (CONVERTER *self) |
| { |
| self->conf = new_options (); |
| self->sorted_options = new_sorted_options (self->conf); |
| |
| set_generic_converter_options (self->conf); |
| |
| self->expanded_formats = new_expanded_formats (); |
| |
| /* set 'translated_commands' => {'error' => 'error@arrow{}',}, */ |
| |
| add_translated_command (&self->translated_commands, CM_error, |
| "error@arrow{}"); |
| } |
| |
| /* Allocate a converter without any initialization such as to leave |
| open the choice of functions used to setup the converter */ |
| /* descriptor starts at 1, 0 is not found or an error */ |
| size_t |
| new_converter (enum converter_format format) |
| { |
| size_t converter_index; |
| int slot_found = 0; |
| size_t i; |
| CONVERTER *converter; |
| |
| for (i = 0; i < converter_number; i++) |
| { |
| if (converter_list[i] == 0) |
| { |
| slot_found = 1; |
| converter_index = i; |
| } |
| } |
| if (!slot_found) |
| { |
| if (converter_number == converter_space) |
| { |
| converter_list = realloc (converter_list, |
| (converter_space += 5) * sizeof (CONVERTER *)); |
| if (!converter_list) |
| fatal ("realloc failed"); |
| } |
| converter_index = converter_number; |
| converter_number++; |
| } |
| converter = (CONVERTER *) malloc (sizeof (CONVERTER)); |
| memset (converter, 0, sizeof (CONVERTER)); |
| |
| converter->format = format; |
| |
| converter_list[converter_index] = converter; |
| converter->converter_descriptor = converter_index +1; |
| |
| /* |
| fprintf (stderr, "REGISTER CONVERTER %zu %d %p %p\n", converter_index +1, |
| format, converter, converter->document); |
| */ |
| return converter_index +1; |
| } |
| |
| /* apply initialization information from one source */ |
| void |
| apply_converter_info (CONVERTER *converter, |
| const CONVERTER_INITIALIZATION_INFO *init_info, int set_configured) |
| { |
| copy_options_list_set_configured (converter->conf, |
| converter->sorted_options, |
| &init_info->conf, set_configured); |
| |
| if (init_info->translated_commands.number) |
| { |
| clear_translated_commands (&converter->translated_commands); |
| copy_translated_commands (&converter->translated_commands, |
| &init_info->translated_commands); |
| } |
| |
| copy_deprecated_dirs (&converter->deprecated_config_directories, |
| &init_info->deprecated_config_directories); |
| } |
| |
| /* apply format_defaults and user_conf initialization information. |
| Corresponds to Perl _generic_converter_init. |
| */ |
| void |
| set_converter_init_information (CONVERTER *converter, |
| const CONVERTER_INITIALIZATION_INFO *format_defaults, |
| const CONVERTER_INITIALIZATION_INFO *user_conf) |
| { |
| init_generic_converter (converter); |
| |
| if (format_defaults) |
| { |
| apply_converter_info (converter, format_defaults, 0); |
| |
| /* Note that it is important to use format_defaults and not |
| converter similar data even though the options values are |
| copied to the converter, because the flags are not copied, and |
| are needed for set_commands_options_value with third argument |
| set */ |
| set_commands_options_value (converter->commands_init_conf, |
| format_defaults->conf.sorted_options, 1); |
| } |
| |
| if (user_conf) |
| { |
| apply_converter_info (converter, user_conf, 1); |
| |
| set_commands_options_value (converter->commands_init_conf, |
| user_conf->conf.sorted_options, 1); |
| } |
| |
| set_expanded_formats_from_options (converter->expanded_formats, |
| converter->conf); |
| |
| if (converter->conf->documentlanguage.o.string) |
| { |
| converter->current_lang_translations |
| = set_translations_documentlanguage (&converters_translation_cache, |
| converter->conf->documentlanguage.o.string, 0, |
| TXI_CONVERT_STRINGS_NR); |
| } |
| if (converter->conf->documentscript.o.string) |
| { |
| converter->current_lang_translations |
| = set_translations_documentscript (&converters_translation_cache, |
| converter->conf->documentscript.o.string, |
| converter->current_lang_translations, |
| TXI_CONVERT_STRINGS_NR); |
| } |
| |
| /* |
| fprintf (stderr, "C|CONVERTER Fill conf: %d; %s, %s\n", |
| converter->converter_descriptor, |
| converter->conf->TEXINFO_OUTPUT_FORMAT.o.string); |
| */ |
| } |
| |
| void |
| clear_converter_initialization_info (CONVERTER_INITIALIZATION_INFO *init_info) |
| { |
| clear_translated_commands (&init_info->translated_commands); |
| |
| clear_options_list (&init_info->conf); |
| |
| clear_deprecated_dirs_list (&init_info->deprecated_config_directories); |
| |
| clear_strings_list (&init_info->non_valid_customization); |
| } |
| |
| void |
| destroy_converter_initialization_info (CONVERTER_INITIALIZATION_INFO *init_info) |
| { |
| free_translated_commands (&init_info->translated_commands); |
| |
| free_options_list (&init_info->conf); |
| |
| free_deprecated_dirs_list (&init_info->deprecated_config_directories); |
| |
| free_strings_list (&init_info->non_valid_customization); |
| free (init_info); |
| } |
| |
| /* not used */ |
| void |
| copy_converter_initialization_info (CONVERTER_INITIALIZATION_INFO *dst_info, |
| const CONVERTER_INITIALIZATION_INFO *src_info) |
| { |
| copy_strings (&dst_info->non_valid_customization, |
| &src_info->non_valid_customization); |
| |
| copy_options_list (&dst_info->conf, &src_info->conf); |
| |
| if (src_info->translated_commands.number) |
| { |
| clear_translated_commands (&dst_info->translated_commands); |
| copy_translated_commands (&dst_info->translated_commands, |
| &src_info->translated_commands); |
| } |
| } |
| |
| /* corresponds to Perl $converter->converter_defaults() or |
| Texinfo::Convert:XXXX->converter_defaults() */ |
| CONVERTER_INITIALIZATION_INFO * |
| converter_defaults (enum converter_format converter_format, |
| const CONVERTER_INITIALIZATION_INFO *user_conf) |
| { |
| if (converter_format != COF_none) |
| { |
| if (converter_format_data[converter_format].converter_defaults) |
| { |
| CONVERTER_INITIALIZATION_INFO * |
| (* format_converter_defaults) (enum converter_format format, |
| const CONVERTER_INITIALIZATION_INFO *conf) |
| = converter_format_data[converter_format].converter_defaults; |
| return format_converter_defaults (converter_format, user_conf); |
| } |
| else |
| { /* Texinfo::Convert::Converter implementation */ |
| CONVERTER_INITIALIZATION_INFO *format_defaults |
| = new_converter_initialization_info (); |
| |
| add_converter_defaults_regular_options_defaults |
| (&format_defaults->conf); |
| return format_defaults; |
| } |
| } |
| return 0; |
| } |
| |
| /* Next four functions are not called from Perl as the Perl equivalent |
| functions are already called (and possibly overriden). Inheritance |
| in Perl is replaced by dispatching using a table here. |
| |
| converter_initialize cannot be overriden fully in HTML as long as Perl |
| code is needed to setup customization in Perl. Therefore, there is |
| no prospect of overriding converter_initialize for now, and therefore |
| of overridding converter_converter. Those functions are only meant |
| for pure C. |
| */ |
| /* corresponds to Perl $converter->converter_initialize() Converter */ |
| /* default is to do nothing */ |
| void |
| converter_initialize (CONVERTER *converter) |
| { |
| if (converter->format != COF_none |
| && converter_format_data[converter->format].converter_initialize) |
| { |
| void (* format_converter_initialize) (CONVERTER *self) |
| = converter_format_data[converter->format].converter_initialize; |
| format_converter_initialize (converter); |
| } |
| } |
| |
| /* Texinfo::Convert::XXXX->converter($conf) in Perl */ |
| /* only called from C, not from Perl */ |
| CONVERTER * |
| converter_converter (enum converter_format format, |
| const CONVERTER_INITIALIZATION_INFO *user_conf) |
| { |
| size_t converter_descriptor = new_converter (format); |
| CONVERTER *converter = retrieve_converter (converter_descriptor); |
| |
| if (converter->format != COF_none |
| && converter_format_data[converter->format].converter_converter) |
| {/* corresponds, in Perl, to a converter not inheriting |
| Texinfo::Convert::Converter, such as the Text converter to rawtext */ |
| void (* format_converter_converter) (CONVERTER *self, |
| const CONVERTER_INITIALIZATION_INFO *user_conf) |
| = converter_format_data[converter->format].converter_converter; |
| format_converter_converter (converter, user_conf); |
| } |
| else |
| { |
| CONVERTER_INITIALIZATION_INFO *format_defaults; |
| |
| format_defaults = converter_defaults (converter->format, user_conf); |
| |
| set_converter_init_information (converter, format_defaults, user_conf); |
| |
| destroy_converter_initialization_info (format_defaults); |
| |
| converter_initialize (converter); |
| } |
| return converter; |
| } |
| |
| char * |
| converter_output (CONVERTER *self, DOCUMENT *document) |
| { |
| enum converter_format converter_format = self->format; |
| |
| if (converter_format != COF_none |
| && converter_format_data[converter_format].converter_output) |
| { |
| char *result; |
| char * (* format_converter_output) (CONVERTER *self, |
| DOCUMENT *document) |
| = converter_format_data[converter_format].converter_output; |
| result = format_converter_output (self, document); |
| return result; |
| } |
| return 0; |
| } |
| |
| char * |
| converter_convert (CONVERTER *self, DOCUMENT *document) |
| { |
| enum converter_format converter_format = self->format; |
| |
| if (converter_format != COF_none |
| && converter_format_data[converter_format].converter_convert) |
| { |
| char *result; |
| char * (* format_converter_convert) (CONVERTER *self, |
| DOCUMENT *document) |
| = converter_format_data[converter_format].converter_convert; |
| result = format_converter_convert (self, document); |
| return result; |
| } |
| return 0; |
| } |
| |
| static void |
| destroy_converter_index_sorting (CONVERTER *converter) |
| { |
| if (converter->indices_sort_strings) |
| { |
| destroy_index_entries_sort_strings (converter->indices_sort_strings); |
| converter->indices_sort_strings = 0; |
| } |
| |
| if (converter->sorted_indices_by_index) |
| { |
| destroy_sorted_indices_by_index (converter->sorted_indices_by_index); |
| converter->sorted_indices_by_index = 0; |
| } |
| |
| if (converter->sorted_indices_by_letter) |
| { |
| destroy_sorted_indices_by_letter (converter->sorted_indices_by_letter); |
| converter->sorted_indices_by_letter = 0; |
| } |
| } |
| |
| void |
| converter_set_document (CONVERTER *converter, DOCUMENT *document) |
| { |
| /* |
| if (document) |
| { |
| fprintf (stderr, "C|CONVERTER %d: Document %d\n", |
| converter->converter_descriptor, document->descriptor); |
| } |
| */ |
| |
| /* |
| If there is already an associated document, reset information linked |
| to the document. |
| |
| We do not call destroy_converter_output_units because it should be the |
| caller responsibility to decide when the output units should be |
| destroyed. |
| |
| The output units will be freed if the document is destroyed anyway. |
| */ |
| if (converter->document) |
| { |
| int i; |
| for (i = 0; i < OUDT_external_nodes_units+1; i++) |
| converter->output_units_descriptors[i] = 0; |
| converter->document = 0; |
| } |
| |
| destroy_converter_index_sorting (converter); |
| |
| if (document) |
| { |
| converter->document = document; |
| |
| set_output_encoding (converter->conf, converter->document); |
| } |
| |
| if (converter->convert_text_options) |
| /* should only happen if a converter is reused */ |
| destroy_text_options (converter->convert_text_options); |
| |
| converter->convert_text_options |
| = copy_converter_options_for_convert_text (converter); |
| } |
| |
| /* default implementation used in converter_output_tree */ |
| void |
| converter_conversion_initialization (CONVERTER *converter, DOCUMENT *document) |
| { |
| converter_set_document (converter, document); |
| } |
| |
| /* output fo $fh if defined, otherwise return the text. */ |
| void |
| write_or_return (const ENCODING_CONVERSION *conversion, |
| const char *encoded_out_filepath, |
| FILE *file_fh, TEXT *result, char *text) |
| { |
| if (file_fh) |
| { |
| char *result; |
| size_t res_len; |
| size_t write_len; |
| |
| if (conversion) |
| { |
| result = encode_with_iconv (conversion->iconv, |
| text, 0, ieh_error, 0); |
| res_len = strlen (result); |
| } |
| else |
| { |
| result = text; |
| res_len = strlen (text); |
| } |
| 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); |
| } |
| } |
| else |
| text_append (result, text); |
| } |
| |
| char * |
| converter_output_tree (CONVERTER *converter, DOCUMENT *document, |
| void * (* conversion_initialization) |
| (CONVERTER *converter, DOCUMENT *document), |
| char * (* conversion_output_begin) |
| (CONVERTER *converter, |
| const char *output_file, const char *output_filename), |
| char * (* conversion_output_end) (CONVERTER *converter), |
| void * (* conversion_finalization) (CONVERTER *converter)) |
| { |
| int status = 1; |
| ELEMENT *root = document->tree; |
| int i; |
| char *paths[5]; |
| char *dir_encoding; |
| const char *output_file; |
| const char *destination_directory; |
| const char *output_filename; |
| FILE *file_fh = 0; |
| char *encoded_destination_directory; |
| int succeeded; |
| const ENCODING_CONVERSION *conversion = 0; |
| TEXT result; |
| char *encoded_out_filepath = 0; |
| char *tree_result; |
| |
| char *(* format_convert_tree) (CONVERTER *converter, |
| const ELEMENT *tree) |
| = converter_format_data[converter->format].converter_convert_tree; |
| |
| if (conversion_initialization) |
| { |
| conversion_initialization (converter, document); |
| } |
| else |
| converter_conversion_initialization (converter, document); |
| |
| determine_files_and_directory (converter, |
| converter->conf->TEXINFO_OUTPUT_FORMAT.o.string, paths); |
| |
| output_file = paths[0]; |
| destination_directory = paths[1]; |
| output_filename = paths[2]; |
| |
| /* 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 (converter->conf, |
| &converter->document->global_info, |
| (char *)destination_directory, |
| &dir_encoding, 0); |
| free (dir_encoding); |
| |
| succeeded = create_destination_directory (converter, |
| encoded_destination_directory, |
| destination_directory); |
| |
| free (encoded_destination_directory); |
| |
| if (!succeeded) |
| { |
| if (conversion_finalization) |
| conversion_finalization (converter); |
| status = 0; |
| goto finalization; |
| } |
| |
| if (strlen (output_file)) |
| { |
| char *path_encoding; |
| int overwritten_file; |
| char *open_error_message; |
| |
| encoded_out_filepath |
| = converter_encoded_output_file_name (converter->conf, |
| &converter->document->global_info, |
| (char *)output_file, &path_encoding, 0); |
| /* overwritten_file being set cannot happen */ |
| file_fh = output_files_open_out (&converter->output_files_information, |
| encoded_out_filepath, &open_error_message, |
| &overwritten_file, 0); |
| free (path_encoding); |
| |
| if (!file_fh) |
| { |
| message_list_document_error (&converter->error_messages, |
| converter->conf, 0, |
| "could not open %s for writing: %s", |
| output_file, open_error_message); |
| free (open_error_message); |
| free (encoded_out_filepath); |
| |
| if (conversion_finalization) |
| conversion_finalization (converter); |
| status = 0; |
| goto finalization; |
| } |
| } |
| |
| text_init (&result); |
| text_append (&result, ""); |
| |
| if (file_fh) |
| { |
| if (converter->conf->OUTPUT_ENCODING_NAME.o.string |
| && strcmp (converter->conf->OUTPUT_ENCODING_NAME.o.string, "utf-8")) |
| { |
| conversion |
| = get_encoding_conversion ( |
| converter->conf->OUTPUT_ENCODING_NAME.o.string, |
| &output_conversions); |
| } |
| } |
| |
| if (conversion_output_begin) |
| { |
| char *output_beginning = conversion_output_begin (converter, |
| output_file, |
| output_filename); |
| if (output_beginning) |
| { |
| write_or_return (conversion, encoded_out_filepath, |
| file_fh, &result, output_beginning); |
| free (output_beginning); |
| } |
| } |
| tree_result = format_convert_tree (converter, root); |
| if (tree_result) |
| { |
| write_or_return (conversion, encoded_out_filepath, |
| file_fh, &result, tree_result); |
| free (tree_result); |
| } |
| |
| if (conversion_output_end) |
| { |
| char *output_end = conversion_output_end (converter); |
| if (output_end) |
| { |
| write_or_return (conversion, encoded_out_filepath, |
| file_fh, &result, output_end); |
| free (output_end); |
| } |
| } |
| |
| /* 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 (file_fh && !strcmp (output_file, "-")) |
| { |
| output_files_register_closed |
| (&converter->output_files_information, |
| encoded_out_filepath); |
| if (fclose (file_fh)) |
| { |
| message_list_document_error ( |
| &converter->error_messages, converter->conf, 0, |
| "error on closing %s: %s", |
| output_file, strerror (errno)); |
| } |
| } |
| |
| if (encoded_out_filepath) |
| free (encoded_out_filepath); |
| |
| if (conversion_finalization) |
| conversion_finalization (converter); |
| |
| finalization: |
| |
| for (i = 0; i < 5; i++) |
| { |
| free (paths[i]); |
| } |
| |
| if (status) |
| return result.text; |
| else |
| { |
| free (result.text); |
| return 0; |
| } |
| } |
| |
| |
| |
| ELEMENT * |
| element_cdt_tree (const char *string, const ELEMENT *element, |
| CONVERTER *self, |
| NAMED_STRING_ELEMENT_LIST *replaced_substrings, |
| const char *translation_context) |
| { |
| const LANG_TRANSLATION *lang_translation; |
| const OPTIONS *options; |
| int debug_level = 0; |
| |
| if (self->format != COF_none |
| && converter_format_data[self->format].element_cdt_tree) |
| { |
| ELEMENT * (*element_cdt_tree_fn) (const char *string, const ELEMENT *element, |
| CONVERTER *self, |
| NAMED_STRING_ELEMENT_LIST *replaced_substrings, |
| const char *translation_context) |
| = converter_format_data[self->format].element_cdt_tree; |
| return (*element_cdt_tree_fn) (string, element, self, |
| replaced_substrings, |
| translation_context); |
| } |
| |
| lang_translation |
| = new_element_language_translation (&converters_translation_cache, |
| element, TXI_CONVERT_STRINGS_NR); |
| options = self->conf; |
| if (options && options->DEBUG.o.integer >= 0) |
| debug_level = options->DEBUG.o.integer; |
| |
| return gdt_tree (string, self->document, lang_translation, |
| replaced_substrings, debug_level, translation_context); |
| } |
| |
| |
| |
| |
| /* RESULT should be a char * array of dimension 5 */ |
| /* results to be freed by the caller */ |
| void |
| determine_files_and_directory (CONVERTER *self, const char *output_format, |
| char **result) |
| { |
| char *input_basename = 0; |
| char *input_basefile; |
| GLOBAL_INFO *document_info = 0; |
| GLOBAL_COMMANDS *global_commands = 0; |
| const char *setfilename = 0; |
| const char *setfilename_for_outfile = 0; |
| char *input_basename_for_outfile; |
| /* the document path, in general the outfile without |
| extension and can be set from setfilename if outfile is not set */ |
| char *document_path; |
| char *output_file; |
| const char *output_filepath; |
| char *document_name_and_directory[2]; |
| char *output_filename_and_directory[2]; |
| char *document_name; |
| char *output_filename; |
| char *destination_directory; |
| |
| if (self->document) |
| { |
| document_info = &self->document->global_info; |
| global_commands = &self->document->global_commands; |
| } |
| |
| if (document_info && document_info->input_file_name) |
| { |
| /* 'input_file_name' is not decoded, as it is derived from input |
| file which is not decoded either. We want to return only |
| decoded (utf-8) character strings such that they can easily be mixed |
| with other character strings, so we decode here. */ |
| const char *encoding = self->conf->COMMAND_LINE_ENCODING.o.string; |
| |
| if (encoding) |
| { |
| int status; |
| input_basefile = decode_string (document_info->input_file_name, |
| encoding, &status, 0); |
| } |
| else |
| input_basefile = strdup (document_info->input_file_name); |
| } |
| else /* This could happen if called on a piece of texinfo */ |
| input_basefile = strdup (""); |
| |
| if (!strcmp (input_basefile, "-")) |
| input_basename = strdup ("stdin"); |
| else |
| { |
| input_basename = texinfo_input_file_basename (input_basefile); |
| } |
| |
| if (self->conf->setfilename.o.string) |
| setfilename = self->conf->setfilename.o.string; |
| else if (global_commands && global_commands->setfilename) |
| { |
| enum command_id cmd; |
| setfilename |
| = informative_command_value (global_commands->setfilename, &cmd); |
| } |
| |
| /* PREFIX overrides both setfilename and the input file base name */ |
| if (self->conf->PREFIX.o.string) |
| { |
| setfilename_for_outfile = 0; |
| free (input_basename); |
| input_basename_for_outfile = strdup (self->conf->PREFIX.o.string); |
| } |
| else |
| { |
| input_basename_for_outfile = input_basename; |
| setfilename_for_outfile = setfilename; |
| } |
| |
| /* determine output file and output file name */ |
| if (!self->conf->OUTFILE.o.string) |
| { |
| if (setfilename_for_outfile) |
| { |
| document_path = remove_extension (setfilename_for_outfile); |
| |
| if (self->conf->USE_SETFILENAME_EXTENSION.o.integer <= 0) |
| { |
| if (self->conf->EXTENSION.o.string |
| && strlen (self->conf->EXTENSION.o.string)) |
| { |
| xasprintf (&output_file, "%s.%s", document_path, |
| self->conf->EXTENSION.o.string); |
| } |
| else |
| output_file = strdup (document_path); |
| } |
| else |
| output_file = strdup (setfilename_for_outfile); |
| } |
| else if (strlen (input_basename_for_outfile)) |
| { |
| document_path = strdup (input_basename_for_outfile); |
| if (self->conf->EXTENSION.o.string |
| && strlen (self->conf->EXTENSION.o.string)) |
| { |
| xasprintf (&output_file, "%s.%s", input_basename_for_outfile, |
| self->conf->EXTENSION.o.string); |
| } |
| else |
| output_file = strdup (input_basename_for_outfile); |
| } |
| else |
| { |
| output_file = strdup (""); |
| document_path = strdup (""); |
| } |
| if (self->conf->SUBDIR.o.string && strlen (output_file)) |
| { |
| char *new_output_file; |
| char *dir = file_separator_canonpath (self->conf->SUBDIR.o.string); |
| xasprintf (&new_output_file, "%s/%s", dir, output_file); |
| free (dir); |
| free (output_file); |
| output_file = new_output_file; |
| } |
| } |
| else |
| { |
| document_path = remove_extension (self->conf->OUTFILE.o.string); |
| output_file = strdup (self->conf->OUTFILE.o.string); |
| } |
| |
| free (input_basename_for_outfile); |
| |
| /* the output file path, output_filepath is in general the same as |
| the outfile but can be set from setfilename if outfile is not set. */ |
| if (!strlen (output_file) && setfilename_for_outfile) |
| { |
| /* in this case one wants to get the result in a string and there |
| is a setfilename. The setfilename is used to get something. |
| This happens in the test suite. */ |
| |
| output_filepath = setfilename_for_outfile; |
| free (document_path); |
| document_path = remove_extension (setfilename_for_outfile); |
| } |
| else |
| output_filepath = output_file; |
| |
| /* $document_name is the name of the document, which is the output |
| file basename, $output_filename, without extension. */ |
| |
| parse_file_path (document_path, document_name_and_directory); |
| free (document_path); |
| document_name = document_name_and_directory[0]; |
| free (document_name_and_directory[1]); |
| parse_file_path (output_filepath, output_filename_and_directory); |
| output_filename = output_filename_and_directory[0]; |
| free (output_filename_and_directory[1]); |
| |
| if (self->conf->SPLIT.o.string && strlen (self->conf->SPLIT.o.string)) |
| { |
| if (self->conf->OUTFILE.o.string) |
| destination_directory = strdup (self->conf->OUTFILE.o.string); |
| else if (self->conf->SUBDIR.o.string) |
| destination_directory = strdup (self->conf->SUBDIR.o.string); |
| else |
| { |
| if (output_format && strlen (output_format)) |
| xasprintf (&destination_directory, "%s_%s", document_name, |
| output_format); |
| else |
| destination_directory = strdup (document_name); |
| } |
| } |
| else |
| { |
| char *output_file_filename_and_directory[2]; |
| /* the filename is not used, but $output_filename should be |
| the same as long as $output_file is the same as $output_filepath |
| which is the case except if $output_file is ''. */ |
| parse_file_path (output_file, output_file_filename_and_directory); |
| destination_directory = output_file_filename_and_directory[1]; |
| /* Perl returns . or ./ if there is no directory */ |
| if (!destination_directory) |
| destination_directory = strdup ("."); |
| free (output_file_filename_and_directory[0]); |
| } |
| |
| if (strlen (destination_directory)) |
| { |
| char *new_destination_directory |
| = file_separator_canonpath (destination_directory); |
| free (destination_directory); |
| destination_directory = new_destination_directory; |
| } |
| |
| result[0] = output_file; |
| result[1] = destination_directory; |
| result[2] = output_filename; |
| result[3] = document_name; |
| result[4] = input_basefile; |
| } |
| |
| int |
| create_destination_directory (CONVERTER *self, |
| const char *destination_directory_path, |
| const char *destination_directory_name) |
| { |
| if (destination_directory_path) |
| { |
| struct stat finfo; |
| |
| if (stat (destination_directory_path, &finfo) != 0 |
| || !S_ISDIR (finfo.st_mode)) |
| { |
| int status = mkdir (destination_directory_path, S_IRWXU |
| | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); |
| if (status) |
| { |
| message_list_document_error (&self->error_messages, |
| self->conf, 0, |
| "could not create directory `%s': %s", |
| destination_directory_name, strerror (errno)); |
| return 0; |
| } |
| } |
| } |
| return 1; |
| } |
| |
| |
| |
| static const enum command_id conf_for_documentlanguage[] |
| = {CM_documentlanguage, CM_documentscript, 0}; |
| |
| const char * |
| current_bcp47_locale (CONVERTER *self) |
| { |
| if (!self->current_lang_translations) |
| return ""; |
| else |
| return self->current_lang_translations->info->bcp47_locale; |
| } |
| |
| void |
| set_converter_preamble_language_commands (CONVERTER *self) |
| { |
| if (self->document) |
| { |
| /* in general not needed as the values have not been reset when called |
| but it is clearer to mark that intent. */ |
| set_global_document_commands (self, CL_before, |
| conf_for_documentlanguage); |
| |
| self->current_lang_translations = |
| set_preamble_language_commands ( |
| &self->document->global_info.preamble_lang_cmd, |
| &converters_translation_cache, |
| self->conf->documentlanguage.o.string, |
| self->conf->documentscript.o.string, |
| TXI_CONVERT_STRINGS_NR); |
| } |
| } |
| |
| |
| |
| ELEMENT * |
| converter_expand_today (CONVERTER *converter, |
| ELEMENT * (*cdt_tree_fn) (const char *string, CONVERTER *self, |
| NAMED_STRING_ELEMENT_LIST *replaced_substrings, |
| const char *translation_context) |
| ) |
| { |
| int test = converter->conf->TEST.o.integer; |
| |
| return expand_today (test, converter->current_lang_translations, |
| converter->conf->DEBUG.o.integer, converter, cdt_tree_fn); |
| } |
| |
| ELEMENT * |
| converter_translated_command_tree (CONVERTER *self, enum command_id cmd, |
| ELEMENT * (*cdt_tree_fn) (const char *string, CONVERTER *self, |
| NAMED_STRING_ELEMENT_LIST *replaced_substrings, |
| const char *translation_context) |
| ) |
| { |
| return translated_command_tree (&self->translated_commands, cmd, |
| self->current_lang_translations, |
| self->conf->DEBUG.o.integer, |
| self, cdt_tree_fn); |
| } |
| |
| |
| |
| /* OF_configured is taken into account in option_set_conf */ |
| static void |
| copy_command_option_value_option (OPTION *option, |
| const COMMAND_OPTION_VALUE *cmd_option_value) |
| { |
| if (cmd_option_value->type == GOT_integer) |
| option_set_conf (option, cmd_option_value->v.value, 0); |
| else |
| option_set_conf (option, -1, cmd_option_value->v.string); |
| } |
| |
| /* freed by caller. Information in structure refers to other data, so |
| should not be freed */ |
| static COMMAND_OPTION_VALUE * |
| command_init (enum command_id cmd, COMMAND_OPTION_VALUE *commands_init_conf) |
| { |
| COMMAND_OPTION_VALUE *result = 0; |
| const OPTION *default_option; |
| /* always true currently, as it always comes from a converter and is |
| part of the converter */ |
| if (commands_init_conf) |
| { |
| const COMMAND_OPTION_VALUE *cmd_init_conf = &commands_init_conf[cmd]; |
| /* |
| if (cmd_init_conf->type == GOT_integer && cmd_init_conf->v.value >= 0 |
| || cmd_init_conf->type == GOT_char && cmd_init_conf->v.string) |
| */ |
| if (cmd_init_conf->set) |
| { |
| result = (COMMAND_OPTION_VALUE *) |
| malloc (sizeof (COMMAND_OPTION_VALUE)); |
| memcpy (result, &commands_init_conf[cmd], |
| sizeof (COMMAND_OPTION_VALUE)); |
| return result; |
| } |
| } |
| default_option = get_converter_command_option (txi_base_sorted_options, cmd); |
| if (default_option) |
| { |
| result = (COMMAND_OPTION_VALUE *) |
| malloc (sizeof (COMMAND_OPTION_VALUE)); |
| result->type = default_option->type; |
| if (default_option->type == GOT_integer) |
| result->v.value = default_option->o.integer; |
| else |
| result->v.string = default_option->o.string; |
| } |
| return result; |
| } |
| |
| void |
| set_global_document_commands (CONVERTER *converter, |
| const enum command_location location, |
| const enum command_id *cmd_list) |
| { |
| if (location == CL_before) |
| { |
| int i; |
| for (i = 0; cmd_list[i] > 0; i++) |
| { |
| enum command_id cmd = cmd_list[i]; |
| COMMAND_OPTION_VALUE *option_value = command_init (cmd, |
| converter->commands_init_conf); |
| if (option_value) |
| { |
| OPTION *option_ref |
| = get_converter_command_option (converter->sorted_options, cmd); |
| copy_command_option_value_option (option_ref, option_value); |
| free (option_value); |
| } |
| } |
| } |
| else |
| { |
| int i; |
| for (i = 0; cmd_list[i] > 0; i++) |
| { |
| const ELEMENT *element = 0; |
| enum command_id cmd = cmd_list[i]; |
| if (converter->conf->DEBUG.o.integer > 0) |
| { |
| fprintf (stderr, "C|SET_global(%s) %s\n", |
| command_location_names[location], |
| builtin_command_data[cmd].cmdname); |
| } |
| if (converter->document) |
| { |
| element |
| = set_global_document_command ( |
| &converter->document->global_commands, |
| converter->sorted_options, |
| cmd, location); |
| } |
| if (!element) |
| { |
| COMMAND_OPTION_VALUE *option_value = command_init (cmd, |
| converter->commands_init_conf); |
| if (option_value) |
| { |
| OPTION *option_ref |
| = get_converter_command_option (converter->sorted_options, |
| cmd); |
| copy_command_option_value_option (option_ref, option_value); |
| free (option_value); |
| } |
| } |
| } |
| } |
| } |
| |
| |
| |
| void |
| id_to_filename (CONVERTER *self, char **id_ref) |
| { |
| if (self->conf->BASEFILENAME_LENGTH.o.integer < 0) |
| return; |
| char *id = *id_ref; |
| if (strlen (id) > (size_t) self->conf->BASEFILENAME_LENGTH.o.integer) |
| { |
| id[self->conf->BASEFILENAME_LENGTH.o.integer] = '\0'; |
| } |
| } |
| |
| TARGET_FILENAME * |
| normalized_sectioning_command_filename (CONVERTER *self, const ELEMENT *command) |
| { |
| TARGET_FILENAME *result = new_target_filename (); |
| TEXT filename; |
| char *normalized_file_name; |
| char *normalized_name; |
| const ELEMENT *label_element; |
| |
| if (builtin_command_data[command->e.c->cmd].flags & CF_root) |
| /* for root level sectioning commands, the first element is the |
| arguments_line element, it contains the label element */ |
| label_element = command->e.c->contents.list[0]->e.c->contents.list[0]; |
| else |
| /* @*heading commands */ |
| label_element = command->e.c->contents.list[0]; |
| |
| if (self->conf->TRANSLITERATE_FILE_NAMES.o.integer > 0) |
| { |
| normalized_name |
| = normalize_transliterate_texinfo_contents (label_element); |
| } |
| else |
| { |
| normalized_name |
| = convert_contents_to_identifier (label_element); |
| } |
| |
| normalized_file_name = strdup (normalized_name); |
| id_to_filename (self, &normalized_file_name); |
| |
| text_init (&filename); |
| text_append (&filename, normalized_file_name); |
| if (self->conf->EXTENSION.o.string && strlen (self->conf->EXTENSION.o.string)) |
| { |
| text_append (&filename, "."); |
| text_append (&filename, self->conf->EXTENSION.o.string); |
| } |
| |
| free (normalized_file_name); |
| |
| result->filename = filename.text; |
| result->target = normalized_name; |
| |
| return result; |
| } |
| |
| char * |
| node_information_filename (CONVERTER *self, const char *normalized, |
| const ELEMENT *label_element) |
| { |
| char *filename; |
| |
| if (normalized) |
| { |
| if (self->conf->TRANSLITERATE_FILE_NAMES.o.integer > 0) |
| { |
| filename = normalize_transliterate_texinfo_contents (label_element); |
| } |
| else |
| filename = strdup (normalized); |
| } |
| else if (label_element) |
| { |
| filename = convert_contents_to_node_identifier (label_element); |
| } |
| else |
| filename = strdup (""); |
| |
| id_to_filename (self, &filename); |
| return filename; |
| } |
| |
| |
| |
| ELEMENT * |
| float_type_number (CONVERTER *self, const ELEMENT *float_e) |
| { |
| ELEMENT *tree = 0; |
| ELEMENT *type_element = 0; |
| NAMED_STRING_ELEMENT_LIST *replaced_substrings |
| = new_named_string_element_list (); |
| char *float_type = lookup_extra_string (float_e, AI_key_float_type); |
| char *float_number = lookup_extra_string (float_e, AI_key_float_number); |
| |
| if (float_type && strlen (float_type)) |
| /* first content of arguments_line type element */ |
| type_element = float_e->e.c->contents.list[0]->e.c->contents.list[0]; |
| |
| if (float_number) |
| { |
| ELEMENT *e_number = new_text_element (ET_normal_text); |
| text_append (e_number->e.text, float_number); |
| add_element_to_named_string_element_list (replaced_substrings, |
| "float_number", e_number); |
| } |
| |
| if (type_element) |
| { |
| ELEMENT *type_element_copy = copy_element_tree (type_element, 0); |
| add_element_to_named_string_element_list (replaced_substrings, |
| "float_type", type_element_copy); |
| if (float_number) |
| tree = cdt_tree ("{float_type} {float_number}", self, |
| replaced_substrings, 0); |
| else |
| tree = cdt_tree ("{float_type}", self, replaced_substrings, 0); |
| } |
| else if (float_number) |
| tree = cdt_tree ("{float_number}", self, replaced_substrings, 0); |
| |
| destroy_named_string_element_list (replaced_substrings); |
| |
| return tree; |
| } |
| |
| FLOAT_CAPTION_PREPENDED_ELEMENT * |
| float_name_caption (CONVERTER *self, const ELEMENT *float_e) |
| { |
| const ELEMENT *caption_element; |
| ELEMENT *prepended = 0; |
| ELEMENT *type_element = 0; |
| FLOAT_CAPTION_PREPENDED_ELEMENT *result = (FLOAT_CAPTION_PREPENDED_ELEMENT *) |
| malloc (sizeof (FLOAT_CAPTION_PREPENDED_ELEMENT)); |
| NAMED_STRING_ELEMENT_LIST *replaced_substrings |
| = new_named_string_element_list (); |
| |
| const char *float_type = lookup_extra_string (float_e, AI_key_float_type); |
| const char *float_number = lookup_extra_string (float_e, AI_key_float_number); |
| |
| const ELEMENT *caption_shortcaption[2]; |
| |
| find_float_caption_shortcaption(float_e, caption_shortcaption); |
| caption_element = caption_shortcaption[0]; |
| if (!caption_element) |
| caption_element = caption_shortcaption[1]; |
| |
| if (float_type && strlen (float_type)) |
| /* first content of arguments_line type element */ |
| type_element = float_e->e.c->contents.list[0]->e.c->contents.list[0]; |
| |
| if (float_number) |
| { |
| ELEMENT *e_number = new_text_element (ET_normal_text); |
| text_append (e_number->e.text, float_number); |
| add_element_to_named_string_element_list (replaced_substrings, |
| "float_number", e_number); |
| } |
| |
| if (type_element) |
| { |
| ELEMENT *type_element_copy = copy_element_tree (type_element, 0); |
| add_element_to_named_string_element_list (replaced_substrings, |
| "float_type", type_element_copy); |
| if (caption_element) |
| { |
| if (float_number) |
| /* TRANSLATORS: added before caption */ |
| prepended = cdt_tree ("{float_type} {float_number}: ", |
| self, replaced_substrings, 0); |
| else |
| /* TRANSLATORS: added before caption, no float label */ |
| prepended = cdt_tree ("{float_type}: ", self, |
| replaced_substrings, 0); |
| } |
| else |
| { |
| if (float_number) |
| prepended = cdt_tree ("{float_type} {float_number}", |
| self, replaced_substrings, 0); |
| else |
| prepended = cdt_tree ("{float_type}", self, |
| replaced_substrings, 0); |
| } |
| } |
| else if (float_number) |
| { |
| if (caption_element) |
| /* TRANSLATORS: added before caption, no float type */ |
| prepended = cdt_tree ("{float_number}: ", self, |
| replaced_substrings, 0); |
| else |
| prepended = cdt_tree ("{float_number}", self, |
| replaced_substrings, 0); |
| } |
| |
| result->caption = caption_element; |
| result->prepended = prepended; |
| |
| destroy_named_string_element_list (replaced_substrings); |
| |
| return result; |
| } |
| |
| |
| |
| char * |
| convert_accents (CONVERTER *self, const ELEMENT *accent, |
| char *(*convert_tree)(CONVERTER *self, const ELEMENT *tree), |
| char *(*format_accent)(CONVERTER *self, const char *text, const ELEMENT *element, |
| int index_in_stack, const ELEMENT_STACK *stack, |
| int set_case), |
| int output_encoded_characters, |
| int set_case) |
| { |
| ACCENTS_STACK *accent_stack = find_innermost_accent_contents (accent); |
| const ELEMENT_STACK *stack; |
| char *arg_text; |
| char *result; |
| int i; |
| |
| if (accent_stack->argument) |
| { |
| arg_text = (*convert_tree) (self, accent_stack->argument); |
| } |
| else |
| arg_text = strdup (""); |
| |
| if (output_encoded_characters) |
| { |
| char *encoded = encoded_accents (self, arg_text, &accent_stack->stack, |
| self->conf->OUTPUT_ENCODING_NAME.o.string, |
| format_accent, set_case); |
| if (encoded) |
| { |
| free (arg_text); |
| destroy_accent_stack (accent_stack); |
| return encoded; |
| } |
| } |
| |
| stack = &accent_stack->stack; |
| result = arg_text; |
| for (i = stack->top - 1; i >= 0; i--) |
| { |
| const ELEMENT *accent_command = stack->stack[i]; |
| char *formatted_accent = (*format_accent) (self, result, accent_command, |
| i, stack, set_case); |
| free (result); |
| result = formatted_accent; |
| } |
| destroy_accent_stack (accent_stack); |
| return result; |
| } |
| |
| static char * |
| lang_info_sorting_locale (const DOCUMENT_LANG_INFO *lang_info) |
| { |
| TEXT t_lang_sorting; |
| |
| text_init (&t_lang_sorting); |
| |
| text_append (&t_lang_sorting, lang_info->lang); |
| if (lang_info->script) |
| { |
| text_append_n (&t_lang_sorting, "_", 1); |
| text_append (&t_lang_sorting, lang_info->script); |
| } |
| |
| return t_lang_sorting.text; |
| } |
| |
| /* passed as a function reference */ |
| static char * |
| converter_index_entry_element_sort_string (const INDEX_ENTRY *main_entry, |
| const ELEMENT *index_entry_element, |
| TEXT_OPTIONS *options, int in_code, |
| int prefer_reference_element, |
| int debug_level, CONVERTER *converter) |
| { |
| char *sort_string; |
| ELEMENT *entry_tree_element; |
| |
| if (!index_entry_element) |
| { |
| fatal ("converter_index_entry_element_sort_string: NULL element"); |
| } |
| |
| char *sortas = lookup_extra_string (index_entry_element, AI_key_sortas); |
| if (sortas) |
| return strdup (sortas); |
| |
| entry_tree_element = converter_index_content_element (index_entry_element, |
| converter, |
| prefer_reference_element); |
| |
| sort_string = entry_tree_element_sort_string (main_entry, |
| entry_tree_element, options, in_code); |
| |
| return sort_string; |
| } |
| |
| static const INDICES_SORT_STRINGS * |
| converter_indices_sort_strings (CONVERTER *converter) |
| { |
| if (!converter->indices_sort_strings) |
| { |
| const MERGED_INDICES *merged_indices |
| = document_merged_indices (converter->document); |
| |
| converter->indices_sort_strings |
| = setup_index_entries_sort_strings (&converter->error_messages, |
| converter->conf, |
| merged_indices, &converter->document->indices_info, |
| 0, converter, |
| &converter_index_entry_element_sort_string); |
| |
| /* document->modified_information |= F_DOCM_indices_sort_strings; */ |
| } |
| return converter->indices_sort_strings; |
| } |
| |
| static COLLATION_INDICES_SORTED_BY_INDEX * |
| converter_sorted_indices_by_index (CONVERTER *converter, |
| int use_unicode_collation, |
| const char *input_lang_sorting_locale, |
| const char *collation_locale) |
| { |
| const char *lang_sorting_locale = 0; |
| COLLATIONS_INDICES_SORTED_BY_INDEX *collations; |
| COLLATION_INDICES_SORTED_BY_INDEX *collation_sorted_indices = 0; |
| |
| if (!converter->sorted_indices_by_index) |
| converter->sorted_indices_by_index |
| = new_base_collations_sorted_indices_by_index (); |
| |
| collations = converter->sorted_indices_by_index; |
| |
| collation_sorted_indices |
| = get_collation_sorted_indices_by_index (collations, |
| use_unicode_collation, |
| input_lang_sorting_locale, |
| collation_locale, |
| &lang_sorting_locale); |
| |
| if (!collation_sorted_indices->sorted_indices) |
| { |
| document_merged_indices (converter->document); |
| |
| const INDICES_SORT_STRINGS *indices_sort_strings |
| = converter_indices_sort_strings (converter); |
| |
| collation_sorted_indices->sorted_indices |
| = sort_indices_by_index (indices_sort_strings, |
| &converter->error_messages, |
| converter->conf, |
| use_unicode_collation, lang_sorting_locale, |
| collation_locale); |
| } |
| return collation_sorted_indices; |
| } |
| |
| INDEX_SORTED_BY_INDEX * |
| get_converter_indices_sorted_by_index (CONVERTER *self, char **language) |
| { |
| *language = 0; |
| if (self->document) |
| { |
| COLLATION_INDICES_SORTED_BY_INDEX *collation_sorted_indices; |
| char *lang_sorting_locale = 0; |
| |
| if (self->conf->USE_UNICODE_COLLATION.o.integer >= 0) |
| { |
| if (self->conf->COLLATION_LANGUAGE.o.string) |
| lang_sorting_locale |
| = strdup (self->conf->COLLATION_LANGUAGE.o.string); |
| else if (self->conf->DOCUMENTLANGUAGE_COLLATION.o.integer > 0 |
| && self->current_lang_translations) |
| lang_sorting_locale = lang_info_sorting_locale |
| (self->current_lang_translations->info); |
| } |
| |
| collation_sorted_indices |
| = converter_sorted_indices_by_index (self, |
| self->conf->USE_UNICODE_COLLATION.o.integer, |
| lang_sorting_locale, |
| self->conf->XS_STRXFRM_COLLATION_LOCALE.o.string); |
| |
| if (lang_sorting_locale) |
| free (lang_sorting_locale); |
| |
| if (collation_sorted_indices->type != ctn_locale_collation) |
| *language = collation_sorted_indices->language; |
| |
| return collation_sorted_indices->sorted_indices; |
| } |
| return 0; |
| } |
| |
| static COLLATION_INDICES_SORTED_BY_LETTER * |
| converter_sorted_indices_by_letter (CONVERTER *converter, |
| int use_unicode_collation, |
| const char *input_lang_sorting_locale, |
| const char *collation_locale) |
| { |
| const char *lang_sorting_locale = 0; |
| COLLATIONS_INDICES_SORTED_BY_LETTER *collations; |
| COLLATION_INDICES_SORTED_BY_LETTER *collation_sorted_indices = 0; |
| |
| if (!converter->sorted_indices_by_letter) |
| { |
| converter->sorted_indices_by_letter |
| = new_base_collations_sorted_indices_by_letter (); |
| } |
| |
| collations = converter->sorted_indices_by_letter; |
| |
| collation_sorted_indices |
| = get_collation_sorted_indices_by_letter (collations, |
| use_unicode_collation, |
| input_lang_sorting_locale, |
| collation_locale, |
| &lang_sorting_locale); |
| |
| if (!collation_sorted_indices->sorted_indices) |
| { |
| document_merged_indices (converter->document); |
| |
| const INDICES_SORT_STRINGS *indices_sort_strings |
| = converter_indices_sort_strings (converter); |
| |
| collation_sorted_indices->sorted_indices |
| = sort_indices_by_letter (indices_sort_strings, |
| &converter->error_messages, converter->conf, |
| use_unicode_collation, lang_sorting_locale, |
| collation_locale); |
| } |
| return collation_sorted_indices; |
| } |
| |
| INDEX_SORTED_BY_LETTER * |
| get_converter_indices_sorted_by_letter (CONVERTER *self, char **language) |
| { |
| *language = 0; |
| if (self->document) |
| { |
| COLLATION_INDICES_SORTED_BY_LETTER *collation_sorted_indices; |
| char *lang_sorting_locale = 0; |
| |
| if (self->conf->USE_UNICODE_COLLATION.o.integer >= 0) |
| { |
| if (self->conf->COLLATION_LANGUAGE.o.string) |
| lang_sorting_locale |
| = strdup (self->conf->COLLATION_LANGUAGE.o.string); |
| else if (self->conf->DOCUMENTLANGUAGE_COLLATION.o.integer > 0 |
| && self->current_lang_translations) |
| lang_sorting_locale = lang_info_sorting_locale |
| (self->current_lang_translations->info); |
| } |
| |
| collation_sorted_indices |
| = converter_sorted_indices_by_letter (self, |
| self->conf->USE_UNICODE_COLLATION.o.integer, |
| lang_sorting_locale, |
| self->conf->XS_STRXFRM_COLLATION_LOCALE.o.string); |
| |
| if (lang_sorting_locale) |
| free (lang_sorting_locale); |
| |
| if (collation_sorted_indices->type != ctn_locale_collation) |
| *language = collation_sorted_indices->language; |
| return collation_sorted_indices->sorted_indices; |
| } |
| return 0; |
| } |
| |
| /* to be freed by caller */ |
| char * |
| top_node_filename (const CONVERTER *self, const char *document_name) |
| { |
| TEXT top_node_filename; |
| |
| if (self->conf->TOP_FILE.o.string && strlen (self->conf->TOP_FILE.o.string)) |
| { |
| return strdup (self->conf->TOP_FILE.o.string); |
| } |
| |
| if (document_name) |
| { |
| text_init (&top_node_filename); |
| text_append (&top_node_filename, document_name); |
| if (self->conf->EXTENSION.o.string |
| && strlen (self->conf->EXTENSION.o.string)) |
| { |
| text_append (&top_node_filename, "."); |
| text_append (&top_node_filename, self->conf->EXTENSION.o.string); |
| } |
| return top_node_filename.text; |
| } |
| return 0; |
| } |
| |
| |
| |
| static size_t |
| find_output_unit_file (const CONVERTER *self, const char *filename, int *status) |
| { |
| const FILE_NAME_PATH_COUNTER_LIST *output_unit_files |
| = &self->output_unit_files; |
| size_t i; |
| *status = 0; |
| |
| for (i = 0; i < output_unit_files->number; i++) |
| { |
| if (!strcmp (output_unit_files->list[i].normalized_filename, filename)) |
| { |
| *status = 1; |
| return i; |
| } |
| } |
| return 0; |
| } |
| |
| static size_t |
| add_output_units_file (CONVERTER *self, const char *filename, |
| const char *normalized_filename) |
| { |
| size_t file_index; |
| FILE_NAME_PATH_COUNTER *new_output_unit_file; |
| FILE_NAME_PATH_COUNTER_LIST *output_unit_files |
| = &self->output_unit_files; |
| |
| if (output_unit_files->number == output_unit_files->space) |
| { |
| output_unit_files->list = realloc (output_unit_files->list, |
| (output_unit_files->space += 5) * sizeof (FILE_NAME_PATH_COUNTER)); |
| if (!output_unit_files->list) |
| fatal ("realloc failed"); |
| } |
| |
| file_index = output_unit_files->number; |
| new_output_unit_file = &output_unit_files->list[file_index]; |
| memset (new_output_unit_file, 0, sizeof (FILE_NAME_PATH_COUNTER)); |
| new_output_unit_file->filename = strdup (filename); |
| if (normalized_filename) |
| new_output_unit_file->normalized_filename = strdup (normalized_filename); |
| else |
| new_output_unit_file->normalized_filename = strdup (filename); |
| |
| output_unit_files->number++; |
| |
| return file_index; |
| } |
| |
| /* |
| If CASE_INSENSITIVE_FILENAMES is set, reuse the first |
| filename with the same name insensitive to the case. |
| */ |
| size_t |
| register_normalize_case_filename (CONVERTER *self, const char *filename) |
| { |
| size_t output_unit_file_idx; |
| if (self->conf->CASE_INSENSITIVE_FILENAMES.o.integer > 0) |
| { |
| char *lc_filename = to_upper_or_lower_multibyte (filename, -1); |
| int status; |
| output_unit_file_idx = find_output_unit_file (self, lc_filename, &status); |
| if (status) |
| { |
| if (self->conf->DEBUG.o.integer > 0) |
| { |
| FILE_NAME_PATH_COUNTER *output_unit_file |
| = &self->output_unit_files.list[output_unit_file_idx]; |
| fprintf (stderr, "C|Reusing case-insensitive %s for %s\n", |
| output_unit_file->filename, filename); |
| /* |
| fprintf (stderr, "C|Reusing case-insensitive %s(%zu) for %s\n", |
| output_unit_file->filename, output_unit_file_idx, |
| filename); |
| */ |
| } |
| free (lc_filename); |
| } |
| else |
| { |
| output_unit_file_idx = add_output_units_file (self, filename, |
| lc_filename); |
| free (lc_filename); |
| } |
| } |
| else |
| { |
| int status; |
| output_unit_file_idx = find_output_unit_file (self, filename, &status); |
| if (status) |
| { |
| if (self->conf->DEBUG.o.integer > 0) |
| { |
| FILE_NAME_PATH_COUNTER *output_unit_file |
| = &self->output_unit_files.list[output_unit_file_idx]; |
| fprintf (stderr, "C|Reusing %s for %s\n", |
| output_unit_file->filename, filename); |
| /* |
| fprintf (stderr, "C|Reusing %s(%zu) for %s\n", |
| output_unit_file->filename, output_unit_file_idx, |
| filename); |
| */ |
| } |
| } |
| else |
| output_unit_file_idx = add_output_units_file (self, filename, 0); |
| } |
| return output_unit_file_idx; |
| } |
| |
| size_t |
| set_output_unit_file (CONVERTER *self, OUTPUT_UNIT *output_unit, |
| const char *filename, int set_counter) |
| { |
| size_t output_unit_file_idx |
| = register_normalize_case_filename (self, filename); |
| FILE_NAME_PATH_COUNTER *output_unit_file |
| = &self->output_unit_files.list[output_unit_file_idx]; |
| if (set_counter) |
| output_unit_file->counter++; |
| free (output_unit->unit_filename); |
| output_unit->unit_filename = strdup (output_unit_file->filename); |
| return output_unit_file_idx; |
| } |
| |
| void |
| set_file_path (CONVERTER *self, const char *filename, const char *filepath, |
| const char *destination_directory) |
| { |
| size_t output_unit_file_idx |
| = register_normalize_case_filename (self, filename); |
| FILE_NAME_PATH_COUNTER *output_unit_file |
| = &self->output_unit_files.list[output_unit_file_idx]; |
| char *filepath_str; |
| |
| if (!filepath) |
| if (destination_directory && strlen (destination_directory)) |
| { |
| xasprintf (&filepath_str, "%s/%s", destination_directory, |
| output_unit_file->filename); |
| } |
| else |
| filepath_str = strdup (output_unit_file->filename); |
| else |
| filepath_str = strdup (filepath); |
| |
| if (output_unit_file->filepath) |
| { |
| if (!strcmp (output_unit_file->filepath, filepath_str)) |
| { |
| if (self->conf->DEBUG.o.integer > 0) |
| fprintf (stderr, "C|set_file_path: filepath set: %s\n", |
| filepath_str); |
| free (filepath_str); |
| } |
| else |
| { |
| if (self->conf->DEBUG.o.integer > 0) |
| fprintf (stderr, "C|set_file_path: filepath reset: %s, %s\n", |
| output_unit_file->filepath, filepath_str); |
| free (output_unit_file->filepath); |
| output_unit_file->filepath = filepath_str; |
| } |
| } |
| else |
| output_unit_file->filepath = filepath_str; |
| } |
| |
| static void |
| free_output_unit_files_file (FILE_NAME_PATH_COUNTER_LIST *output_unit_files) |
| { |
| size_t i; |
| for (i = 0; i < output_unit_files->number; i++) |
| { |
| FILE_NAME_PATH_COUNTER *output_unit_file = &output_unit_files->list[i]; |
| free (output_unit_file->filename); |
| free (output_unit_file->normalized_filename); |
| free (output_unit_file->filepath); |
| if (output_unit_file->body.space) |
| free (output_unit_file->body.text); |
| } |
| } |
| |
| void |
| clear_output_unit_files (FILE_NAME_PATH_COUNTER_LIST *output_unit_files) |
| { |
| free_output_unit_files_file (output_unit_files); |
| output_unit_files->number = 0; |
| } |
| |
| void |
| free_output_unit_files (FILE_NAME_PATH_COUNTER_LIST *output_unit_files) |
| { |
| free_output_unit_files_file (output_unit_files); |
| free (output_unit_files->list); |
| } |
| |
| |
| |
| static void |
| destroy_converter_output_units (CONVERTER *self) |
| { |
| int i; |
| int check_counts = (self->conf->TEST.o.integer > 1); |
| ERROR_MESSAGE_LIST *error_messages = 0; |
| |
| if (check_counts) |
| error_messages = set_check_element_interpreter_refcount (); |
| for (i = 0; i < OUDT_external_nodes_units+1; i++) |
| { |
| if (self->output_units_descriptors[i]) |
| { |
| OUTPUT_UNIT_LIST *output_unit_list |
| = retrieve_output_units (self->document, |
| self->output_units_descriptors[i]); |
| if (output_unit_list) |
| free_output_unit_list (output_unit_list); |
| self->output_units_descriptors[i] = 0; |
| } |
| } |
| if (check_counts) |
| { |
| if (error_messages->number) |
| merge_error_messages_lists (&self->error_messages, error_messages); |
| unset_check_element_interpreter_refcount (); |
| } |
| } |
| |
| static void |
| reset_tree_to_build (CONVERTER *self) |
| { |
| if (self->tree_to_build.number > 0) |
| { |
| fprintf (stderr, "BUG: tree_to_build: %zu\n", |
| self->tree_to_build.number); |
| if (self->conf->DEBUG.o.integer > 0) |
| { |
| size_t i; |
| for (i = 0; i < self->tree_to_build.number; i++) |
| { |
| ELEMENT *element = self->tree_to_build.list[i]; |
| /* in most cases, the trees have been destroyed, so this |
| will often segfault */ |
| fprintf (stderr, " %zu: '%s'\n", i, |
| convert_to_texinfo (element)); |
| } |
| } |
| } |
| self->tree_to_build.number = 0; |
| } |
| |
| void |
| converter_remove_output_units (CONVERTER *self) |
| { |
| enum converter_format converter_format = self->format; |
| |
| if (converter_format != COF_none |
| && converter_format_data[converter_format].converter_release_output_units) |
| { |
| void (* format_converter_release_output_units) (CONVERTER *self) |
| = converter_format_data[converter_format].converter_release_output_units; |
| format_converter_release_output_units (self); |
| } |
| |
| destroy_converter_output_units (self); |
| |
| /* HTML specific, but good to be here. |
| If there is still tree to build at this point, this means |
| will almost certainty that there is something wrong, as |
| the associated trees are most likely to have been destroyed |
| and having the output units is a sign that conversion data |
| should have been reset or could be reset at any time. |
| */ |
| reset_tree_to_build (self); |
| } |
| |
| void |
| free_generic_converter (CONVERTER *self) |
| { |
| size_t i; |
| |
| if (self->error_messages.number) |
| { |
| const char *converter_name; |
| if (self->format >= 0) |
| converter_name = converter_format_data[self->format].default_format; |
| else |
| converter_name = "generic"; |
| |
| fprintf (stderr, "BUG: %zu ignored messages in %s converter\n", |
| self->error_messages.number, converter_name); |
| for (i = 0; i < self->error_messages.number; i++) |
| { |
| const ERROR_MESSAGE *error_message = &self->error_messages.list[i]; |
| fprintf (stderr, " %zu: %s", i, error_message->error_line); |
| } |
| } |
| |
| free_translated_commands (&self->translated_commands); |
| |
| free_deprecated_dirs_list (&self->deprecated_config_directories); |
| |
| free (self->expanded_formats); |
| |
| for (i = 0; i < TXI_COMMAND_OPTIONS_NR; i++) |
| { |
| const COMMAND_OPTION_NUMBER_CMD *option_nr_cmd |
| = &txi_options_command_map[i]; |
| const OPTION *option |
| = self->sorted_options[option_nr_cmd->option_number -1]; |
| if (option->type == GOT_char) |
| { |
| free (self->commands_init_conf[option_nr_cmd->cmd].v.string); |
| } |
| } |
| |
| if (self->sorted_options) |
| { |
| size_t i; |
| for (i = 0; i < TXI_OPTIONS_NR; i++) |
| free_option (self->sorted_options[i]); |
| |
| free (self->sorted_options); |
| free (self->conf); |
| } |
| |
| destroy_converter_index_sorting (self); |
| |
| if (self->convert_index_text_options) |
| destroy_text_options (self->convert_index_text_options); |
| |
| free_output_files_information (&self->output_files_information); |
| free_output_unit_files (&self->output_unit_files); |
| |
| if (self->convert_text_options) |
| destroy_text_options (self->convert_text_options); |
| |
| wipe_error_message_list (&self->error_messages); |
| |
| free_strings_list (&self->small_strings); |
| |
| if (self->sv) |
| { |
| unregister_perl_data (self->sv); |
| if (0) |
| { |
| /* if an embedded Perl interpreter is used, the reference held by the |
| C code is the last (and only) reference, but when XS modules are used, |
| in general, there are converter hash reference variables, in all the |
| functions down to the main program. |
| |
| This can be used for debugging, but no generic check can be done. |
| */ |
| void *hv = get_sv_hv (self->sv); |
| int sv_refcount = get_refcount (self->sv); |
| int hv_refcount = get_refcount (hv); |
| if (sv_refcount != 0 || hv_refcount != 0) |
| { |
| fprintf (stderr, |
| "DEBUG Converter refcounts (%p<-%p): sv: %d hv: %d\n", |
| hv, self->sv, sv_refcount, hv_refcount); |
| } |
| } |
| } |
| } |
| |
| void |
| free_converter (CONVERTER *self) |
| { |
| enum converter_format converter_format = self->format; |
| |
| if (converter_format != COF_none |
| && converter_format_data[converter_format].converter_free) |
| { |
| void (* format_converter_free) (CONVERTER *self) |
| = converter_format_data[converter_format].converter_free; |
| format_converter_free (self); |
| } |
| |
| free_generic_converter (self); |
| } |
| |
| void |
| destroy_converter (CONVERTER *converter) |
| { |
| size_t converter_descriptor = converter->converter_descriptor; |
| |
| free_converter (converter); |
| |
| if (converter_descriptor) |
| converter_list[converter_descriptor-1] = 0; |
| |
| free (converter); |
| } |
| |
| |
| |
| /* XML conversion functions */ |
| |
| void |
| xml_format_text_with_numeric_entities (const char *text, TEXT *result) |
| { |
| const char *p; |
| |
| p = text; |
| while (*p) |
| { |
| int before_sep_nr = strcspn (p, "-'`"); |
| if (before_sep_nr) |
| { |
| text_append_n (result, p, before_sep_nr); |
| p += before_sep_nr; |
| } |
| if (!*p) |
| break; |
| switch (*p) |
| { |
| OTXI_NUMERIC_ENTITY_TEXT_CASES(p) |
| } |
| } |
| } |
| |
| |
| void |
| xml_protect_text (const char *text, TEXT *result) |
| { |
| const char *p; |
| |
| p = text; |
| |
| while (*p) |
| { |
| int before_sep_nr = strcspn (p, "<>&\""); |
| if (before_sep_nr) |
| { |
| text_append_n (result, p, before_sep_nr); |
| p += before_sep_nr; |
| } |
| if (!*p) |
| break; |
| switch (*p) |
| { |
| OTXI_PROTECT_XML_CASES(p); |
| /* should never happen */ |
| default: |
| p++; |
| } |
| } |
| } |
| |
| static char * |
| next_for_tieaccent (const char *text, const char **next) |
| { |
| const char *p; |
| if (!strlen (text)) |
| { |
| return 0; |
| } |
| if (text[0] == '&') |
| { |
| if (strlen (text) > 3 && isascii_alnum (*(text+1))) |
| { |
| p = text +2; |
| while (*p) |
| { |
| if (*p == ';') |
| { |
| p++; |
| *next = p; |
| return strndup (text, p - text); |
| } |
| else if (isascii_alnum (*p)) |
| { |
| p++; |
| } |
| else |
| break; |
| } |
| } |
| return 0; |
| } |
| else |
| { |
| uint8_t *encoded_u8 = utf8_from_string (text); |
| ucs4_t first_char; |
| u8_next (&first_char, encoded_u8); |
| free (encoded_u8); |
| if (uc_is_general_category (first_char, UC_CATEGORY_L) |
| /* ASCII digits */ |
| || (first_char >= 0x0030 && first_char <= 0x0039)) |
| { |
| char *first_char_text; |
| uint8_t *first_char_u8 = malloc (7 * sizeof (uint8_t)); |
| int first_char_len = u8_uctomb (first_char_u8, first_char, 6); |
| if (first_char_len < 0) |
| fatal ("u8_uctomb returns negative value"); |
| first_char_u8[first_char_len] = 0; |
| first_char_text = string_from_utf8 (first_char_u8); |
| free (first_char_u8); |
| p = text + strlen (first_char_text); |
| *next = p; |
| return first_char_text; |
| } |
| return 0; |
| } |
| } |
| |
| typedef struct UNICODE_ACCENT_LETTER { |
| enum command_id cmd; |
| char *letter; |
| char *numerical_entity; |
| } UNICODE_ACCENT_LETTER; |
| |
| /* only those that are not obtained through diacritic + normalization */ |
| static UNICODE_ACCENT_LETTER unicode_accented_letters[] = { |
| {CM_dotless, "i", "305"}, |
| {CM_dotless, "j", "567"}, |
| {0, 0, 0} |
| }; |
| |
| char * |
| xml_numeric_entity_accent (enum command_id cmd, const char *text) |
| { |
| char *result; |
| |
| if (unicode_diacritics[cmd].text) |
| { |
| if (cmd != CM_tieaccent) |
| { |
| if (strlen (text) == 1 && isascii_alpha (*text)) |
| { |
| char *accented_char; |
| char *normalized_char; |
| uint8_t *encoded_u8; |
| ucs4_t first_char; |
| const uint8_t *next; |
| |
| xasprintf (&accented_char, "%s%s", text, |
| unicode_diacritics[cmd].text); |
| normalized_char = normalize_NFC (accented_char); |
| encoded_u8 = utf8_from_string (normalized_char); |
| next = u8_next (&first_char, encoded_u8); |
| if (next) |
| { |
| ucs4_t other_char; |
| const uint8_t *after = u8_next (&other_char, next); |
| next = after; |
| } |
| free (encoded_u8); |
| free (accented_char); |
| free (normalized_char); |
| if (!next) |
| { |
| char *entity; |
| /* hex entity |
| xasprintf (&entity, "&#%04lX;", first_char); */ |
| /* seems to be the way for portable uint32_t unsigned integer format */ |
| xasprintf (&entity, "&#%" PRIu32 ";", first_char); |
| return entity; |
| } |
| } |
| xasprintf (&result, "%s&#%s;", text, unicode_diacritics[cmd].codepoint); |
| return result; |
| } |
| else |
| { |
| char *result; |
| const char *p = 0; |
| const char *remaining = 0; |
| char *first = next_for_tieaccent (text, &p); |
| char *second; |
| if (!first) |
| goto invalid; |
| second = next_for_tieaccent (p, &remaining); |
| if (second) |
| { |
| xasprintf (&result, "%s&#%s;%s%s", first, |
| unicode_diacritics[cmd].codepoint, second, remaining); |
| free (first); |
| free (second); |
| return result; |
| } |
| else |
| free (first); |
| |
| invalid: |
| xasprintf (&result, "%s&#%s;", text, |
| unicode_diacritics[cmd].codepoint); |
| return result; |
| } |
| } |
| else if (strlen (text) == 1 && isascii_alpha (*text)) |
| { |
| int i; |
| for (i = 0; unicode_accented_letters[i].cmd; i++) |
| { |
| UNICODE_ACCENT_LETTER *letter = &unicode_accented_letters[i]; |
| if (cmd == letter->cmd && ! strcmp (text, letter->letter)) |
| { |
| xasprintf (&result, "&#%s;", letter->numerical_entity); |
| return result; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* return to be freed by the caller */ |
| char * |
| xml_comment (CONVERTER *converter, const char *text) |
| { |
| const char *p = text; |
| |
| TEXT result; |
| |
| text_init (&result); |
| text_append_n (&result, "<!--", 4); |
| while (*p) |
| { |
| char *q = strchr (p, '-'); |
| if (q) |
| { |
| if (q - p) |
| text_append_n (&result, p, q +1 - p); |
| p = q + 1; |
| p += strspn (p, "-"); |
| } |
| else |
| { |
| text_append (&result, p); |
| break; |
| } |
| } |
| if (result.end > 0 && result.text[result.end - 1] == '\n') |
| { |
| result.end--; |
| } |
| text_append_n (&result, " -->\n", 5); |
| return result.text; |
| } |