| /* Copyright 2010-2026 Free Software Foundation, Inc. |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program. If not, see <https://www.gnu.org/licenses/>. */ |
| |
| #include <config.h> |
| |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdio.h> |
| #include <locale.h> |
| #include <ctype.h> |
| #include "unictype.h" |
| #include "unistr.h" |
| |
| #include "text.h" |
| #include "command_ids.h" |
| #include "element_types.h" |
| #include "types_data.h" |
| #include "tree_types.h" |
| #include "document_types.h" |
| #include "converter_types.h" |
| #include "base_utils.h" |
| #include "tree.h" |
| #include "extra.h" |
| #include "builtin_commands.h" |
| /* whitespace_chars ultimate_index indices_info_index_by_name |
| to_upper_or_lower_multibyte */ |
| #include "utils.h" |
| #include "translations.h" |
| #include "manipulate_tree.h" |
| #include "errors.h" |
| #include "debug.h" |
| #include "unicode.h" |
| #include "document.h" |
| #include "convert_to_text.h" |
| #include "convert_to_texinfo.h" |
| #include "call_perl_function.h" |
| #include "api_to_perl.h" |
| #include "unicode-collation/collation_key.h" |
| #include "manipulate_indices.h" |
| |
| /* corresponding perl code in Texinfo::Indices */ |
| |
| MERGED_INDICES * |
| merge_indices (INDEX_LIST *indices_information) |
| { |
| size_t merged_indices_space = 4; |
| size_t merged_indices_number = 0; |
| size_t i; |
| |
| if (!indices_information->number) |
| return 0; |
| |
| MERGED_INDEX *merged_indices_list |
| = (MERGED_INDEX *) malloc (sizeof (MERGED_INDEX) * merged_indices_space); |
| |
| MERGED_INDICES *merged_indices |
| = (MERGED_INDICES *) malloc (sizeof (MERGED_INDICES)); |
| |
| for (i = 0; i < indices_information->number; i++) |
| { |
| INDEX *idx = indices_information->list[i]; |
| if (idx->index_entries && idx->entries_number) |
| { |
| size_t j; |
| INDEX *ultimate_idx; |
| char *in_idx_name; |
| MERGED_INDEX *merged_idx = 0; |
| |
| if (idx->merged_in) |
| ultimate_idx = ultimate_index (idx); |
| else |
| ultimate_idx = idx; |
| |
| in_idx_name = ultimate_idx->name; |
| |
| for (j = 0; j < merged_indices_number; j++) |
| { |
| if (!strcmp (merged_indices_list[j].name, in_idx_name)) |
| { |
| merged_idx = &merged_indices_list[j]; |
| break; |
| } |
| } |
| if (!merged_idx) |
| /* main index (possibly idx) not already setup, do it */ |
| { |
| if (merged_indices_number == merged_indices_space) |
| { |
| merged_indices_space += 5; |
| merged_indices_list |
| = realloc (merged_indices_list, merged_indices_space |
| * sizeof (MERGED_INDEX)); |
| } |
| merged_idx = &merged_indices_list[merged_indices_number]; |
| merged_idx->name = in_idx_name; |
| merged_idx->entries_number = ultimate_idx->entries_number; |
| merged_idx->index_entries |
| = malloc (sizeof (INDEX_ENTRY) * merged_idx->entries_number); |
| memcpy (merged_idx->index_entries, ultimate_idx->index_entries, |
| ultimate_idx->entries_number * sizeof (INDEX_ENTRY)); |
| merged_indices_number++; |
| } |
| |
| if (idx->merged_in) |
| { |
| merged_idx->index_entries = realloc (merged_idx->index_entries, |
| (idx->entries_number + merged_idx->entries_number) * |
| sizeof (INDEX_ENTRY)); |
| memcpy (merged_idx->index_entries + merged_idx->entries_number, |
| idx->index_entries, |
| idx->entries_number * sizeof (INDEX_ENTRY)); |
| merged_idx->entries_number += idx->entries_number; |
| } |
| } |
| } |
| /* set to the final size, including a trailing MERGED_INDEX filled with 0 */ |
| merged_indices_list = realloc (merged_indices_list, |
| (merged_indices_number +1) * sizeof (MERGED_INDEX)); |
| memset (&merged_indices_list[merged_indices_number], 0, |
| sizeof (MERGED_INDEX)); |
| |
| merged_indices->indices = merged_indices_list; |
| merged_indices->number = merged_indices_number; |
| |
| return merged_indices; |
| } |
| |
| void |
| destroy_merged_indices (MERGED_INDICES *merged_indices) |
| { |
| size_t i; |
| |
| for (i = 0; i < merged_indices->number; i++) |
| { |
| free (merged_indices->indices[i].index_entries); |
| } |
| free (merged_indices->indices); |
| free (merged_indices); |
| } |
| |
| |
| |
| /* indices entries sorting */ |
| |
| void |
| destroy_indices_sorted_by_index ( |
| INDEX_SORTED_BY_INDEX *indices_entries_by_index) |
| { |
| INDEX_SORTED_BY_INDEX *index; |
| |
| for (index = indices_entries_by_index; index->name; index++) |
| { |
| free (index->name); |
| free (index->entries); |
| } |
| free (indices_entries_by_index); |
| } |
| |
| void |
| destroy_indices_sorted_by_letter ( |
| INDEX_SORTED_BY_LETTER *indices_entries_by_letter) |
| { |
| INDEX_SORTED_BY_LETTER *index; |
| |
| for (index = indices_entries_by_letter; index->name; index++) |
| { |
| size_t i; |
| free (index->name); |
| for (i = 0; i < index->letter_number; i++) |
| { |
| LETTER_INDEX_ENTRIES *letter_entries = &index->letter_entries[i]; |
| free (letter_entries->letter); |
| free (letter_entries->entries); |
| } |
| free (index->letter_entries); |
| } |
| free (indices_entries_by_letter); |
| } |
| |
| static void |
| remove_def_types (ELEMENT *element) |
| { |
| element->type = ET_NONE; |
| |
| if (element->e.c->contents.number |
| && element->e.c->contents.list[0]->type == ET_bracketed_arg) |
| element->e.c->contents.list[0]->type = ET_brace_arg; |
| } |
| |
| static void |
| unexpected_def_name_class_message (enum command_id def_command) |
| { |
| char *msg; |
| /* set index_entry to a value to avoid a compiler warning |
| on uninitialized value. Incorrect for the code below, but |
| we do not care as fatal is called. */ |
| xasprintf (&msg, "BUG: unexpected def command with name " |
| "and class: %s", |
| builtin_command_name (def_command)); |
| fatal (msg); |
| free (msg); |
| } |
| |
| /* return index content element if there is no need for translation, |
| otherwise return information for translation in DEF_COMMAND_OUT, |
| NAME_COPY_OUT and CLASS_COPY_OUT. |
| */ |
| ELEMENT * |
| get_index_content_info_element (const ELEMENT *element, |
| int prefer_reference_element, |
| enum command_id *def_command_out, |
| ELEMENT **name_copy_out, |
| ELEMENT **class_copy_out) |
| { |
| ELEMENT *name = 0; |
| ELEMENT *class = 0; |
| const char *def_cmdname = lookup_extra_string (element, AI_key_def_command); |
| ELEMENT *def_l_e; |
| |
| if (!def_cmdname) |
| { |
| ELEMENT *element_copy |
| = copy_element_tree (element->e.c->contents.list[0], 0); |
| return element_copy; |
| } |
| |
| def_l_e = element->e.c->contents.list[0]; |
| if (def_l_e->e.c->contents.number > 0) |
| { |
| size_t ic; |
| for (ic = 0; ic < def_l_e->e.c->contents.number; ic++) |
| { |
| ELEMENT *arg = def_l_e->e.c->contents.list[ic]; |
| if (arg->type == ET_def_name) |
| name = arg; |
| else if (arg->type == ET_def_class) |
| class = arg; |
| else if (arg->type == ET_def_arg |
| || arg->type == ET_def_typearg |
| || arg->type == ET_delimiter) |
| break; |
| } |
| } |
| |
| if (name) |
| { |
| enum command_id def_command |
| = lookup_builtin_command (def_cmdname); |
| ELEMENT *name_copy = copy_element_tree (name, 0); |
| remove_def_types (name_copy); |
| if (!(builtin_command_data[def_command].flags & CF_def_class_method) |
| && !(builtin_command_data[def_command].flags |
| & CF_def_class_variable)) |
| { |
| return name_copy; |
| } |
| else |
| { |
| ELEMENT *class_copy = copy_element_tree (class, 0); |
| remove_def_types (class_copy); |
| |
| if (prefer_reference_element) |
| { |
| ELEMENT *text_element = new_text_element (ET_normal_text); |
| /* container without type */ |
| ELEMENT *index_entry_normalized = new_element (ET_NONE); |
| |
| if (builtin_command_data[def_command].flags |
| & CF_def_class_method) |
| text_append (text_element->e.text, " on "); |
| else if (builtin_command_data[def_command].flags |
| & CF_def_class_variable) |
| text_append (text_element->e.text, " of "); |
| /* should not be possible, still considered for more robust code */ |
| else |
| unexpected_def_name_class_message (def_command); |
| |
| add_to_contents_as_array (index_entry_normalized, name_copy); |
| add_to_contents_as_array (index_entry_normalized, text_element); |
| add_to_contents_as_array (index_entry_normalized, class_copy); |
| return index_entry_normalized; |
| } |
| else |
| { |
| *name_copy_out = name_copy; |
| *class_copy_out = class_copy; |
| *def_command_out = def_command; |
| return 0; |
| } |
| } |
| } |
| *name_copy_out = 0; |
| *class_copy_out = 0; |
| *def_command_out = CM_NONE; |
| return 0; |
| } |
| |
| /* It would have been better to return a const element, as the calling codes |
| are not supposed to modify the tree, however in the calling code the |
| elements are put in arrays of non-const elements, even though they are not |
| modified */ |
| ELEMENT * |
| document_index_content_element (const ELEMENT *element, |
| int prefer_reference_element, |
| DOCUMENT *document, int debug_level) |
| { |
| enum command_id def_command; |
| ELEMENT *class_copy; |
| ELEMENT *name_copy; |
| NAMED_STRING_ELEMENT_LIST *substrings; |
| const LANG_TRANSLATION *element_lang_translations; |
| |
| ELEMENT *index_element |
| = get_index_content_info_element (element, prefer_reference_element, |
| &def_command, &name_copy, &class_copy); |
| |
| if (index_element || !class_copy) |
| return index_element; |
| |
| substrings = new_named_string_element_list (); |
| add_element_to_named_string_element_list (substrings, |
| "name", name_copy); |
| add_element_to_named_string_element_list (substrings, |
| "class", class_copy); |
| |
| element_lang_translations |
| = new_element_language_translation (&converters_translation_cache, |
| element, TXI_CONVERT_STRINGS_NR); |
| |
| if (builtin_command_data[def_command].flags & CF_def_class_method) |
| { |
| index_element = gdt_tree ("{name} on {class}", |
| document, element_lang_translations, |
| substrings, debug_level, 0); |
| } |
| else if (builtin_command_data[def_command].flags & CF_def_class_variable) |
| { |
| index_element = gdt_tree ("{name} of {class}", |
| document, element_lang_translations, |
| substrings, debug_level, 0); |
| } |
| /* should not be possible, still considered for more robust code */ |
| else |
| unexpected_def_name_class_message (def_command); |
| |
| destroy_named_string_element_list (substrings); |
| |
| /* prefer a type-less container rather than 'root_line' returned by gdt */ |
| index_element->type = ET_NONE; |
| |
| return index_element; |
| } |
| |
| char * |
| strip_index_ignore_chars (const char *string, const char *index_ignore_chars) |
| { |
| TEXT result_text; |
| const char *p = string; |
| text_init (&result_text); |
| /* in particular to be consistent with convert_to_text, which returned |
| variable is never NUL */ |
| text_append (&result_text, ""); |
| |
| while (*p) |
| { |
| int n = strspn (p, index_ignore_chars); |
| if (n) |
| { |
| p += n; |
| } |
| if (*p) |
| { |
| /* store a character */ |
| int char_len = 1; |
| while ((p[char_len] & 0xC0) == 0x80) |
| char_len++; |
| text_append_n (&result_text, p, char_len); |
| p += char_len; |
| } |
| } |
| return result_text.text; |
| } |
| |
| /* corresponding perl code in Texinfo::Indices */ |
| |
| char * |
| entry_tree_element_sort_string (const INDEX_ENTRY *main_entry, |
| ELEMENT *entry_tree_element, |
| TEXT_OPTIONS *options, int in_code) |
| { |
| char *sort_string; |
| const char *index_ignore_chars; |
| |
| if (in_code) |
| options->code_state++; |
| sort_string = convert_to_text (entry_tree_element, options); |
| |
| destroy_element_and_children (entry_tree_element); |
| |
| if (in_code) |
| options->code_state--; |
| |
| index_ignore_chars = lookup_extra_string (main_entry->entry_element, |
| AI_key_index_ignore_chars); |
| if (index_ignore_chars) |
| { |
| char *sort_string_text = strip_index_ignore_chars (sort_string, |
| index_ignore_chars); |
| free (sort_string); |
| sort_string = sort_string_text; |
| } |
| return sort_string; |
| } |
| |
| /* FIXME taking the document from options is not intuitive. Pass |
| and explicit document? Similarly, make sure that a correctly |
| set debug_level is set, instead of using options->DEBUG? */ |
| char * |
| 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; |
| int used_debug_level; |
| |
| if (!index_entry_element) |
| { |
| fatal ("index_entry_element_sort_string: NULL element"); |
| } |
| |
| char *sortas = lookup_extra_string (index_entry_element, AI_key_sortas); |
| if (sortas) |
| return strdup (sortas); |
| |
| if (debug_level < 0) |
| used_debug_level = options->DEBUG; |
| else |
| used_debug_level = debug_level; |
| |
| entry_tree_element = document_index_content_element (index_entry_element, |
| prefer_reference_element, |
| options->document, |
| used_debug_level); |
| |
| sort_string = entry_tree_element_sort_string (main_entry, |
| entry_tree_element, options, in_code); |
| |
| return sort_string; |
| } |
| |
| |
| typedef struct INDEX_COLLATOR { |
| enum collation_type_name type; |
| union { |
| /* perl element. This should be SV *sv, |
| but we don't want to include the Perl headers everywhere; */ |
| /* not const because of refcount increase/decrease */ |
| void *sv; |
| #ifdef HAVE_NEWLOCALE |
| locale_t locale; |
| #endif |
| } coll; |
| } INDEX_COLLATOR; |
| |
| static BYTES_STRING * |
| get_sort_key (const INDEX_COLLATOR *collator, const char *sort_string) |
| { |
| BYTES_STRING *sort_key; |
| |
| switch (collator->type) |
| { |
| case ctn_no_unicode: |
| sort_key = (BYTES_STRING *) malloc (sizeof (BYTES_STRING)); |
| sort_key->len = strlen (sort_string); |
| sort_key->bytes = (unsigned char *) |
| malloc (sizeof (unsigned char) * sort_key->len); |
| memcpy (sort_key->bytes, (unsigned char *) sort_string, |
| sort_key->len); |
| break; |
| #ifdef HAVE_STRXFRM_L |
| case ctn_locale_collation: |
| { |
| size_t check_len; |
| char *char_sort_key; |
| sort_key = (BYTES_STRING *) malloc (sizeof (BYTES_STRING)); |
| sort_key->len = strxfrm_l (0, sort_string, 0, collator->coll.locale); |
| char_sort_key = (char *) malloc (sizeof (char) * sort_key->len); |
| /* there is uninitialized memory without the next line. Somewhat |
| unclear why it is needed, strxfrm_l could have done it. */ |
| memset (char_sort_key, 0, sizeof (char) * sort_key->len); |
| check_len = strxfrm_l (char_sort_key, sort_string, sort_key->len, |
| collator->coll.locale); |
| if (check_len != sort_key->len) |
| fatal ("strxfrm_l returns a different length"); |
| sort_key->bytes = (unsigned char *) |
| malloc (sizeof (unsigned char) * sort_key->len); |
| memcpy (sort_key->bytes, (unsigned char *) char_sort_key, |
| sort_key->len); |
| free (char_sort_key); |
| } |
| break; |
| #endif |
| case ctn_unicode: |
| sort_key = (BYTES_STRING *) malloc (sizeof (BYTES_STRING)); |
| sort_key->bytes = u8_make_collation_key |
| (sort_string, strlen (sort_string), |
| UNICOLL_VARIABLE_NONIGNORABLE, |
| NULL, &sort_key->len); |
| |
| break; |
| case ctn_language_collation: |
| default: /* !HAVE_STRXFRM_L && ctn_locale_collation */ |
| sort_key = call_collator_getSortKey (collator->coll.sv, |
| sort_string); |
| break; |
| } |
| return sort_key; |
| } |
| |
| void |
| destroy_index_entries_sort_strings (INDICES_SORT_STRINGS *indices_sort_strings) |
| { |
| if (indices_sort_strings && indices_sort_strings->number) |
| { |
| size_t i; |
| for (i = 0; i < indices_sort_strings->number; i++) |
| { |
| INDEX_SORT_STRINGS *index_sort_strings |
| = &indices_sort_strings->indices[i]; |
| if (index_sort_strings->entries_number > 0) |
| { |
| size_t j; |
| for (j = 0; j < index_sort_strings->entries_number; j++) |
| { |
| INDEX_ENTRY_SORT_STRING *entry_sort_string |
| = &index_sort_strings->sort_string_entries[j]; |
| if (entry_sort_string->subentries_number > 0) |
| { |
| size_t k; |
| for (k = 0; k < entry_sort_string->subentries_number; |
| k++) |
| { |
| free (entry_sort_string->sort_string_subentries[k] |
| .sort_string); |
| } |
| free (entry_sort_string->sort_string_subentries); |
| } |
| } |
| } |
| free (index_sort_strings->sort_string_entries); |
| } |
| free (indices_sort_strings->indices); |
| } |
| free (indices_sort_strings); |
| } |
| |
| INDICES_SORT_STRINGS * |
| setup_index_entries_sort_strings (ERROR_MESSAGE_LIST *error_messages, |
| OPTIONS *options, const MERGED_INDICES *merged_indices, |
| INDEX_LIST *indices_information, |
| int prefer_reference_element, |
| CONVERTER *converter, |
| char *(*index_entry_element_sort_string_fn) (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) |
| ) |
| { |
| size_t i; |
| TEXT_OPTIONS *convert_text_options; |
| CONST_ELEMENT_LIST subentries_list; |
| |
| if (index_entry_element_sort_string_fn == 0) |
| index_entry_element_sort_string_fn = &index_entry_element_sort_string; |
| |
| if (merged_indices->number <= 0) |
| return 0; |
| |
| memset (&subentries_list, 0, sizeof (CONST_ELEMENT_LIST)); |
| |
| /* convert index entries to sort string using unicode when possible |
| independently of input and output encodings */ |
| convert_text_options = new_text_options (); |
| convert_text_options->encoding = strdup ("utf-8"); |
| /* It could be possible to set INCLUDE_DIRECTORIES, but there is no |
| point doing so, as it is only useful for @verbatiminclude, which |
| cannot appear in index entries. |
| copy_strings (&convert_text_options->include_directories, |
| options->INCLUDE_DIRECTORIES.strlist); |
| */ |
| |
| INDICES_SORT_STRINGS *indices_sort_strings |
| = (INDICES_SORT_STRINGS *) malloc (sizeof (INDICES_SORT_STRINGS)); |
| |
| indices_sort_strings->number = merged_indices->number; |
| indices_sort_strings->indices = (INDEX_SORT_STRINGS *) |
| malloc (merged_indices->number * sizeof (INDEX_SORT_STRINGS)); |
| memset (indices_sort_strings->indices, 0, |
| merged_indices->number * sizeof (INDEX_SORT_STRINGS)); |
| |
| for (i = 0; i < merged_indices->number; i++) |
| { |
| MERGED_INDEX *index = &merged_indices->indices[i]; |
| INDEX_SORT_STRINGS *index_sort_strings |
| = &indices_sort_strings->indices[i]; |
| if (index->entries_number > 0) |
| { |
| size_t j; |
| /* keep track of number of non empty index entries number */ |
| size_t nr = 0; |
| |
| index_sort_strings->index = index; |
| index_sort_strings->sort_string_entries = (INDEX_ENTRY_SORT_STRING *) |
| malloc (index->entries_number * sizeof (INDEX_ENTRY_SORT_STRING)); |
| |
| for (j = 0; j < index->entries_number; j++) |
| { |
| size_t l; |
| int non_empty_index_subentries = 0; |
| char *sort_string; |
| INDEX_SUBENTRY_SORT_STRING *subentry_sort_string; |
| INDEX_ENTRY *index_entry = &index->index_entries[j]; |
| INDEX_ENTRY_SORT_STRING entry_sort_string; |
| |
| ELEMENT *main_entry_element = index_entry->entry_element; |
| |
| INDEX *entry_index |
| = indices_info_index_by_name (indices_information, |
| index_entry->index_name); |
| |
| sort_string |
| = (*index_entry_element_sort_string_fn) (index_entry, |
| main_entry_element, convert_text_options, |
| entry_index->in_code, |
| prefer_reference_element, -1, |
| converter); |
| |
| entry_sort_string.entry = index_entry; |
| entry_sort_string.subentries_number = 1; |
| entry_sort_string.sort_string_subentries |
| = (INDEX_SUBENTRY_SORT_STRING *) |
| malloc (sizeof (INDEX_SUBENTRY_SORT_STRING)); |
| |
| subentry_sort_string |
| = &entry_sort_string.sort_string_subentries[0]; |
| |
| if (sort_string[strspn |
| (sort_string, whitespace_chars)] == '\0') |
| { |
| const char *entry_cmdname; |
| |
| subentry_sort_string->sort_string = strdup (""); |
| free (sort_string); |
| |
| entry_cmdname = element_command_name (main_entry_element); |
| if (!entry_cmdname) |
| { |
| entry_cmdname = lookup_extra_string (main_entry_element, |
| AI_key_original_def_cmdname); |
| } |
| |
| message_list_command_warn (error_messages, |
| (options && options->DEBUG.o.integer > 0), |
| main_entry_element, 0, |
| "empty index key in @%s", entry_cmdname); |
| } |
| else |
| { |
| subentry_sort_string->sort_string = sort_string; |
| non_empty_index_subentries++; |
| } |
| |
| collect_subentries (main_entry_element, &subentries_list); |
| for (l = 0; l < subentries_list.number; l++) |
| { |
| const ELEMENT *subentry = subentries_list.list[l]; |
| INDEX_SUBENTRY_SORT_STRING *subentry_sort_string; |
| |
| entry_sort_string.subentries_number++; |
| |
| entry_sort_string.sort_string_subentries |
| = (INDEX_SUBENTRY_SORT_STRING *) |
| realloc (entry_sort_string.sort_string_subentries, |
| sizeof (INDEX_SUBENTRY_SORT_STRING) |
| * entry_sort_string.subentries_number); |
| if (!entry_sort_string.sort_string_subentries) |
| fatal ("realloc failed"); |
| |
| subentry_sort_string |
| = &entry_sort_string.sort_string_subentries[ |
| entry_sort_string.subentries_number -1]; |
| |
| sort_string |
| = (*index_entry_element_sort_string_fn) (index_entry, |
| subentry, convert_text_options, |
| entry_index->in_code, 0, -1, |
| converter); |
| |
| if (sort_string[strspn |
| (sort_string, whitespace_chars)] == '\0') |
| { |
| const char *entry_cmdname; |
| |
| subentry_sort_string->sort_string = strdup (""); |
| free (sort_string); |
| |
| entry_cmdname = element_command_name (main_entry_element); |
| if (!entry_cmdname) |
| { |
| entry_cmdname |
| = lookup_extra_string (main_entry_element, |
| AI_key_original_def_cmdname); |
| } |
| |
| message_list_command_warn (error_messages, |
| (options && options->DEBUG.o.integer > 0), |
| main_entry_element, 0, |
| "empty index sub entry %zu key in @%s", |
| entry_sort_string.subentries_number -1, |
| entry_cmdname); |
| } |
| else |
| { |
| subentry_sort_string->sort_string = sort_string; |
| non_empty_index_subentries++; |
| } |
| } |
| subentries_list.number = 0; |
| if (non_empty_index_subentries > 0) |
| { |
| size_t k; |
| nr++; |
| |
| for (k = 0; k < entry_sort_string.subentries_number; k++) |
| { |
| ucs4_t next_char; |
| INDEX_SUBENTRY_SORT_STRING *subentry_sort_string |
| = &entry_sort_string.sort_string_subentries[k]; |
| int new_len = u8_strmbtouc (&next_char, |
| (uint8_t *) subentry_sort_string->sort_string); |
| if (new_len > 0 |
| && uc_is_property (next_char, UC_PROPERTY_ALPHABETIC)) |
| subentry_sort_string->alpha = 1; |
| else |
| subentry_sort_string->alpha = 0; |
| } |
| |
| memcpy (&index_sort_strings->sort_string_entries[nr-1], |
| &entry_sort_string, |
| sizeof (INDEX_ENTRY_SORT_STRING)); |
| } |
| else |
| { |
| size_t k; |
| for (k = 0; k < entry_sort_string.subentries_number; k++) |
| { |
| free (entry_sort_string.sort_string_subentries[k] |
| .sort_string); |
| } |
| free (entry_sort_string.sort_string_subentries); |
| } |
| } |
| index_sort_strings->entries_number = nr; |
| } |
| } |
| |
| destroy_text_options (convert_text_options); |
| |
| free (subentries_list.list); |
| |
| return indices_sort_strings; |
| } |
| |
| static INDEX_COLLATOR * |
| setup_collator (int use_unicode_collation, const char *collation_language, |
| const char *collation_locale, |
| ERROR_MESSAGE_LIST *error_messages, OPTIONS *options) |
| { |
| INDEX_COLLATOR *result = (INDEX_COLLATOR *) malloc (sizeof (INDEX_COLLATOR)); |
| memset (result, 0, sizeof (INDEX_COLLATOR)); |
| |
| if (use_unicode_collation == 0) |
| { |
| result->type = ctn_no_unicode; |
| } |
| else |
| { |
| if (collation_language) |
| { |
| result->coll.sv = call_setup_lang_collator (collation_language); |
| if (result->coll.sv) |
| result->type = ctn_language_collation; |
| } |
| |
| if (!result->type) |
| { |
| #ifdef HAVE_STRXFRM_L |
| #ifdef HAVE_NEWLOCALE |
| if (collation_locale) |
| { |
| result->coll.locale |
| = newlocale (LC_COLLATE_MASK, collation_locale, 0); |
| if (result->coll.locale) |
| { |
| result->type = ctn_locale_collation; |
| return result; |
| } |
| else |
| { |
| message_list_document_warn (error_messages, options, 0, |
| "collation locale not found: %s", collation_locale); |
| } |
| } |
| #endif |
| #endif |
| |
| result->type = ctn_unicode; |
| } |
| } |
| return result; |
| } |
| |
| static INDICES_SORTABLE_ENTRIES * |
| setup_sortable_index_entries (const INDEX_COLLATOR *collator, |
| const INDICES_SORT_STRINGS *indices_sort_strings) |
| { |
| size_t i; |
| |
| if (!indices_sort_strings || indices_sort_strings->number <= 0) |
| return 0; |
| |
| INDICES_SORTABLE_ENTRIES *indices_sortable_entries |
| = (INDICES_SORTABLE_ENTRIES *) malloc (sizeof (INDICES_SORTABLE_ENTRIES)); |
| |
| indices_sortable_entries->number = indices_sort_strings->number; |
| indices_sortable_entries->indices = (INDEX_SORTABLE_ENTRIES *) |
| malloc (indices_sort_strings->number * sizeof (INDEX_SORTABLE_ENTRIES)); |
| memset (indices_sortable_entries->indices, 0, |
| indices_sort_strings->number * sizeof (INDEX_SORTABLE_ENTRIES)); |
| |
| for (i = 0; i < indices_sort_strings->number; i++) |
| { |
| INDEX_SORT_STRINGS *index_sort_strings |
| = &indices_sort_strings->indices[i]; |
| INDEX_SORTABLE_ENTRIES *sortable_index_entries |
| = &indices_sortable_entries->indices[i]; |
| if (index_sort_strings->entries_number > 0) |
| { |
| size_t j; |
| |
| sortable_index_entries->index = index_sort_strings->index; |
| sortable_index_entries->number = index_sort_strings->entries_number; |
| sortable_index_entries->sortable_entries = (SORTABLE_INDEX_ENTRY *) |
| malloc (index_sort_strings->entries_number |
| * sizeof (SORTABLE_INDEX_ENTRY)); |
| |
| for (j = 0; j < index_sort_strings->entries_number; j++) |
| { |
| size_t k; |
| INDEX_ENTRY_SORT_STRING *index_entry_sort_string |
| = &index_sort_strings->sort_string_entries[j]; |
| |
| SORTABLE_INDEX_ENTRY *sortable_entry |
| = &sortable_index_entries->sortable_entries[j]; |
| |
| sortable_entry->entry = index_entry_sort_string->entry; |
| sortable_entry->subentries_number |
| = index_entry_sort_string->subentries_number; |
| sortable_entry->sortable_subentries = (SORTABLE_INDEX_SUBENTRY *) |
| malloc (index_entry_sort_string->subentries_number |
| * sizeof (SORTABLE_INDEX_SUBENTRY)); |
| |
| for (k = 0; k < index_entry_sort_string->subentries_number; k++) |
| { |
| SORTABLE_INDEX_SUBENTRY *sortable_subentry |
| = &sortable_entry->sortable_subentries[k]; |
| INDEX_SUBENTRY_SORT_STRING *subenty_sort_string |
| = &index_entry_sort_string->sort_string_subentries[k]; |
| |
| sortable_subentry->sort_string |
| = strdup (subenty_sort_string->sort_string); |
| sortable_subentry->alpha = subenty_sort_string->alpha; |
| sortable_subentry->sort_key |
| = get_sort_key (collator, subenty_sort_string->sort_string); |
| } |
| } |
| } |
| } |
| return indices_sortable_entries; |
| } |
| |
| |
| static INDICES_SORTABLE_ENTRIES * |
| setup_sort_sortable_strings_collator ( |
| const INDICES_SORT_STRINGS *indices_sort_strings, |
| ERROR_MESSAGE_LIST *error_messages, |
| OPTIONS *options, |
| int use_unicode_collation, |
| const char *collation_language, |
| const char *collation_locale, |
| INDEX_COLLATOR **collator) |
| { |
| INDICES_SORTABLE_ENTRIES *index_sortable_index_entries; |
| |
| *collator = setup_collator (use_unicode_collation, collation_language, |
| collation_locale, error_messages, options); |
| |
| index_sortable_index_entries = setup_sortable_index_entries (*collator, |
| indices_sort_strings); |
| |
| return index_sortable_index_entries; |
| } |
| |
| |
| /* for debugging purposes, printable representation of sort keys bytes */ |
| /* |
| static |
| */ |
| char * |
| print_bytes (BYTES_STRING *bytes) |
| { |
| size_t i; |
| TEXT text; |
| text_init (&text); |
| text_append (&text, ""); |
| |
| char *p = (char *)bytes->bytes; |
| for (i = 0; i < bytes->len; i++) |
| { |
| char c = *p; |
| if (((c & ~0x7f) == 0) && c != '\\' && isprint (c)) |
| text_append_n (&text, p, 1); |
| else |
| text_printf (&text, "\\%02X", c); |
| p++; |
| } |
| return text.text; |
| } |
| |
| typedef struct LETTER_SORTABLE_ENTRIES { |
| char *letter; |
| BYTES_STRING *letter_sort_key; |
| size_t space; |
| size_t number; |
| SORTABLE_INDEX_ENTRY **sortable_entries; |
| } LETTER_SORTABLE_ENTRIES; |
| |
| typedef struct INDEX_LETTERS_SORTABLE_ENTRIES { |
| size_t letter_number; |
| size_t space; |
| LETTER_SORTABLE_ENTRIES *letter_entries; |
| } INDEX_LETTERS_SORTABLE_ENTRIES; |
| |
| #define MIN_INT(a,b) (((a)<(b))?(a):(b)) |
| static int |
| compare_byte_strings (BYTES_STRING *a, BYTES_STRING *b) |
| { |
| size_t common_len = MIN_INT (a->len, b->len); |
| int res = memcmp (a->bytes, b->bytes, common_len); |
| if (res == 0) |
| return (a->len > b->len) - (a->len < b->len); |
| return res; |
| } |
| |
| #undef MIN_INT |
| |
| static int |
| compare_index_letter (const void *a, const void *b) |
| { |
| const LETTER_SORTABLE_ENTRIES *lse_a = (const LETTER_SORTABLE_ENTRIES *) a; |
| const LETTER_SORTABLE_ENTRIES *lse_b = (const LETTER_SORTABLE_ENTRIES *) b; |
| |
| return compare_byte_strings (lse_a->letter_sort_key, lse_b->letter_sort_key); |
| } |
| |
| static int |
| compare_sortable_subentry_keys (const void *a, const void *b) |
| { |
| const SORTABLE_INDEX_SUBENTRY *sis_a = (const SORTABLE_INDEX_SUBENTRY *) a; |
| const SORTABLE_INDEX_SUBENTRY *sis_b = (const SORTABLE_INDEX_SUBENTRY *) b; |
| |
| if (sis_a->alpha == sis_b->alpha) |
| return compare_byte_strings (sis_a->sort_key, sis_b->sort_key); |
| |
| return (sis_a->alpha > sis_b->alpha) - (sis_a->alpha < sis_b->alpha); |
| } |
| |
| static int |
| compare_sortable_index_entry (const SORTABLE_INDEX_ENTRY *sie_a, |
| const SORTABLE_INDEX_ENTRY *sie_b) |
| { |
| size_t i; |
| int res; |
| |
| /* corresponds to the main entry and subentries */ |
| for (i = 0; i < sie_a->subentries_number; i++) |
| { |
| SORTABLE_INDEX_SUBENTRY *sub_key_a = &sie_a->sortable_subentries[i]; |
| SORTABLE_INDEX_SUBENTRY *sub_key_b = &sie_b->sortable_subentries[i]; |
| |
| res = compare_sortable_subentry_keys (sub_key_a, sub_key_b); |
| |
| if (res != 0) |
| return res; |
| |
| if (i + 1 >= sie_b->subentries_number) |
| break; |
| } |
| res = (sie_a->subentries_number > sie_b->subentries_number) |
| - (sie_a->subentries_number < sie_b->subentries_number); |
| |
| if (res != 0) |
| return res; |
| |
| res = (sie_a->entry->number > sie_b->entry->number) |
| - (sie_a->entry->number < sie_b->entry->number); |
| |
| if (res != 0) |
| return res; |
| |
| /* This may happen if 2 indices are merged as the number is per |
| index name. */ |
| |
| return strcmp (sie_a->entry->index_name, sie_b->entry->index_name); |
| } |
| |
| static int |
| compare_sortable_index_entry_refs (const void *a, const void *b) |
| { |
| const SORTABLE_INDEX_ENTRY **psie_a = (const SORTABLE_INDEX_ENTRY **) a; |
| const SORTABLE_INDEX_ENTRY **psie_b = (const SORTABLE_INDEX_ENTRY **) b; |
| |
| return compare_sortable_index_entry (*psie_a, *psie_b); |
| } |
| |
| static int |
| compare_sortable_index_entry_wrapper (const void *a, const void *b) |
| { |
| const SORTABLE_INDEX_ENTRY *sie_a = (const SORTABLE_INDEX_ENTRY *) a; |
| const SORTABLE_INDEX_ENTRY *sie_b = (const SORTABLE_INDEX_ENTRY *) b; |
| |
| return compare_sortable_index_entry (sie_a, sie_b); |
| } |
| |
| /* the entry is not destroyed here as it is transferred to the sorted indices */ |
| static void |
| destroy_indices_sortable_entries ( |
| INDICES_SORTABLE_ENTRIES *indices_sortable_entries) |
| { |
| if (indices_sortable_entries) |
| { |
| size_t i; |
| for (i = 0; i < indices_sortable_entries->number; i++) |
| { |
| INDEX_SORTABLE_ENTRIES *sortable_index_entries |
| = &indices_sortable_entries->indices[i]; |
| if (sortable_index_entries->number > 0) |
| { |
| size_t j; |
| |
| for (j = 0; j < sortable_index_entries->number; j++) |
| { |
| size_t k; |
| SORTABLE_INDEX_ENTRY *sortable_entry |
| = &sortable_index_entries->sortable_entries[j]; |
| |
| for (k = 0; k < sortable_entry->subentries_number; |
| k++) |
| { |
| SORTABLE_INDEX_SUBENTRY *sortable_subentry |
| = &sortable_entry->sortable_subentries[k]; |
| free (sortable_subentry->sort_key->bytes); |
| free (sortable_subentry->sort_key); |
| free (sortable_subentry->sort_string); |
| } |
| free (sortable_entry->sortable_subentries); |
| } |
| free (sortable_index_entries->sortable_entries); |
| } |
| } |
| free (indices_sortable_entries->indices); |
| free (indices_sortable_entries); |
| } |
| } |
| |
| static void |
| destroy_collator (INDEX_COLLATOR *collator) |
| { |
| if (collator->type == ctn_locale_collation) |
| { |
| #ifdef HAVE_NEWLOCALE |
| freelocale (collator->coll.locale); |
| #endif |
| } |
| else if (collator->coll.sv) |
| unregister_perl_data (collator->coll.sv); |
| free (collator); |
| } |
| |
| INDEX_SORTED_BY_INDEX * |
| sort_indices_by_index (const INDICES_SORT_STRINGS *indices_sort_strings, |
| ERROR_MESSAGE_LIST *error_messages, |
| OPTIONS *options, |
| int use_unicode_collation, |
| const char *collation_language, |
| const char *collation_locale) |
| { |
| size_t i; |
| size_t index_nr = 0; |
| INDEX_COLLATOR *collator; |
| |
| INDICES_SORTABLE_ENTRIES *indices_sortable_entries |
| = setup_sort_sortable_strings_collator (indices_sort_strings, |
| error_messages, options, |
| use_unicode_collation, collation_language, |
| collation_locale, &collator); |
| |
| if (!indices_sortable_entries || indices_sortable_entries->number <= 0) |
| { |
| destroy_indices_sortable_entries (indices_sortable_entries); |
| destroy_collator (collator); |
| return 0; |
| } |
| |
| INDEX_SORTED_BY_INDEX *sorted_index_entries |
| = (INDEX_SORTED_BY_INDEX *) malloc |
| ((indices_sortable_entries->number +1) * sizeof (INDEX_SORTED_BY_INDEX)); |
| |
| for (i = 0; i < indices_sortable_entries->number; i++) |
| { |
| size_t k; |
| INDEX_SORTABLE_ENTRIES *sortable_index_entries |
| = &indices_sortable_entries->indices[i]; |
| INDEX_SORTED_BY_INDEX *index_sorted; |
| |
| if (sortable_index_entries->number <= 0) |
| continue; |
| |
| index_sorted = &sorted_index_entries[index_nr]; |
| index_sorted->name = strdup (sortable_index_entries->index->name); |
| index_sorted->entries_number = sortable_index_entries->number; |
| |
| /* directly sort the sortable entries */ |
| qsort (sortable_index_entries->sortable_entries, |
| index_sorted->entries_number, |
| sizeof (SORTABLE_INDEX_ENTRY), |
| compare_sortable_index_entry_wrapper); |
| |
| index_sorted->entries = (INDEX_ENTRY **) malloc |
| (sizeof (INDEX_ENTRY *) * index_sorted->entries_number); |
| |
| /* set index entries in the sorted order */ |
| for (k = 0; k < index_sorted->entries_number; k++) |
| { |
| index_sorted->entries[k] |
| = sortable_index_entries->sortable_entries[k].entry; |
| } |
| |
| index_nr++; |
| } |
| |
| /* add a 0 at the end of the list */ |
| memset (&sorted_index_entries[index_nr], 0, sizeof (INDEX_SORTED_BY_INDEX)); |
| /* shrink the list if there are less sorted indices than sortable indices */ |
| if (index_nr < indices_sortable_entries->number) |
| sorted_index_entries = realloc (sorted_index_entries, |
| (index_nr+1) * sizeof (INDEX_SORTED_BY_INDEX)); |
| |
| destroy_collator (collator); |
| destroy_indices_sortable_entries (indices_sortable_entries); |
| |
| return sorted_index_entries; |
| } |
| |
| |
| INDEX_SORTED_BY_LETTER * |
| sort_indices_by_letter (const INDICES_SORT_STRINGS *indices_sort_strings, |
| ERROR_MESSAGE_LIST *error_messages, |
| OPTIONS *options, |
| int use_unicode_collation, |
| const char *collation_language, |
| const char *collation_locale) |
| { |
| size_t i; |
| size_t index_nr = 0; |
| static INDEX_LETTERS_SORTABLE_ENTRIES index_letters_sortable_entries; |
| INDEX_COLLATOR *collator; |
| |
| INDICES_SORTABLE_ENTRIES *indices_sortable_entries |
| = setup_sort_sortable_strings_collator (indices_sort_strings, |
| error_messages, options, |
| use_unicode_collation, collation_language, |
| collation_locale, &collator); |
| |
| if (!indices_sortable_entries || indices_sortable_entries->number <= 0) |
| { |
| destroy_indices_sortable_entries (indices_sortable_entries); |
| destroy_collator (collator); |
| return 0; |
| } |
| |
| INDEX_SORTED_BY_LETTER *sorted_index_entries |
| = (INDEX_SORTED_BY_LETTER *) malloc |
| ((indices_sortable_entries->number +1) * sizeof (INDEX_SORTED_BY_LETTER)); |
| |
| for (i = 0; i < indices_sortable_entries->number; i++) |
| { |
| size_t j; |
| INDEX_SORTABLE_ENTRIES *sortable_index_entries |
| = &indices_sortable_entries->indices[i]; |
| INDEX_SORTED_BY_LETTER *index_sorted; |
| |
| if (sortable_index_entries->number <= 0) |
| continue; |
| |
| index_sorted = &sorted_index_entries[index_nr]; |
| index_sorted->name = strdup (sortable_index_entries->index->name); |
| |
| for (j = 0; j < sortable_index_entries->number; j++) |
| { |
| int len = 0; |
| SORTABLE_INDEX_ENTRY *sortable_entry |
| = &sortable_index_entries->sortable_entries[j]; |
| char *sort_string |
| = sortable_entry->sortable_subentries[0].sort_string; |
| uint8_t *encoded_u8 = utf8_from_string (sort_string); |
| uint8_t *current_u8 = encoded_u8; |
| char *letter_string; |
| char *upper_letter_string; |
| char *norm_letter_string; |
| TEXT letter_text; |
| BYTES_STRING *letter_sort_key; |
| int letter_added = 0; |
| |
| LETTER_SORTABLE_ENTRIES *letter_sortable_entries = 0; |
| |
| while (1) |
| { |
| ucs4_t next_char; |
| int new_len = u8_strmbtouc (&next_char, current_u8); |
| if (!new_len) |
| break; |
| |
| len += new_len; |
| /* UC_NON_SPACING_MARK is the same as UC_CATEGORY_Mn */ |
| if (uc_is_general_category (next_char, UC_NON_SPACING_MARK)) |
| { |
| current_u8 += new_len; |
| } |
| else |
| { |
| break; |
| } |
| } |
| free (encoded_u8); |
| |
| letter_string = strndup (sort_string, len); |
| upper_letter_string = to_upper_or_lower_multibyte (letter_string, 1); |
| free (letter_string); |
| norm_letter_string = normalize_NFKD (upper_letter_string); |
| free (upper_letter_string); |
| encoded_u8 = utf8_from_string (norm_letter_string); |
| free (norm_letter_string); |
| current_u8 = encoded_u8; |
| |
| text_init (&letter_text); |
| /* we want an empty string if the input string is empty */ |
| text_append (&letter_text, ""); |
| |
| while (1) |
| { |
| ucs4_t next_char; |
| int new_len = u8_strmbtouc (&next_char, current_u8); |
| if (!new_len) |
| break; |
| |
| if (!uc_is_general_category (next_char, UC_NON_SPACING_MARK)) |
| { |
| char *first_char_text; |
| uint8_t *first_char_u8 = malloc (7 * sizeof (uint8_t)); |
| int first_char_len = u8_uctomb (first_char_u8, next_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); |
| text_append (&letter_text, first_char_text); |
| free (first_char_text); |
| } |
| current_u8 += new_len; |
| } |
| free (encoded_u8); |
| |
| letter_sort_key = get_sort_key (collator, letter_text.text); |
| |
| if (index_letters_sortable_entries.letter_number > 0) |
| { |
| /* used to find an existing letter */ |
| static LETTER_SORTABLE_ENTRIES compared_letter_sortable; |
| |
| compared_letter_sortable.letter_sort_key = letter_sort_key; |
| |
| letter_sortable_entries = (LETTER_SORTABLE_ENTRIES *) |
| bsearch (&compared_letter_sortable, |
| index_letters_sortable_entries.letter_entries, |
| index_letters_sortable_entries.letter_number, |
| sizeof (LETTER_SORTABLE_ENTRIES), |
| compare_index_letter); |
| } |
| |
| if (!letter_sortable_entries) |
| { |
| if (index_letters_sortable_entries.letter_number |
| >= index_letters_sortable_entries.space) |
| { |
| index_letters_sortable_entries.letter_entries |
| = realloc (index_letters_sortable_entries.letter_entries, |
| (index_letters_sortable_entries.space += 5) |
| * sizeof (LETTER_SORTABLE_ENTRIES)); |
| if (!index_letters_sortable_entries.letter_entries) |
| fatal ("realloc failed"); |
| } |
| letter_sortable_entries |
| = &index_letters_sortable_entries.letter_entries[ |
| index_letters_sortable_entries.letter_number]; |
| |
| letter_sortable_entries->space = 0; |
| letter_sortable_entries->number = 0; |
| letter_sortable_entries->sortable_entries = 0; |
| letter_sortable_entries->letter = letter_text.text; |
| letter_sortable_entries->letter_sort_key = letter_sort_key; |
| |
| index_letters_sortable_entries.letter_number++; |
| |
| letter_added = 1; |
| } |
| else |
| { |
| free (letter_text.text); |
| free (letter_sort_key->bytes); |
| free (letter_sort_key); |
| } |
| |
| /* now add the sortable entry to the letter sortable entries */ |
| if (letter_sortable_entries->number |
| >= letter_sortable_entries->space) |
| { |
| letter_sortable_entries->sortable_entries |
| = realloc (letter_sortable_entries->sortable_entries, |
| (letter_sortable_entries->space += 5) |
| * sizeof (SORTABLE_INDEX_ENTRY)); |
| if (!letter_sortable_entries->sortable_entries) |
| fatal ("realloc failed"); |
| } |
| letter_sortable_entries->sortable_entries[ |
| letter_sortable_entries->number] = sortable_entry; |
| |
| letter_sortable_entries->number++; |
| /* sort after using letter_sortable_entries, as its address may be |
| changed by the sort */ |
| if (index_letters_sortable_entries.letter_number > 1 |
| && letter_added) |
| { |
| /* re-sort letters with the new letter added */ |
| if (index_letters_sortable_entries.letter_number > 1) |
| qsort (index_letters_sortable_entries.letter_entries, |
| index_letters_sortable_entries.letter_number, |
| sizeof (LETTER_SORTABLE_ENTRIES), |
| compare_index_letter); |
| } |
| } |
| |
| /* sort the index entries by letter */ |
| /* also reset for the next index */ |
| |
| if (index_letters_sortable_entries.letter_number > 0) |
| { |
| index_sorted->letter_number |
| = index_letters_sortable_entries.letter_number; |
| index_sorted->letter_entries = (LETTER_INDEX_ENTRIES *) malloc |
| (index_sorted->letter_number * sizeof (LETTER_INDEX_ENTRIES)); |
| |
| for (j = 0; j < index_letters_sortable_entries.letter_number; j++) |
| { |
| size_t k; |
| LETTER_SORTABLE_ENTRIES *letter_sortable_entries |
| = &index_letters_sortable_entries.letter_entries[j]; |
| LETTER_INDEX_ENTRIES *letter_index_entries |
| = &index_sorted->letter_entries[j]; |
| |
| qsort (letter_sortable_entries->sortable_entries, |
| letter_sortable_entries->number, |
| sizeof (SORTABLE_INDEX_ENTRY *), |
| compare_sortable_index_entry_refs); |
| |
| letter_index_entries->letter = letter_sortable_entries->letter; |
| letter_index_entries->entries = (INDEX_ENTRY **) malloc |
| (letter_sortable_entries->number * sizeof (INDEX_ENTRY *)); |
| letter_index_entries->entries_number |
| = letter_sortable_entries->number; |
| |
| /* set index entries in the sorted order */ |
| for (k = 0; k < letter_sortable_entries->number; k++) |
| { |
| letter_index_entries->entries[k] |
| = letter_sortable_entries->sortable_entries[k]->entry; |
| } |
| |
| free (letter_sortable_entries->letter_sort_key->bytes); |
| free (letter_sortable_entries->letter_sort_key); |
| free (letter_sortable_entries->sortable_entries); |
| } |
| index_letters_sortable_entries.letter_number = 0; |
| } |
| index_nr++; |
| } |
| |
| /* add a 0 at the end of the list */ |
| memset (&sorted_index_entries[index_nr], 0, sizeof (INDEX_SORTED_BY_LETTER)); |
| /* shrink the list if there are less sorted indices than sortable indices */ |
| if (index_nr < indices_sortable_entries->number) |
| sorted_index_entries = realloc (sorted_index_entries, |
| (index_nr+1) * sizeof (INDEX_SORTED_BY_LETTER)); |
| |
| destroy_collator (collator); |
| destroy_indices_sortable_entries (indices_sortable_entries); |
| |
| return sorted_index_entries; |
| } |
| |
| static COLLATION_INDICES_SORTED_BY_INDEX * |
| new_collation_sorted_indices_by_index ( |
| COLLATIONS_INDICES_SORTED_BY_INDEX *collations, |
| enum collation_type_name type, |
| const char *language) |
| { |
| COLLATION_INDICES_SORTED_BY_INDEX *result = 0; |
| if (collations->number <= collations->space) |
| { |
| collations->collation_sorted_indices |
| = (COLLATION_INDICES_SORTED_BY_INDEX *) realloc |
| (collations->collation_sorted_indices, |
| (collations->space += 3) |
| * sizeof (COLLATION_INDICES_SORTED_BY_INDEX)); |
| if (!collations->collation_sorted_indices) |
| fatal ("realloc failed"); |
| } |
| |
| result = &collations->collation_sorted_indices[collations->number]; |
| memset (result, 0, sizeof (COLLATION_INDICES_SORTED_BY_INDEX)); |
| result->type = type; |
| result->language = strdup (language); |
| |
| collations->number++; |
| |
| return result; |
| } |
| |
| COLLATIONS_INDICES_SORTED_BY_INDEX * |
| new_base_collations_sorted_indices_by_index (void) |
| { |
| COLLATIONS_INDICES_SORTED_BY_INDEX *collations |
| = (COLLATIONS_INDICES_SORTED_BY_INDEX *) |
| malloc (sizeof (COLLATIONS_INDICES_SORTED_BY_INDEX)); |
| memset (collations, 0, |
| sizeof (COLLATIONS_INDICES_SORTED_BY_INDEX)); |
| |
| /* order is important, to match enum */ |
| new_collation_sorted_indices_by_index (collations, ctn_unicode, "-"); |
| new_collation_sorted_indices_by_index (collations, ctn_no_unicode, ""); |
| |
| return collations; |
| } |
| |
| static COLLATION_INDICES_SORTED_BY_INDEX * |
| find_collation_sorted_indices_by_index ( |
| COLLATIONS_INDICES_SORTED_BY_INDEX *collations, |
| enum collation_type_name type, |
| const char *language) |
| { |
| size_t i; |
| for (i = 2; i < collations->number; i++) |
| { |
| COLLATION_INDICES_SORTED_BY_INDEX *collation_sorted_indices |
| = &collations->collation_sorted_indices[i]; |
| if (collation_sorted_indices->type == type |
| && !strcmp (collation_sorted_indices->language, language)) |
| return collation_sorted_indices; |
| } |
| return 0; |
| } |
| |
| COLLATION_INDICES_SORTED_BY_INDEX * |
| get_collation_sorted_indices_by_index ( |
| COLLATIONS_INDICES_SORTED_BY_INDEX *collations, |
| int use_unicode_collation, |
| const char *input_lang_sorting_locale, |
| const char *collation_locale, |
| const char **lang_sorting_locale_out) |
| { |
| const char *lang_sorting_locale = 0; |
| COLLATION_INDICES_SORTED_BY_INDEX *collation_sorted_indices = 0; |
| |
| if (use_unicode_collation == 0) |
| collation_sorted_indices |
| = &collations->collation_sorted_indices[ctn_no_unicode]; |
| else if (!input_lang_sorting_locale && !collation_locale) |
| collation_sorted_indices |
| = &collations->collation_sorted_indices[ctn_unicode]; |
| else |
| { |
| enum collation_type_name type; |
| |
| if (input_lang_sorting_locale) |
| { |
| type = ctn_language_collation; |
| lang_sorting_locale = input_lang_sorting_locale; |
| } |
| else |
| { |
| type = ctn_locale_collation; |
| lang_sorting_locale = collation_locale; |
| } |
| |
| collation_sorted_indices |
| = find_collation_sorted_indices_by_index (collations, type, |
| lang_sorting_locale); |
| if (!collation_sorted_indices) |
| collation_sorted_indices |
| = new_collation_sorted_indices_by_index (collations, |
| type, lang_sorting_locale); |
| } |
| |
| *lang_sorting_locale_out = lang_sorting_locale; |
| return collation_sorted_indices; |
| } |
| |
| static COLLATION_INDICES_SORTED_BY_LETTER * |
| new_collation_sorted_indices_by_letter ( |
| COLLATIONS_INDICES_SORTED_BY_LETTER *collations, |
| enum collation_type_name type, |
| const char *language) |
| { |
| COLLATION_INDICES_SORTED_BY_LETTER *result = 0; |
| if (collations->number <= collations->space) |
| { |
| collations->collation_sorted_indices |
| = (COLLATION_INDICES_SORTED_BY_LETTER *) realloc |
| (collations->collation_sorted_indices, |
| (collations->space += 3) |
| * sizeof (COLLATION_INDICES_SORTED_BY_LETTER)); |
| if (!collations->collation_sorted_indices) |
| fatal ("realloc failed"); |
| } |
| |
| result = &collations->collation_sorted_indices[collations->number]; |
| memset (result, 0, sizeof (COLLATION_INDICES_SORTED_BY_LETTER)); |
| result->type = type; |
| result->language = strdup (language); |
| |
| collations->number++; |
| |
| return result; |
| } |
| |
| static COLLATION_INDICES_SORTED_BY_LETTER * |
| find_collation_sorted_indices_by_letter ( |
| COLLATIONS_INDICES_SORTED_BY_LETTER *collations, |
| enum collation_type_name type, |
| const char *language) |
| { |
| size_t i; |
| for (i = 2; i < collations->number; i++) |
| { |
| COLLATION_INDICES_SORTED_BY_LETTER *collation_sorted_indices |
| = &collations->collation_sorted_indices[i]; |
| if (collation_sorted_indices->type == type |
| && !strcmp (collation_sorted_indices->language, language)) |
| return collation_sorted_indices; |
| } |
| return 0; |
| } |
| |
| COLLATIONS_INDICES_SORTED_BY_LETTER * |
| new_base_collations_sorted_indices_by_letter (void) |
| { |
| COLLATIONS_INDICES_SORTED_BY_LETTER *collations |
| = (COLLATIONS_INDICES_SORTED_BY_LETTER *) |
| malloc (sizeof (COLLATIONS_INDICES_SORTED_BY_LETTER)); |
| memset (collations, 0, |
| sizeof (COLLATIONS_INDICES_SORTED_BY_LETTER)); |
| |
| /* order is important, to match enum */ |
| new_collation_sorted_indices_by_letter (collations, ctn_unicode, "-"); |
| new_collation_sorted_indices_by_letter (collations, ctn_no_unicode, ""); |
| |
| return collations; |
| } |
| |
| COLLATION_INDICES_SORTED_BY_LETTER * |
| get_collation_sorted_indices_by_letter ( |
| COLLATIONS_INDICES_SORTED_BY_LETTER *collations, |
| int use_unicode_collation, |
| const char *input_lang_sorting_locale, |
| const char *collation_locale, |
| const char **lang_sorting_locale_out) |
| { |
| const char *lang_sorting_locale = 0; |
| COLLATION_INDICES_SORTED_BY_LETTER *collation_sorted_indices = 0; |
| |
| if (use_unicode_collation == 0) |
| collation_sorted_indices |
| = &collations->collation_sorted_indices[ctn_no_unicode]; |
| else if (!input_lang_sorting_locale && !collation_locale) |
| collation_sorted_indices |
| = &collations->collation_sorted_indices[ctn_unicode]; |
| else |
| { |
| enum collation_type_name type; |
| |
| if (input_lang_sorting_locale) |
| { |
| type = ctn_language_collation; |
| lang_sorting_locale = input_lang_sorting_locale; |
| } |
| else |
| { |
| type = ctn_locale_collation; |
| lang_sorting_locale = collation_locale; |
| } |
| |
| collation_sorted_indices |
| = find_collation_sorted_indices_by_letter (collations, type, |
| lang_sorting_locale); |
| if (!collation_sorted_indices) |
| collation_sorted_indices |
| = new_collation_sorted_indices_by_letter (collations, |
| type, lang_sorting_locale); |
| } |
| |
| *lang_sorting_locale_out = lang_sorting_locale; |
| return collation_sorted_indices; |
| } |
| |
| void |
| destroy_sorted_indices_by_index (COLLATIONS_INDICES_SORTED_BY_INDEX *collations) |
| { |
| if (collations) |
| { |
| if (collations->number > 0) |
| { |
| size_t i; |
| for (i = 0; i < collations->number; i++) |
| { |
| COLLATION_INDICES_SORTED_BY_INDEX *collation_sorted_indices |
| = &collations->collation_sorted_indices[i]; |
| free (collation_sorted_indices->language); |
| if (collation_sorted_indices->sorted_indices) |
| destroy_indices_sorted_by_index ( |
| collation_sorted_indices->sorted_indices); |
| } |
| } |
| free (collations->collation_sorted_indices); |
| free (collations); |
| } |
| } |
| |
| void |
| destroy_sorted_indices_by_letter (COLLATIONS_INDICES_SORTED_BY_LETTER *collations) |
| { |
| if (collations) |
| { |
| if (collations->number > 0) |
| { |
| size_t i; |
| for (i = 0; i < collations->number; i++) |
| { |
| COLLATION_INDICES_SORTED_BY_LETTER *collation_sorted_indices |
| = &collations->collation_sorted_indices[i]; |
| free (collation_sorted_indices->language); |
| if (collation_sorted_indices->sorted_indices) |
| destroy_indices_sorted_by_letter ( |
| collation_sorted_indices->sorted_indices); |
| } |
| } |
| free (collations->collation_sorted_indices); |
| free (collations); |
| } |
| } |
| |
| |
| |
| /* sorting and information on indices, but not on index entries */ |
| |
| static int |
| compare_index_name (const void *a, const void *b) |
| { |
| const INDEX **idx_a = (const INDEX **) a; |
| const INDEX **idx_b = (const INDEX **) b; |
| |
| return strcmp ((*idx_a)->name, (*idx_b)->name); |
| } |
| |
| /* to be freed by caller */ |
| const INDEX ** |
| sort_index_names (INDEX_LIST *indices_info) |
| { |
| const INDEX **sorted_index_names; |
| size_t index_nr = indices_info->number; |
| |
| sorted_index_names = (const INDEX **) malloc (index_nr * sizeof (INDEX *)); |
| |
| memcpy (sorted_index_names, indices_info->list, index_nr * sizeof (INDEX *)); |
| |
| qsort (sorted_index_names, index_nr, sizeof (INDEX *), compare_index_name); |
| |
| return sorted_index_names; |
| } |
| |
| /* return value to be freed by caller */ |
| char * |
| print_indices_information (INDEX_LIST *indices_info) |
| { |
| size_t i; |
| const INDEX **sorted_index_names; |
| TEXT result; |
| |
| if (!indices_info || indices_info->number == 0) |
| return 0; |
| |
| text_init (&result); |
| text_append (&result, ""); |
| |
| sorted_index_names = sort_index_names(indices_info); |
| |
| for (i = 0; i < indices_info->number; i++) |
| { |
| const INDEX *idx = sorted_index_names[i]; |
| text_append (&result, idx->name); |
| if (idx->in_code) |
| text_append_n (&result, " C", 2); |
| if (idx->merged_in) |
| text_printf (&result, " ->%s", idx->merged_in->name); |
| text_append_n (&result, "\n", 1); |
| } |
| |
| free (sorted_index_names); |
| |
| return result.text; |
| } |