/src/postgres/src/common/unicode_norm.c
Line | Count | Source |
1 | | /*------------------------------------------------------------------------- |
2 | | * unicode_norm.c |
3 | | * Normalize a Unicode string |
4 | | * |
5 | | * This implements Unicode normalization, per the documentation at |
6 | | * https://www.unicode.org/reports/tr15/. |
7 | | * |
8 | | * Portions Copyright (c) 2017-2026, PostgreSQL Global Development Group |
9 | | * |
10 | | * IDENTIFICATION |
11 | | * src/common/unicode_norm.c |
12 | | * |
13 | | *------------------------------------------------------------------------- |
14 | | */ |
15 | | #ifndef FRONTEND |
16 | | #include "postgres.h" |
17 | | #else |
18 | | #include "postgres_fe.h" |
19 | | #endif |
20 | | |
21 | | #include "common/unicode_norm.h" |
22 | | #ifndef FRONTEND |
23 | | #include "common/unicode_norm_hashfunc.h" |
24 | | #include "common/unicode_normprops_table.h" |
25 | | #include "port/pg_bswap.h" |
26 | | #include "utils/memutils.h" |
27 | | #else |
28 | | #include "common/unicode_norm_table.h" |
29 | | #endif |
30 | | |
31 | | #ifndef FRONTEND |
32 | 0 | #define ALLOC(size) palloc(size) |
33 | 0 | #define FREE(size) pfree(size) |
34 | | #else |
35 | | #define ALLOC(size) malloc(size) |
36 | | #define FREE(size) free(size) |
37 | | #endif |
38 | | |
39 | | /* Constants for calculations with Hangul characters */ |
40 | 0 | #define SBASE 0xAC00 /* U+AC00 */ |
41 | 0 | #define LBASE 0x1100 /* U+1100 */ |
42 | 0 | #define VBASE 0x1161 /* U+1161 */ |
43 | 0 | #define TBASE 0x11A7 /* U+11A7 */ |
44 | 0 | #define LCOUNT 19 |
45 | 0 | #define VCOUNT 21 |
46 | 0 | #define TCOUNT 28 |
47 | 0 | #define NCOUNT VCOUNT * TCOUNT |
48 | 0 | #define SCOUNT LCOUNT * NCOUNT |
49 | | |
50 | | #ifdef FRONTEND |
51 | | /* comparison routine for bsearch() of decomposition lookup table. */ |
52 | | static int |
53 | | conv_compare(const void *p1, const void *p2) |
54 | | { |
55 | | uint32 v1, |
56 | | v2; |
57 | | |
58 | | v1 = *(const uint32 *) p1; |
59 | | v2 = ((const pg_unicode_decomposition *) p2)->codepoint; |
60 | | return (v1 > v2) ? 1 : ((v1 == v2) ? 0 : -1); |
61 | | } |
62 | | |
63 | | #endif |
64 | | |
65 | | /* |
66 | | * get_code_entry |
67 | | * |
68 | | * Get the entry corresponding to code in the decomposition lookup table. |
69 | | * The backend version of this code uses a perfect hash function for the |
70 | | * lookup, while the frontend version uses a binary search. |
71 | | */ |
72 | | static const pg_unicode_decomposition * |
73 | | get_code_entry(char32_t code) |
74 | 0 | { |
75 | 0 | #ifndef FRONTEND |
76 | 0 | int h; |
77 | 0 | uint32 hashkey; |
78 | 0 | pg_unicode_decompinfo decompinfo = UnicodeDecompInfo; |
79 | | |
80 | | /* |
81 | | * Compute the hash function. The hash key is the codepoint with the bytes |
82 | | * in network order. |
83 | | */ |
84 | 0 | hashkey = pg_hton32(code); |
85 | 0 | h = decompinfo.hash(&hashkey); |
86 | | |
87 | | /* An out-of-range result implies no match */ |
88 | 0 | if (h < 0 || h >= decompinfo.num_decomps) |
89 | 0 | return NULL; |
90 | | |
91 | | /* |
92 | | * Since it's a perfect hash, we need only match to the specific codepoint |
93 | | * it identifies. |
94 | | */ |
95 | 0 | if (code != decompinfo.decomps[h].codepoint) |
96 | 0 | return NULL; |
97 | | |
98 | | /* Success! */ |
99 | 0 | return &decompinfo.decomps[h]; |
100 | | #else |
101 | | return bsearch(&(code), |
102 | | UnicodeDecompMain, |
103 | | lengthof(UnicodeDecompMain), |
104 | | sizeof(pg_unicode_decomposition), |
105 | | conv_compare); |
106 | | #endif |
107 | 0 | } |
108 | | |
109 | | /* |
110 | | * Get the combining class of the given codepoint. |
111 | | */ |
112 | | static uint8 |
113 | | get_canonical_class(char32_t code) |
114 | 0 | { |
115 | 0 | const pg_unicode_decomposition *entry = get_code_entry(code); |
116 | | |
117 | | /* |
118 | | * If no entries are found, the character used is either a Hangul |
119 | | * character or a character with a class of 0 and no decompositions. |
120 | | */ |
121 | 0 | if (!entry) |
122 | 0 | return 0; |
123 | 0 | else |
124 | 0 | return entry->comb_class; |
125 | 0 | } |
126 | | |
127 | | /* |
128 | | * Given a decomposition entry looked up earlier, get the decomposed |
129 | | * characters. |
130 | | * |
131 | | * Note: the returned pointer can point to statically allocated buffer, and |
132 | | * is only valid until next call to this function! |
133 | | */ |
134 | | static const char32_t * |
135 | | get_code_decomposition(const pg_unicode_decomposition *entry, int *dec_size) |
136 | 0 | { |
137 | 0 | static char32_t x; |
138 | |
|
139 | 0 | if (DECOMPOSITION_IS_INLINE(entry)) |
140 | 0 | { |
141 | 0 | Assert(DECOMPOSITION_SIZE(entry) == 1); |
142 | 0 | x = (char32_t) entry->dec_index; |
143 | 0 | *dec_size = 1; |
144 | 0 | return &x; |
145 | 0 | } |
146 | 0 | else |
147 | 0 | { |
148 | 0 | *dec_size = DECOMPOSITION_SIZE(entry); |
149 | 0 | return &UnicodeDecomp_codepoints[entry->dec_index]; |
150 | 0 | } |
151 | 0 | } |
152 | | |
153 | | /* |
154 | | * Calculate how many characters a given character will decompose to. |
155 | | * |
156 | | * This needs to recurse, if the character decomposes into characters that |
157 | | * are, in turn, decomposable. |
158 | | */ |
159 | | static int |
160 | | get_decomposed_size(char32_t code, bool compat) |
161 | 0 | { |
162 | 0 | const pg_unicode_decomposition *entry; |
163 | 0 | int size = 0; |
164 | 0 | int i; |
165 | 0 | const uint32 *decomp; |
166 | 0 | int dec_size; |
167 | | |
168 | | /* |
169 | | * Fast path for Hangul characters not stored in tables to save memory as |
170 | | * decomposition is algorithmic. See |
171 | | * https://www.unicode.org/reports/tr15/tr15-18.html, annex 10 for details |
172 | | * on the matter. |
173 | | */ |
174 | 0 | if (code >= SBASE && code < SBASE + SCOUNT) |
175 | 0 | { |
176 | 0 | uint32 tindex, |
177 | 0 | sindex; |
178 | |
|
179 | 0 | sindex = code - SBASE; |
180 | 0 | tindex = sindex % TCOUNT; |
181 | |
|
182 | 0 | if (tindex != 0) |
183 | 0 | return 3; |
184 | 0 | return 2; |
185 | 0 | } |
186 | | |
187 | 0 | entry = get_code_entry(code); |
188 | | |
189 | | /* |
190 | | * Just count current code if no other decompositions. A NULL entry is |
191 | | * equivalent to a character with class 0 and no decompositions. |
192 | | */ |
193 | 0 | if (entry == NULL || DECOMPOSITION_SIZE(entry) == 0 || |
194 | 0 | (!compat && DECOMPOSITION_IS_COMPAT(entry))) |
195 | 0 | return 1; |
196 | | |
197 | | /* |
198 | | * If this entry has other decomposition codes look at them as well. First |
199 | | * get its decomposition in the list of tables available. |
200 | | */ |
201 | 0 | decomp = get_code_decomposition(entry, &dec_size); |
202 | 0 | for (i = 0; i < dec_size; i++) |
203 | 0 | { |
204 | 0 | uint32 lcode = decomp[i]; |
205 | |
|
206 | 0 | size += get_decomposed_size(lcode, compat); |
207 | 0 | } |
208 | |
|
209 | 0 | return size; |
210 | 0 | } |
211 | | |
212 | | /* |
213 | | * Recompose a set of characters. For hangul characters, the calculation |
214 | | * is algorithmic. For others, an inverse lookup at the decomposition |
215 | | * table is necessary. Returns true if a recomposition can be done, and |
216 | | * false otherwise. |
217 | | */ |
218 | | static bool |
219 | | recompose_code(uint32 start, uint32 code, uint32 *result) |
220 | 0 | { |
221 | | /* |
222 | | * Handle Hangul characters algorithmically, per the Unicode spec. |
223 | | * |
224 | | * Check if two current characters are L and V. |
225 | | */ |
226 | 0 | if (start >= LBASE && start < LBASE + LCOUNT && |
227 | 0 | code >= VBASE && code < VBASE + VCOUNT) |
228 | 0 | { |
229 | | /* make syllable of form LV */ |
230 | 0 | uint32 lindex = start - LBASE; |
231 | 0 | uint32 vindex = code - VBASE; |
232 | |
|
233 | 0 | *result = SBASE + (lindex * VCOUNT + vindex) * TCOUNT; |
234 | 0 | return true; |
235 | 0 | } |
236 | | /* Check if two current characters are LV and T */ |
237 | 0 | else if (start >= SBASE && start < (SBASE + SCOUNT) && |
238 | 0 | ((start - SBASE) % TCOUNT) == 0 && |
239 | 0 | code > TBASE && code < (TBASE + TCOUNT)) |
240 | 0 | { |
241 | | /* make syllable of form LVT */ |
242 | 0 | uint32 tindex = code - TBASE; |
243 | |
|
244 | 0 | *result = start + tindex; |
245 | 0 | return true; |
246 | 0 | } |
247 | 0 | else |
248 | 0 | { |
249 | 0 | const pg_unicode_decomposition *entry; |
250 | | |
251 | | /* |
252 | | * Do an inverse lookup of the decomposition tables to see if anything |
253 | | * matches. The comparison just needs to be a perfect match on the |
254 | | * sub-table of size two, because the start character has already been |
255 | | * recomposed partially. This lookup uses a perfect hash function for |
256 | | * the backend code. |
257 | | */ |
258 | 0 | #ifndef FRONTEND |
259 | |
|
260 | 0 | int h, |
261 | 0 | inv_lookup_index; |
262 | 0 | uint64 hashkey; |
263 | 0 | pg_unicode_recompinfo recompinfo = UnicodeRecompInfo; |
264 | | |
265 | | /* |
266 | | * Compute the hash function. The hash key is formed by concatenating |
267 | | * bytes of the two codepoints in network order. See also |
268 | | * src/common/unicode/generate-unicode_norm_table.pl. |
269 | | */ |
270 | 0 | hashkey = pg_hton64(((uint64) start << 32) | (uint64) code); |
271 | 0 | h = recompinfo.hash(&hashkey); |
272 | | |
273 | | /* An out-of-range result implies no match */ |
274 | 0 | if (h < 0 || h >= recompinfo.num_recomps) |
275 | 0 | return false; |
276 | | |
277 | 0 | inv_lookup_index = recompinfo.inverse_lookup[h]; |
278 | 0 | entry = &UnicodeDecompMain[inv_lookup_index]; |
279 | |
|
280 | 0 | if (start == UnicodeDecomp_codepoints[entry->dec_index] && |
281 | 0 | code == UnicodeDecomp_codepoints[entry->dec_index + 1]) |
282 | 0 | { |
283 | 0 | *result = entry->codepoint; |
284 | 0 | return true; |
285 | 0 | } |
286 | |
|
287 | | #else |
288 | | |
289 | | for (size_t i = 0; i < lengthof(UnicodeDecompMain); i++) |
290 | | { |
291 | | entry = &UnicodeDecompMain[i]; |
292 | | |
293 | | if (DECOMPOSITION_SIZE(entry) != 2) |
294 | | continue; |
295 | | |
296 | | if (DECOMPOSITION_NO_COMPOSE(entry)) |
297 | | continue; |
298 | | |
299 | | if (start == UnicodeDecomp_codepoints[entry->dec_index] && |
300 | | code == UnicodeDecomp_codepoints[entry->dec_index + 1]) |
301 | | { |
302 | | *result = entry->codepoint; |
303 | | return true; |
304 | | } |
305 | | } |
306 | | #endif /* !FRONTEND */ |
307 | 0 | } |
308 | | |
309 | 0 | return false; |
310 | 0 | } |
311 | | |
312 | | /* |
313 | | * Decompose the given code into the array given by caller. The |
314 | | * decomposition begins at the position given by caller, saving one |
315 | | * lookup on the decomposition table. The current position needs to be |
316 | | * updated here to let the caller know from where to continue filling |
317 | | * in the array result. |
318 | | */ |
319 | | static void |
320 | | decompose_code(char32_t code, bool compat, char32_t **result, int *current) |
321 | 0 | { |
322 | 0 | const pg_unicode_decomposition *entry; |
323 | 0 | int i; |
324 | 0 | const uint32 *decomp; |
325 | 0 | int dec_size; |
326 | | |
327 | | /* |
328 | | * Fast path for Hangul characters not stored in tables to save memory as |
329 | | * decomposition is algorithmic. See |
330 | | * https://www.unicode.org/reports/tr15/tr15-18.html, annex 10 for details |
331 | | * on the matter. |
332 | | */ |
333 | 0 | if (code >= SBASE && code < SBASE + SCOUNT) |
334 | 0 | { |
335 | 0 | uint32 l, |
336 | 0 | v, |
337 | 0 | tindex, |
338 | 0 | sindex; |
339 | 0 | char32_t *res = *result; |
340 | |
|
341 | 0 | sindex = code - SBASE; |
342 | 0 | l = LBASE + sindex / (VCOUNT * TCOUNT); |
343 | 0 | v = VBASE + (sindex % (VCOUNT * TCOUNT)) / TCOUNT; |
344 | 0 | tindex = sindex % TCOUNT; |
345 | |
|
346 | 0 | res[*current] = l; |
347 | 0 | (*current)++; |
348 | 0 | res[*current] = v; |
349 | 0 | (*current)++; |
350 | |
|
351 | 0 | if (tindex != 0) |
352 | 0 | { |
353 | 0 | res[*current] = TBASE + tindex; |
354 | 0 | (*current)++; |
355 | 0 | } |
356 | |
|
357 | 0 | return; |
358 | 0 | } |
359 | | |
360 | 0 | entry = get_code_entry(code); |
361 | | |
362 | | /* |
363 | | * Just fill in with the current decomposition if there are no |
364 | | * decomposition codes to recurse to. A NULL entry is equivalent to a |
365 | | * character with class 0 and no decompositions, so just leave also in |
366 | | * this case. |
367 | | */ |
368 | 0 | if (entry == NULL || DECOMPOSITION_SIZE(entry) == 0 || |
369 | 0 | (!compat && DECOMPOSITION_IS_COMPAT(entry))) |
370 | 0 | { |
371 | 0 | char32_t *res = *result; |
372 | |
|
373 | 0 | res[*current] = code; |
374 | 0 | (*current)++; |
375 | 0 | return; |
376 | 0 | } |
377 | | |
378 | | /* |
379 | | * If this entry has other decomposition codes look at them as well. |
380 | | */ |
381 | 0 | decomp = get_code_decomposition(entry, &dec_size); |
382 | 0 | for (i = 0; i < dec_size; i++) |
383 | 0 | { |
384 | 0 | char32_t lcode = (char32_t) decomp[i]; |
385 | | |
386 | | /* Leave if no more decompositions */ |
387 | 0 | decompose_code(lcode, compat, result, current); |
388 | 0 | } |
389 | 0 | } |
390 | | |
391 | | /* |
392 | | * unicode_normalize - Normalize a Unicode string to the specified form. |
393 | | * |
394 | | * The input is a 0-terminated array of codepoints. |
395 | | * |
396 | | * In frontend, returns a 0-terminated array of codepoints, allocated with |
397 | | * malloc. Or NULL if we run out of memory. In backend, the returned |
398 | | * string is palloc'd instead, and OOM is reported with ereport(). |
399 | | */ |
400 | | char32_t * |
401 | | unicode_normalize(UnicodeNormalizationForm form, const char32_t *input) |
402 | 0 | { |
403 | 0 | bool compat = (form == UNICODE_NFKC || form == UNICODE_NFKD); |
404 | 0 | bool recompose = (form == UNICODE_NFC || form == UNICODE_NFKC); |
405 | 0 | char32_t *decomp_chars; |
406 | 0 | char32_t *recomp_chars; |
407 | 0 | int decomp_size, |
408 | 0 | current_size; |
409 | 0 | int count; |
410 | 0 | const char32_t *p; |
411 | | |
412 | | /* variables for recomposition */ |
413 | 0 | int last_class; |
414 | 0 | int starter_pos; |
415 | 0 | int target_pos; |
416 | 0 | uint32 starter_ch; |
417 | | |
418 | | /* First, do character decomposition */ |
419 | | |
420 | | /* |
421 | | * Calculate how many characters long the decomposed version will be. |
422 | | * |
423 | | * Some characters decompose to quite a few code points, so that the |
424 | | * decomposed version's size could overrun MaxAllocSize, and even 32-bit |
425 | | * size_t, even though the input string presumably fits in that. In |
426 | | * frontend we want to just return NULL in that case, so monitor the sum |
427 | | * and exit early once we'd need more than MaxAllocSize bytes. |
428 | | */ |
429 | 0 | decomp_size = 0; |
430 | 0 | for (p = input; *p; p++) |
431 | 0 | { |
432 | 0 | decomp_size += get_decomposed_size(*p, compat); |
433 | 0 | if (unlikely(decomp_size > MaxAllocSize / sizeof(char32_t))) |
434 | 0 | { |
435 | 0 | #ifndef FRONTEND |
436 | | /* Exit loop and let palloc() throw error below */ |
437 | 0 | break; |
438 | | #else |
439 | | /* Just return NULL with no explicit error */ |
440 | | return NULL; |
441 | | #endif |
442 | 0 | } |
443 | 0 | } |
444 | |
|
445 | 0 | decomp_chars = (char32_t *) ALLOC((decomp_size + 1) * sizeof(char32_t)); |
446 | 0 | if (decomp_chars == NULL) |
447 | 0 | return NULL; |
448 | | |
449 | | /* |
450 | | * Now fill in each entry recursively. This needs a second pass on the |
451 | | * decomposition table. |
452 | | */ |
453 | 0 | current_size = 0; |
454 | 0 | for (p = input; *p; p++) |
455 | 0 | decompose_code(*p, compat, &decomp_chars, ¤t_size); |
456 | 0 | decomp_chars[decomp_size] = '\0'; |
457 | 0 | Assert(decomp_size == current_size); |
458 | | |
459 | | /* Leave if there is nothing to decompose */ |
460 | 0 | if (decomp_size == 0) |
461 | 0 | return decomp_chars; |
462 | | |
463 | | /* |
464 | | * Now apply canonical ordering. |
465 | | */ |
466 | 0 | for (count = 1; count < decomp_size; count++) |
467 | 0 | { |
468 | 0 | char32_t prev = decomp_chars[count - 1]; |
469 | 0 | char32_t next = decomp_chars[count]; |
470 | 0 | char32_t tmp; |
471 | 0 | const uint8 prevClass = get_canonical_class(prev); |
472 | 0 | const uint8 nextClass = get_canonical_class(next); |
473 | | |
474 | | /* |
475 | | * Per Unicode (https://www.unicode.org/reports/tr15/tr15-18.html) |
476 | | * annex 4, a sequence of two adjacent characters in a string is an |
477 | | * exchangeable pair if the combining class (from the Unicode |
478 | | * Character Database) for the first character is greater than the |
479 | | * combining class for the second, and the second is not a starter. A |
480 | | * character is a starter if its combining class is 0. |
481 | | */ |
482 | 0 | if (prevClass == 0 || nextClass == 0) |
483 | 0 | continue; |
484 | | |
485 | 0 | if (prevClass <= nextClass) |
486 | 0 | continue; |
487 | | |
488 | | /* exchange can happen */ |
489 | 0 | tmp = decomp_chars[count - 1]; |
490 | 0 | decomp_chars[count - 1] = decomp_chars[count]; |
491 | 0 | decomp_chars[count] = tmp; |
492 | | |
493 | | /* backtrack to check again */ |
494 | 0 | if (count > 1) |
495 | 0 | count -= 2; |
496 | 0 | } |
497 | |
|
498 | 0 | if (!recompose) |
499 | 0 | return decomp_chars; |
500 | | |
501 | | /* |
502 | | * The last phase of NFC and NFKC is the recomposition of the reordered |
503 | | * Unicode string using combining classes. The recomposed string cannot be |
504 | | * longer than the decomposed one, so make the allocation of the output |
505 | | * string based on that assumption. |
506 | | */ |
507 | 0 | recomp_chars = (char32_t *) ALLOC((decomp_size + 1) * sizeof(char32_t)); |
508 | 0 | if (!recomp_chars) |
509 | 0 | { |
510 | 0 | FREE(decomp_chars); |
511 | 0 | return NULL; |
512 | 0 | } |
513 | | |
514 | 0 | last_class = -1; /* this eliminates a special check */ |
515 | 0 | starter_pos = 0; |
516 | 0 | target_pos = 1; |
517 | 0 | starter_ch = recomp_chars[0] = decomp_chars[0]; |
518 | |
|
519 | 0 | for (count = 1; count < decomp_size; count++) |
520 | 0 | { |
521 | 0 | char32_t ch = decomp_chars[count]; |
522 | 0 | int ch_class = get_canonical_class(ch); |
523 | 0 | char32_t composite; |
524 | |
|
525 | 0 | if (last_class < ch_class && |
526 | 0 | recompose_code(starter_ch, ch, &composite)) |
527 | 0 | { |
528 | 0 | recomp_chars[starter_pos] = composite; |
529 | 0 | starter_ch = composite; |
530 | 0 | } |
531 | 0 | else if (ch_class == 0) |
532 | 0 | { |
533 | 0 | starter_pos = target_pos; |
534 | 0 | starter_ch = ch; |
535 | 0 | last_class = -1; |
536 | 0 | recomp_chars[target_pos++] = ch; |
537 | 0 | } |
538 | 0 | else |
539 | 0 | { |
540 | 0 | last_class = ch_class; |
541 | 0 | recomp_chars[target_pos++] = ch; |
542 | 0 | } |
543 | 0 | } |
544 | 0 | recomp_chars[target_pos] = (char32_t) '\0'; |
545 | |
|
546 | 0 | FREE(decomp_chars); |
547 | |
|
548 | 0 | return recomp_chars; |
549 | 0 | } |
550 | | |
551 | | /* |
552 | | * Normalization "quick check" algorithm; see |
553 | | * <http://www.unicode.org/reports/tr15/#Detecting_Normalization_Forms> |
554 | | */ |
555 | | |
556 | | /* We only need this in the backend. */ |
557 | | #ifndef FRONTEND |
558 | | |
559 | | static const pg_unicode_normprops * |
560 | | qc_hash_lookup(char32_t ch, const pg_unicode_norminfo *norminfo) |
561 | 0 | { |
562 | 0 | int h; |
563 | 0 | uint32 hashkey; |
564 | | |
565 | | /* |
566 | | * Compute the hash function. The hash key is the codepoint with the bytes |
567 | | * in network order. |
568 | | */ |
569 | 0 | hashkey = pg_hton32(ch); |
570 | 0 | h = norminfo->hash(&hashkey); |
571 | | |
572 | | /* An out-of-range result implies no match */ |
573 | 0 | if (h < 0 || h >= norminfo->num_normprops) |
574 | 0 | return NULL; |
575 | | |
576 | | /* |
577 | | * Since it's a perfect hash, we need only match to the specific codepoint |
578 | | * it identifies. |
579 | | */ |
580 | 0 | if (ch != norminfo->normprops[h].codepoint) |
581 | 0 | return NULL; |
582 | | |
583 | | /* Success! */ |
584 | 0 | return &norminfo->normprops[h]; |
585 | 0 | } |
586 | | |
587 | | /* |
588 | | * Look up the normalization quick check character property |
589 | | */ |
590 | | static UnicodeNormalizationQC |
591 | | qc_is_allowed(UnicodeNormalizationForm form, char32_t ch) |
592 | 0 | { |
593 | 0 | const pg_unicode_normprops *found = NULL; |
594 | |
|
595 | 0 | switch (form) |
596 | 0 | { |
597 | 0 | case UNICODE_NFC: |
598 | 0 | found = qc_hash_lookup(ch, &UnicodeNormInfo_NFC_QC); |
599 | 0 | break; |
600 | 0 | case UNICODE_NFKC: |
601 | 0 | found = qc_hash_lookup(ch, &UnicodeNormInfo_NFKC_QC); |
602 | 0 | break; |
603 | 0 | default: |
604 | 0 | Assert(false); |
605 | 0 | break; |
606 | 0 | } |
607 | | |
608 | 0 | if (found) |
609 | 0 | return found->quickcheck; |
610 | 0 | else |
611 | 0 | return UNICODE_NORM_QC_YES; |
612 | 0 | } |
613 | | |
614 | | UnicodeNormalizationQC |
615 | | unicode_is_normalized_quickcheck(UnicodeNormalizationForm form, const char32_t *input) |
616 | 0 | { |
617 | 0 | uint8 lastCanonicalClass = 0; |
618 | 0 | UnicodeNormalizationQC result = UNICODE_NORM_QC_YES; |
619 | | |
620 | | /* |
621 | | * For the "D" forms, we don't run the quickcheck. We don't include the |
622 | | * lookup tables for those because they are huge, checking for these |
623 | | * particular forms is less common, and running the slow path is faster |
624 | | * for the "D" forms than the "C" forms because you don't need to |
625 | | * recompose, which is slow. |
626 | | */ |
627 | 0 | if (form == UNICODE_NFD || form == UNICODE_NFKD) |
628 | 0 | return UNICODE_NORM_QC_MAYBE; |
629 | | |
630 | 0 | for (const char32_t *p = input; *p; p++) |
631 | 0 | { |
632 | 0 | char32_t ch = *p; |
633 | 0 | uint8 canonicalClass; |
634 | 0 | UnicodeNormalizationQC check; |
635 | |
|
636 | 0 | canonicalClass = get_canonical_class(ch); |
637 | 0 | if (lastCanonicalClass > canonicalClass && canonicalClass != 0) |
638 | 0 | return UNICODE_NORM_QC_NO; |
639 | | |
640 | 0 | check = qc_is_allowed(form, ch); |
641 | 0 | if (check == UNICODE_NORM_QC_NO) |
642 | 0 | return UNICODE_NORM_QC_NO; |
643 | 0 | else if (check == UNICODE_NORM_QC_MAYBE) |
644 | 0 | result = UNICODE_NORM_QC_MAYBE; |
645 | | |
646 | 0 | lastCanonicalClass = canonicalClass; |
647 | 0 | } |
648 | 0 | return result; |
649 | 0 | } |
650 | | |
651 | | #endif /* !FRONTEND */ |