blob: 25e2ff92990ae156df876a06ae458ab934410872 [file]
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <setjmp.h>
#include "unistr.h"
#include "uninorm.h"
#include "collation_data_lookup.h"
#include "collation_key.h"
Collation_choice
unicoll_default (void)
{
return (Collation_choice) UNICOLL_VARIABLE_NONIGNORABLE;
}
Collation_choice
unicoll_set_variable (Collation_choice collation,
int variable)
{
if (variable != UNICOLL_VARIABLE_NONIGNORABLE
&& variable != UNICOLL_VARIABLE_SHIFTED
&& variable != UNICOLL_VARIABLE_BLANKED)
{
errno = EINVAL;
return collation;
}
return (Collation_choice) (collation & ~UNICOLL_VARIABLE_MASK) | variable;
}
/* Set bit to disable output of a collation level. */
Collation_choice
unicoll_disable_level (Collation_choice collation, int level)
{
switch (level)
{
case 1:
return (collation | UNICOLL_LEVEL1_BIT);
case 2:
return (collation | UNICOLL_LEVEL2_BIT);
case 3:
return (collation | UNICOLL_LEVEL3_BIT);
case 4:
return (collation | UNICOLL_LEVEL4_BIT);
default:
errno = EINVAL;
return collation;
}
}
/* Clear bit to enable output of a collation level. */
Collation_choice
unicoll_enable_level (Collation_choice collation, int level)
{
switch (level)
{
case 1:
return (collation & ~UNICOLL_LEVEL1_BIT);
case 2:
return (collation & ~UNICOLL_LEVEL2_BIT);
case 3:
return (collation & ~UNICOLL_LEVEL3_BIT);
case 4:
return (collation & ~UNICOLL_LEVEL4_BIT);
default:
errno = EINVAL;
return collation;
}
}
Collation_choice
unicoll_set_normalization (Collation_choice collation, int normalization_on)
{
/* Set bit to disable normalization. */
if (normalization_on)
return (collation & ~UNICOLL_NORMALIZATION_MASK);
else
return (collation | UNICOLL_NORMALIZATION_MASK);
}
/* If CONTRACTIONS_ON is 0, disable use of contractions, i.e. sequence
lookup. */
Collation_choice
unicoll_set_contractions (Collation_choice collation, int contractions_on)
{
if (contractions_on)
return (collation & ~UNICOLL_CONTRACTIONS_MASK);
else
return (collation | UNICOLL_CONTRACTIONS_MASK);
}
/* If PARTIAL_KEY_ENABLED is non-zero, u*_make_collation_key does not
re-allocate RESULTBUF and will return a partial key (null-terminated)
if the full result would be longer than *LENGTHP bytes. */
Collation_choice
unicoll_enable_partial (Collation_choice collation,
int partial_key_enabled)
{
if (!partial_key_enabled)
return (collation & ~UNICOLL_PARTIAL_MASK);
else
return (collation | UNICOLL_PARTIAL_MASK);
}
#define BITS 32
#include "uN_make_key_streaming.inc"
#undef BITS
#define BITS 8
#include "uN_make_key_streaming.inc"
#undef BITS
static char *
make_key_internal (char *psort_key,
struct collation_unit *elements, size_t elements_count,
int variable_shifted_or_blanked, int variable_shifted,
int primaryp, int secondaryp, int tertiaryp,
jmp_buf bug_jmp,
char *limit)
{
int last_was_variable;
#define cat(c) { \
if (psort_key == limit) return psort_key; \
else *psort_key++ = (c); \
}
/* Note that we do not call malloc in this function so no clean-up
is required at function exit. */
/* Output collation key without any null bytes.
See UTS#10 s.9.4 "Avoiding Zero Bytes". */
if (primaryp)
{
for (size_t i = 0; i < elements_count; i++)
{
uint16_t weight = elements[i].primary;
if (variable_shifted_or_blanked
&& collation_element_is_variable (&elements[i]))
{
/* Skip at primary level. */
continue;
}
if (weight)
{
if (weight > 0xFE00)
{
/* bug: primary weight too high */
errno = EINVAL;
longjmp (bug_jmp, 1);
}
cat ((weight / 0xFF) + 1);
cat ((weight % 0xFF) + 1);
}
}
if (secondaryp || tertiaryp || variable_shifted)
{
cat ('\x01');
cat ('\x01');
}
}
if (secondaryp)
{
last_was_variable = 0;
for (size_t i = 0; i < elements_count; i++)
{
if (variable_shifted_or_blanked
&& collation_element_is_variable (&elements[i]))
{
/* Skip at secondary level. */
last_was_variable = 1;
continue;
}
if (last_was_variable)
{
/* Ignore completely - e.g. combining grave following a space. */
if (!elements[i].primary && elements[i].tertiary)
continue;
/* This could be a continuation element for a high secondary
weight. */
if (!elements[i].primary && elements[i].secondary)
continue;
}
uint16_t weight = elements[i].secondary;
if (weight)
{
if (weight == 0xFF)
{
/* bug: secondary weight too high */
errno = EINVAL;
longjmp (bug_jmp, 1);
}
cat (weight + 1);
}
last_was_variable = 0;
}
/* As we only use a single byte per unit at secondary level,
a single byte suffices as a level separator. */
if (tertiaryp || variable_shifted)
cat ('\x01');
}
if (tertiaryp)
{
last_was_variable = 0;
for (size_t i = 0; i < elements_count; i++)
{
if (variable_shifted_or_blanked
&& collation_element_is_variable (&elements[i]))
{
/* Skip at tertiary level. */
last_was_variable = 1;
continue;
}
if (last_was_variable)
{
/* Ignore completely - e.g. combining grave following a space. */
if (!elements[i].primary && elements[i].tertiary)
continue;
/* This could be a continuation element for a high secondary
weight. */
if (!elements[i].primary && elements[i].secondary)
continue;
}
uint8_t weight = elements[i].tertiary;
if (weight)
{
if (weight == 0xFF)
{
/* bug: tertiary weight too high */
errno = EINVAL;
longjmp (bug_jmp, 1);
}
cat (weight + 1);
}
}
/* As we only use a single byte per unit at tertiary level,
a single byte suffices as a level separator. */
if (variable_shifted)
cat ('\x01');
}
if (variable_shifted)
{
/* See UTS #10 c.4 "Variable Weighting". */
last_was_variable = 0;
for (size_t i = 0; i < elements_count; i++)
{
int variable_element = collation_element_is_variable (&elements[i]);
if (!elements[i].primary && !elements[i].secondary
&& !elements[i].tertiary)
{
/* Completely ignorable element. */
continue;
}
else if (!elements[i].primary
&& (elements[i].secondary || elements[i].tertiary)
&& last_was_variable)
{
/* E.g. combining grave following a space. Ignore. */
continue;
}
else if (variable_element)
{
uint16_t weight = elements[i].primary;
if (weight > 0xFE00)
{
/* bug: shifted primary weight too high */
errno = EINVAL;
longjmp (bug_jmp, 1);
}
cat ((weight / 0xFF) + 1);
cat ((weight % 0xFF) + 1);
}
else if (!elements[i].primary
&& (elements[i].secondary || elements[i].tertiary)
&& !last_was_variable)
{
/* This needs to be greater than any shifted weights. */
cat (0xFF);
cat (0xFF);
}
else if (elements[i].primary && !variable_element)
{
/* This needs to be greater than any shifted weights. */
cat (0xFF);
cat (0xFF);
}
last_was_variable = variable_element;
}
}
return psort_key;
#undef cat
}
static char *u32_make_collation_key_internal (Collation_choice collation,
const char32_t *codepoints_in, size_t length_in,
char *resultbuf, size_t *lengthp);
char *
u32_make_collation_key (Collation_choice collation,
const char32_t *codepoints_in, size_t length_in,
char *resultbuf, size_t *lengthp)
{
if (u32_make_key_streaming (collation,
codepoints_in, length_in, resultbuf, lengthp))
return resultbuf;
return u32_make_collation_key_internal (collation,
codepoints_in, length_in, resultbuf, lengthp);
}
static char *
u32_make_collation_key_internal (Collation_choice collation,
const char32_t *codepoints_in, size_t length_in,
char *resultbuf, size_t *lengthp)
{
char *sort_key;
int variable = (collation & UNICOLL_VARIABLE_MASK);
if (variable != UNICOLL_VARIABLE_NONIGNORABLE
&& variable != UNICOLL_VARIABLE_SHIFTED
&& variable != UNICOLL_VARIABLE_BLANKED)
{
errno = EINVAL;
return 0;
}
int variable_shifted = (variable == UNICOLL_VARIABLE_SHIFTED);
int variable_shifted_or_blanked = (variable == UNICOLL_VARIABLE_SHIFTED
|| variable == UNICOLL_VARIABLE_BLANKED);
int disable_sequence_lookup = (collation & UNICOLL_CONTRACTIONS_MASK);
int fix_length = 0;
char32_t *codepoints = NULL;
const char32_t *codepoints_const = NULL;
const char32_t *pcodepoints;
size_t length;
if (!(collation & UNICOLL_NORMALIZATION_MASK))
{
codepoints =
u32_normalize (UNINORM_NFD, codepoints_in, length_in, NULL, &length);
pcodepoints = codepoints;
}
else
{
codepoints_const = codepoints_in;
length = length_in;
pcodepoints = codepoints_const;
}
/* get array of collation entries */
struct collation_info
{
size_t string_index;
const struct collation_unit *array;
size_t num_elements;
};
/* Maximum one collation_info per character. Less if there are
multi-character sequences. */
struct collation_info *entry_array
= malloc (sizeof (*entry_array) * length);
if (!entry_array)
{
errno = ENOMEM;
sort_key = 0;
goto cleanup;
}
size_t n_entries = 0;
for (size_t i = 0; i < length;)
{
size_t n_consumed;
const struct collation_unit *data_array;
size_t data_num_elements;
lookup_collation_data_at_char (codepoints ? &codepoints[i] : 0,
codepoints_const ? &codepoints_const[i] : 0,
length - i,
disable_sequence_lookup,
&n_consumed,
&data_array,
&data_num_elements);
if (n_consumed > 0)
{
entry_array[n_entries].array = data_array;
entry_array[n_entries].num_elements = data_num_elements;
entry_array[n_entries++].string_index = i;
i += n_consumed;
}
else
{
entry_array[n_entries].array = 0;
entry_array[n_entries].num_elements = 0;
entry_array[n_entries++].string_index = i;
i++;
}
}
int num_elements = 0;
for (size_t i = 0; i < n_entries; i++)
{
if (entry_array[i].array)
num_elements += entry_array[i].num_elements;
else
num_elements += 3; /* implicitly determined weights? */
}
struct collation_unit *elements = calloc (num_elements, sizeof (*elements));
if (!elements)
{
errno = ENOMEM;
sort_key = 0;
goto cleanup;
}
size_t elements_count = 0;
for (size_t i = 0; i < n_entries; i++)
{
if (entry_array[i].array)
{
size_t num_entry_elements = entry_array[i].num_elements;
memcpy (&elements[elements_count],
entry_array[i].array,
num_entry_elements * sizeof (struct collation_unit));
elements_count += num_entry_elements;
}
else
{
size_t num_entry_elements;
get_implicit_weight
(pcodepoints[entry_array[i].string_index],
&elements[elements_count], &num_entry_elements);
elements_count += num_entry_elements;
}
}
free (entry_array);
char *psort_key;
size_t sort_key_alloc;
int primaryp, secondaryp, tertiaryp, quaternaryp;
primaryp = !(collation & UNICOLL_LEVEL1_BIT);
secondaryp = !(collation & UNICOLL_LEVEL2_BIT);
tertiaryp = !(collation & UNICOLL_LEVEL3_BIT);
quaternaryp = !(collation & UNICOLL_LEVEL4_BIT);
if (!quaternaryp)
variable_shifted = 0;
sort_key_alloc = 0;
if (primaryp)
{
/* Two bytes per collation element plus "\x01\x01" separator. */
sort_key_alloc += num_elements * 2;
if (secondaryp || tertiaryp || variable_shifted)
sort_key_alloc += 2;
}
if (secondaryp)
{
/* One byte per collation element plus "\x01" separator. */
sort_key_alloc += num_elements * 1;
if (tertiaryp || variable_shifted)
sort_key_alloc += 1;
}
if (tertiaryp)
{
/* One byte per collation element plus "\x01" separator. */
sort_key_alloc += num_elements * 1;
if (variable_shifted)
sort_key_alloc += 1;
}
if (variable_shifted)
{
/* Two bytes per collation element. */
sort_key_alloc += num_elements * 2;
}
/* Terminating null. */
sort_key_alloc++;
if (resultbuf && sort_key_alloc < *lengthp)
sort_key = resultbuf;
else
{
int partial_key_enabled = (collation & UNICOLL_PARTIAL_MASK);
if (!partial_key_enabled)
{
sort_key = malloc (sort_key_alloc);
if (!sort_key)
{
errno = ENOMEM;
goto cleanup;
}
}
else
{
sort_key = resultbuf;
fix_length = 1;
}
}
psort_key = sort_key;
jmp_buf bug_jmp;
if (!setjmp (bug_jmp))
{
psort_key = make_key_internal (psort_key, elements, elements_count,
variable_shifted_or_blanked, variable_shifted,
primaryp, secondaryp, tertiaryp,
bug_jmp,
fix_length ? &sort_key[*lengthp] : NULL);
*psort_key = '\0';
*lengthp = psort_key - sort_key + 1;
}
else
{
/* a bug was detected in the library */
sort_key = 0;
}
cleanup:
free (elements);
free (codepoints);
return sort_key;
}
char *
u8_make_collation_key (Collation_choice collation,
const uint8_t *u8_str, size_t length_in,
char *resultbuf, size_t *lengthp)
{
static char32_t *u32_str;
static size_t u32_len;
static char32_t *ret;
if (u8_make_key_streaming (collation,
u8_str, length_in, resultbuf, lengthp))
{
/* u8_make_key_streaming returns resultbuf or null. */
return resultbuf;
}
ret = u8_to_u32 (u8_str, length_in, u32_str, &u32_len);
if (ret != u32_str)
{
free (u32_str);
u32_str = ret;
}
char *key = u32_make_collation_key_internal (collation, u32_str, u32_len,
resultbuf, lengthp);
return key;
}