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