blob: b5b665d6b477637abf1671fb8f8cd8e946fb7b7b [file]
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include "unictype.h"
#include "uninorm.h"
#include "collation_data_lookup.h"
#include "collation-table.c"
struct collation_data_ref {
const struct collation_unit *array;
uint8_t num_elements;
};
typedef struct collation_data_ref COLLATION_DATA;
int
collation_element_is_variable (struct collation_unit *element)
{
return (element->primary
&& element->primary <= collation_data.max_variable_weight);
}
/* Lookup collation information for a single codepoint. */
static COLLATION_DATA
lookup_codepoint_data (char32_t codepoint)
{
if (codepoint >= 0x110000)
return (COLLATION_DATA) {0};
int index1 = codepoint >> 16; /* Unicode plane */
if (collation_data.level1[index1] < 0)
return (COLLATION_DATA) {0};
uint32_t index2 = (collation_data.level1[index1] << 8)
+ ((codepoint >> 8) & 0xff);
int index3 = collation_data.level2[index2];
if (index3 < 0)
return (COLLATION_DATA) {0};
const struct block256_data *block256 = &collation_data.level3[index3];
uint8_t index4 = codepoint & 0xFF;
COLLATION_DATA data;
data.num_elements = block256->num_elements[index4];
size_t data_index = block256->data_index[index4];
if (data_index)
data.array = &collation_data.collation_data[data_index];
else
data.array = 0;
return data;
}
static int
compare_trie_node_children (const void *a, const void *b)
{
struct trie_node *node1 = (struct trie_node *) a;
struct trie_node *node2 = (struct trie_node *) b;
return (node1->codepoint > node2->codepoint)
- (node1->codepoint < node2->codepoint);
}
/* Check for sequence at STRING, rearranging for a non-contiguous
match if necessary. */
static int
check_sequence_rearranging (char32_t *const string,
const size_t length,
const struct trie_node **node_out)
{
char32_t *pchar;
char32_t *seq_end = 0; /* end of a complete sequence with data */
int max_combining_class = 0;
const struct trie_node *node = &collation_data.trie_array[0];
size_t n_codepoints;
int non_contig = 0;
/* Starting at the beginning of the string, try to match the longest
sequence possible. */
for (pchar = string, n_codepoints = 0;
pchar < string + length && (*pchar) != 0;
pchar++)
{
int combining_class = 0;
if (non_contig)
{
/* We are trying to find a non-contiguous match. */
combining_class = uc_combining_class (*pchar);
if (combining_class == UC_CCC_NR) /* UC_CCC_NR == 0 */
{
/* This codepoint cannot be part of a non-contiguous match,
and blocks a non-contiguous match with any later codepoints. */
break;
}
if (combining_class <= max_combining_class)
{
/* This codepoint is blocked from being part of a non-contiguous
match by earlier non-starters. */
/* Note: if input is in NFD, the case
combining_class < max_combining_class will not occur. */
continue;
}
}
int num_children = node->num_children;
uint32_t first_child = node->first_child;
/* Search for matching child. */
struct trie_node dummy = { 0 };
dummy.codepoint = *pchar;
struct trie_node *found = bsearch (&dummy,
&collation_data.trie_array[first_child],
num_children,
sizeof(collation_data.trie_array[first_child]),
compare_trie_node_children);
if (!found)
{
if (!non_contig && pchar > string)
{
/* Start looking for a non-contiguous match. */
if (seq_end)
pchar = seq_end;
else
{
/* Note: according to UTS#10, we should only
extend a sequence of length 1 if it has an entry
in the collation table. We assume that it does
("well-formedness criterion 5") so we don't have
to call lookup_codepoint_data or similar. */
seq_end = string;
pchar = seq_end;
}
n_codepoints = seq_end - string + 1;
non_contig = 1;
continue;
}
else if (non_contig)
{
max_combining_class = combining_class;
/* Continue looking for a non-contiguous match. */
continue;
}
else
{
/* Cannot extend a match - at start of string. */
break;
}
}
if (found->data_index)
{
node = found;
n_codepoints++;
if (!non_contig)
{
seq_end = pchar;
}
else
{
/* This is part of a non-contiguous match. Move matched
character right after the contiguous part of the match. */
char32_t tmp = *pchar;
memmove (&seq_end[2], &seq_end[1],
(pchar - &seq_end[1]) * sizeof (char32_t));
seq_end[1] = tmp;
seq_end++;
break;
}
}
else
{
if (!non_contig)
{
node = found;
n_codepoints++;
}
else
{
/* We can't use this character to extend the match.
For non-contiguous matches, we require each extra
character to lead to a sequence with collation data.
Hence 0FB2 0334 0F71 0F80 will not match with 0FB2 0F71 0F80,
unless there is data for 0FB2 0F71.
The UCA#10 document discusses this sequence for Tibetan
but I don't really understand it, e.g. why 0FB2 0F71 was
missing.
According to "UTC #187 properties feedback &
recommendations", 2026-04-16 [*], this was an anomaly that
will be eliminated in the future.
See also documentation for Perl module Unicode::Collate.
[*] https://www.unicode.org/L2/L2026/26096-pag-report-utc187.pdf
*/
continue;
}
}
}
if (n_codepoints >= 2)
{
*node_out = node;
return n_codepoints;
}
return 0;
}
/* Much simplified version of 'check_sequence_rearranging'.
Takes a const STRING as an argument and does not attempt
to look for a non-contiguous match. */
static int
check_sequence_nonrearranging (const char32_t *const string,
const size_t length,
const struct trie_node **node_out)
{
const char32_t *pchar;
const struct trie_node *node = &collation_data.trie_array[0];
size_t n_codepoints;
/* Starting at the beginning of the string, try to match the longest
sequence possible. */
for (pchar = string, n_codepoints = 0;
pchar < string + length && (*pchar) != 0;
pchar++)
{
int num_children = node->num_children;
uint32_t first_child = node->first_child;
/* Search for matching child. */
int found = 0;
for (int j = 0; j < num_children; j++)
{
const struct trie_node *child
= &collation_data.trie_array[first_child + j];
uint32_t child_codepoint = child->codepoint;
if (child_codepoint == *pchar)
{
node = child;
found = 1;
n_codepoints++;
break;
}
if (child_codepoint > *pchar)
break;
}
if (!found)
break;
}
if (n_codepoints >= 2)
{
*node_out = node;
return n_codepoints;
}
return 0;
}
/* STRING or STRING_CONST points into a char32_t array.
First check for sequence entry at STRING, then for individual codepoint entry.
This function can reorder STRING, but not STRING_CONST. Exactly one must
be non-null.
Output variables:
N_CODEPOINTS_OUT: number of codepoints consumed to find match
COLLATION_UNITS: pointer to data array
N_COLLATION_UNITS: length of data array
*/
void
lookup_collation_data_at_char (char32_t *const string,
const char32_t *const string_const,
const size_t length,
int disable_sequences,
size_t *n_codepoints_out,
const struct collation_unit **collation_units,
size_t *n_collation_units)
{
if (string && string_const || !string && !string_const)
{
/* bug */
errno = EINVAL;
(*n_codepoints_out) = 0;
return;
}
#define CHECK_SEQUENCE_BIT 0x80
COLLATION_DATA data = lookup_codepoint_data
(string ? string[0] : string_const[0]);
if ((data.num_elements & CHECK_SEQUENCE_BIT) && !disable_sequences)
{
const struct trie_node *node = NULL;
size_t n_codepoints_matched;
if (string)
n_codepoints_matched
= check_sequence_rearranging (string, length, &node);
else /* string_const */
n_codepoints_matched
= check_sequence_nonrearranging (string_const, length, &node);
if (n_codepoints_matched > 0)
{
size_t data_index = node->data_index;
if (data_index != 0)
{
(*n_codepoints_out) = n_codepoints_matched;
*collation_units = &collation_data.collation_data[data_index];
*n_collation_units = node->num_elements;
return;
}
}
}
if (string_const && (data.num_elements & CHECK_SEQUENCE_BIT))
{
/* If a sequence record exists, do not return the single codepoint
record, as it is possible that we were unable to find a
discontiguous match. The calling code should try again with the
STRING argument. */
errno = EINVAL;
(*n_codepoints_out) = 0;
(*n_collation_units) = 0;
return;
}
(*n_codepoints_out) = data.array ? 1 : 0;
*collation_units = data.array;
*n_collation_units = (data.num_elements & ~CHECK_SEQUENCE_BIT);
return;
}
/* Return implicitly determined weights. */
void
get_implicit_weight (char32_t codepoint,
struct collation_unit *elements, size_t *n_elements)
{
if (codepoint == 0x0000)
{
(*n_elements) = 0;
return;
}
uint16_t AAAA = 0, BBBB = 0;
int i;
for (i = 0; i < NUM_IMPLICIT_BLOCKS; i++)
{
if (codepoint >= collation_data.implicit_blocks[i].low
&& codepoint <= collation_data.implicit_blocks[i].high)
{
AAAA = collation_data.implicit_blocks[i].primary;
/* Use 'low_base' which stores the lowest codepoint of all
blocks that share the same primary.
E.g., Tangut 17000-187FF and Tangut Supplement 18D00-18D7F
both have primary=FB00, so low_base here is 17000 for both
blocks. */
BBBB = (codepoint - collation_data.implicit_blocks[i].low_base)
| 0x8000;
}
}
if (!AAAA && uc_is_property_unified_ideograph (codepoint))
{
/* Check if in blocks "CJK Unified Ideographs" or "CJK Compatibility
Ideographs" */
if ((codepoint >= 0x4E00 && codepoint <= 0x9FFF)
|| (codepoint >= 0xF900 && codepoint <= 0xFAFF))
{
AAAA = 0xFB40 + (codepoint >> 15);
}
else
{
AAAA = 0xFB80 + (codepoint >> 15);
}
BBBB = (codepoint & 0x7FFF) | 0x8000;
}
if (!AAAA)
{
/* possibly an invalid codepoint */
AAAA = 0xFBC0 + (codepoint >> 15);
BBBB = (codepoint & 0x7FFF) | 0x8000;
}
if (AAAA && BBBB)
{
struct collation_unit e1 = { AAAA, 0x0020, 0x0002 };
/* same in gen-collation-table.c:expand_collation_sequence to
fit secondary weight in a single byte. */
e1.secondary -= 0x1f;
/* Limit maximum primary weight by using an extra collation element
if necessary. Same transformation in
gen-collation_table.c:write_collation_data */
if (BBBB >= 0xFE00)
{
struct collation_unit e2 = { 0xFE00, 0x0000, 0x0000 };
struct collation_unit e3 = { BBBB - 0xFE00 + 0x8000 + 1, 0x0000, 0x0000 };
elements[0] = e1;
elements[1] = e2;
elements[2] = e3;
(*n_elements) = 3;
}
else
{
struct collation_unit e2 = { BBBB, 0x0000, 0x0000 };
elements[0] = e1;
elements[1] = e2;
(*n_elements) = 2;
}
}
else
{
(*n_elements) = 0;
}
}