1 /* $NetBSD: ucdata.c,v 1.3 2021/08/14 16:14:57 christos Exp $ */
2
3 /* $OpenLDAP$ */
4 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
5 *
6 * Copyright 1998-2021 The OpenLDAP Foundation.
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted only as authorized by the OpenLDAP
11 * Public License.
12 *
13 * A copy of this license is available in file LICENSE in the
14 * top-level directory of the distribution or, alternatively, at
15 * <http://www.OpenLDAP.org/license.html>.
16 */
17 /* Copyright 2001 Computing Research Labs, New Mexico State University
18 *
19 * Permission is hereby granted, free of charge, to any person obtaining a
20 * copy of this software and associated documentation files (the "Software"),
21 * to deal in the Software without restriction, including without limitation
22 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
23 * and/or sell copies of the Software, and to permit persons to whom the
24 * Software is furnished to do so, subject to the following conditions:
25 *
26 * The above copyright notice and this permission notice shall be included in
27 * all copies or substantial portions of the Software.
28 *
29 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
31 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
32 * THE COMPUTING RESEARCH LAB OR NEW MEXICO STATE UNIVERSITY BE LIABLE FOR ANY
33 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
34 * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
35 * THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36 */
37 /* Id: ucdata.c,v 1.4 2001/01/02 18:46:20 mleisher Exp " */
38
39 #include <sys/cdefs.h>
40 __RCSID("$NetBSD: ucdata.c,v 1.3 2021/08/14 16:14:57 christos Exp $");
41
42 #include "portable.h"
43 #include "ldap_config.h"
44
45 #include <stdio.h>
46 #include <ac/stdlib.h>
47 #include <ac/string.h>
48 #include <ac/unistd.h>
49
50 #include <ac/bytes.h>
51
52 #include "lber_pvt.h"
53 #include "ucdata.h"
54
55 #ifndef HARDCODE_DATA
56 #define HARDCODE_DATA 1
57 #endif
58
59 #if HARDCODE_DATA
60 #include "uctable.h"
61 #endif
62
63 /**************************************************************************
64 *
65 * Miscellaneous types, data, and support functions.
66 *
67 **************************************************************************/
68
69 typedef struct {
70 ac_uint2 bom;
71 ac_uint2 cnt;
72 union {
73 ac_uint4 bytes;
74 ac_uint2 len[2];
75 } size;
76 } _ucheader_t;
77
78 /*
79 * A simple array of 32-bit masks for lookup.
80 */
81 static ac_uint4 masks32[32] = {
82 0x00000001UL, 0x00000002UL, 0x00000004UL, 0x00000008UL,
83 0x00000010UL, 0x00000020UL, 0x00000040UL, 0x00000080UL,
84 0x00000100UL, 0x00000200UL, 0x00000400UL, 0x00000800UL,
85 0x00001000UL, 0x00002000UL, 0x00004000UL, 0x00008000UL,
86 0x00010000UL, 0x00020000UL, 0x00040000UL, 0x00080000UL,
87 0x00100000UL, 0x00200000UL, 0x00400000UL, 0x00800000UL,
88 0x01000000UL, 0x02000000UL, 0x04000000UL, 0x08000000UL,
89 0x10000000UL, 0x20000000UL, 0x40000000UL, 0x80000000UL
90 };
91
92 #define endian_short(cc) (((cc) >> 8) | (((cc) & 0xff) << 8))
93 #define endian_long(cc) ((((cc) & 0xff) << 24)|((((cc) >> 8) & 0xff) << 16)|\
94 ((((cc) >> 16) & 0xff) << 8)|((cc) >> 24))
95
96 #if !HARDCODE_DATA
97 static FILE *
_ucopenfile(char * paths,char * filename,char * mode)98 _ucopenfile(char *paths, char *filename, char *mode)
99 {
100 FILE *f;
101 char *fp, *dp, *pp, path[BUFSIZ];
102
103 if (filename == 0 || *filename == 0)
104 return 0;
105
106 dp = paths;
107 while (dp && *dp) {
108 pp = path;
109 while (*dp && *dp != ':')
110 *pp++ = *dp++;
111 *pp++ = *LDAP_DIRSEP;
112
113 fp = filename;
114 while (*fp)
115 *pp++ = *fp++;
116 *pp = 0;
117
118 if ((f = fopen(path, mode)) != 0)
119 return f;
120
121 if (*dp == ':')
122 dp++;
123 }
124
125 return 0;
126 }
127 #endif
128
129 /**************************************************************************
130 *
131 * Support for the character properties.
132 *
133 **************************************************************************/
134
135 #if !HARDCODE_DATA
136
137 static ac_uint4 _ucprop_size;
138 static ac_uint2 *_ucprop_offsets;
139 static ac_uint4 *_ucprop_ranges;
140
141 /*
142 * Return -1 on error, 0 if okay
143 */
144 static int
_ucprop_load(char * paths,int reload)145 _ucprop_load(char *paths, int reload)
146 {
147 FILE *in;
148 ac_uint4 size, i;
149 _ucheader_t hdr;
150
151 if (_ucprop_size > 0) {
152 if (!reload)
153 /*
154 * The character properties have already been loaded.
155 */
156 return 0;
157
158 /*
159 * Unload the current character property data in preparation for
160 * loading a new copy. Only the first array has to be deallocated
161 * because all the memory for the arrays is allocated as a single
162 * block.
163 */
164 free((char *) _ucprop_offsets);
165 _ucprop_size = 0;
166 }
167
168 if ((in = _ucopenfile(paths, "ctype.dat", "rb")) == 0)
169 return -1;
170
171 /*
172 * Load the header.
173 */
174 fread((char *) &hdr, sizeof(_ucheader_t), 1, in);
175
176 if (hdr.bom == 0xfffe) {
177 hdr.cnt = endian_short(hdr.cnt);
178 hdr.size.bytes = endian_long(hdr.size.bytes);
179 }
180
181 if ((_ucprop_size = hdr.cnt) == 0) {
182 fclose(in);
183 return -1;
184 }
185
186 /*
187 * Allocate all the storage needed for the lookup table.
188 */
189 _ucprop_offsets = (ac_uint2 *) malloc(hdr.size.bytes);
190
191 /*
192 * Calculate the offset into the storage for the ranges. The offsets
193 * array is on a 4-byte boundary and one larger than the value provided in
194 * the header count field. This means the offset to the ranges must be
195 * calculated after aligning the count to a 4-byte boundary.
196 */
197 if ((size = ((hdr.cnt + 1) * sizeof(ac_uint2))) & 3)
198 size += 4 - (size & 3);
199 size >>= 1;
200 _ucprop_ranges = (ac_uint4 *) (_ucprop_offsets + size);
201
202 /*
203 * Load the offset array.
204 */
205 fread((char *) _ucprop_offsets, sizeof(ac_uint2), size, in);
206
207 /*
208 * Do an endian swap if necessary. Don't forget there is an extra node on
209 * the end with the final index.
210 */
211 if (hdr.bom == 0xfffe) {
212 for (i = 0; i <= _ucprop_size; i++)
213 _ucprop_offsets[i] = endian_short(_ucprop_offsets[i]);
214 }
215
216 /*
217 * Load the ranges. The number of elements is in the last array position
218 * of the offsets.
219 */
220 fread((char *) _ucprop_ranges, sizeof(ac_uint4),
221 _ucprop_offsets[_ucprop_size], in);
222
223 fclose(in);
224
225 /*
226 * Do an endian swap if necessary.
227 */
228 if (hdr.bom == 0xfffe) {
229 for (i = 0; i < _ucprop_offsets[_ucprop_size]; i++)
230 _ucprop_ranges[i] = endian_long(_ucprop_ranges[i]);
231 }
232 return 0;
233 }
234
235 static void
_ucprop_unload(void)236 _ucprop_unload(void)
237 {
238 if (_ucprop_size == 0)
239 return;
240
241 /*
242 * Only need to free the offsets because the memory is allocated as a
243 * single block.
244 */
245 free((char *) _ucprop_offsets);
246 _ucprop_size = 0;
247 }
248 #endif
249
250 static int
_ucprop_lookup(ac_uint4 code,ac_uint4 n)251 _ucprop_lookup(ac_uint4 code, ac_uint4 n)
252 {
253 long l, r, m;
254
255 if (_ucprop_size == 0)
256 return 0;
257
258 /*
259 * There is an extra node on the end of the offsets to allow this routine
260 * to work right. If the index is 0xffff, then there are no nodes for the
261 * property.
262 */
263 if ((l = _ucprop_offsets[n]) == 0xffff)
264 return 0;
265
266 /*
267 * Locate the next offset that is not 0xffff. The sentinel at the end of
268 * the array is the max index value.
269 */
270 for (m = 1;
271 n + m < _ucprop_size && _ucprop_offsets[n + m] == 0xffff; m++) ;
272
273 r = _ucprop_offsets[n + m] - 1;
274
275 while (l <= r) {
276 /*
277 * Determine a "mid" point and adjust to make sure the mid point is at
278 * the beginning of a range pair.
279 */
280 m = (l + r) >> 1;
281 m -= (m & 1);
282 if (code > _ucprop_ranges[m + 1])
283 l = m + 2;
284 else if (code < _ucprop_ranges[m])
285 r = m - 2;
286 else if (code >= _ucprop_ranges[m] && code <= _ucprop_ranges[m + 1])
287 return 1;
288 }
289 return 0;
290 }
291
292 int
ucisprop(ac_uint4 code,ac_uint4 mask1,ac_uint4 mask2)293 ucisprop(ac_uint4 code, ac_uint4 mask1, ac_uint4 mask2)
294 {
295 ac_uint4 i;
296
297 if (mask1 == 0 && mask2 == 0)
298 return 0;
299
300 for (i = 0; mask1 && i < 32; i++) {
301 if ((mask1 & masks32[i]) && _ucprop_lookup(code, i))
302 return 1;
303 }
304
305 for (i = 32; mask2 && i < _ucprop_size; i++) {
306 if ((mask2 & masks32[i & 31]) && _ucprop_lookup(code, i))
307 return 1;
308 }
309
310 return 0;
311 }
312
313 /**************************************************************************
314 *
315 * Support for case mapping.
316 *
317 **************************************************************************/
318
319 #if !HARDCODE_DATA
320
321 /* These record the number of slots in the map.
322 * There are 3 words per slot.
323 */
324 static ac_uint4 _uccase_size;
325 static ac_uint2 _uccase_len[2];
326 static ac_uint4 *_uccase_map;
327
328 /*
329 * Return -1 on error, 0 if okay
330 */
331 static int
_uccase_load(char * paths,int reload)332 _uccase_load(char *paths, int reload)
333 {
334 FILE *in;
335 ac_uint4 i;
336 _ucheader_t hdr;
337
338 if (_uccase_size > 0) {
339 if (!reload)
340 /*
341 * The case mappings have already been loaded.
342 */
343 return 0;
344
345 free((char *) _uccase_map);
346 _uccase_size = 0;
347 }
348
349 if ((in = _ucopenfile(paths, "case.dat", "rb")) == 0)
350 return -1;
351
352 /*
353 * Load the header.
354 */
355 fread((char *) &hdr, sizeof(_ucheader_t), 1, in);
356
357 if (hdr.bom == 0xfffe) {
358 hdr.cnt = endian_short(hdr.cnt);
359 hdr.size.len[0] = endian_short(hdr.size.len[0]);
360 hdr.size.len[1] = endian_short(hdr.size.len[1]);
361 }
362
363 /*
364 * Set the node count and lengths of the upper and lower case mapping
365 * tables.
366 */
367 _uccase_size = hdr.cnt;
368 _uccase_len[0] = hdr.size.len[0];
369 _uccase_len[1] = hdr.size.len[1];
370
371 _uccase_map = (ac_uint4 *)
372 malloc(_uccase_size * 3 * sizeof(ac_uint4));
373
374 /*
375 * Load the case mapping table.
376 */
377 fread((char *) _uccase_map, sizeof(ac_uint4), _uccase_size * 3, in);
378
379 /*
380 * Do an endian swap if necessary.
381 */
382 if (hdr.bom == 0xfffe) {
383 for (i = 0; i < _uccase_size * 3; i++)
384 _uccase_map[i] = endian_long(_uccase_map[i]);
385 }
386 fclose(in);
387 return 0;
388 }
389
390 static void
_uccase_unload(void)391 _uccase_unload(void)
392 {
393 if (_uccase_size == 0)
394 return;
395
396 free((char *) _uccase_map);
397 _uccase_size = 0;
398 }
399 #endif
400
401 static ac_uint4
_uccase_lookup(ac_uint4 code,long l,long r,int field)402 _uccase_lookup(ac_uint4 code, long l, long r, int field)
403 {
404 long m;
405 const ac_uint4 *tmp;
406
407 /*
408 * Do the binary search.
409 */
410 while (l <= r) {
411 /*
412 * Determine a "mid" point and adjust to make sure the mid point is at
413 * the beginning of a case mapping triple.
414 */
415 m = (l + r) >> 1;
416 tmp = &_uccase_map[m*3];
417 if (code > *tmp)
418 l = m + 1;
419 else if (code < *tmp)
420 r = m - 1;
421 else if (code == *tmp)
422 return tmp[field];
423 }
424
425 return code;
426 }
427
428 ac_uint4
uctoupper(ac_uint4 code)429 uctoupper(ac_uint4 code)
430 {
431 int field;
432 long l, r;
433
434 if (ucisupper(code))
435 return code;
436
437 if (ucislower(code)) {
438 /*
439 * The character is lower case.
440 */
441 field = 2;
442 l = _uccase_len[0];
443 r = (l + _uccase_len[1]) - 1;
444 } else {
445 /*
446 * The character is title case.
447 */
448 field = 1;
449 l = _uccase_len[0] + _uccase_len[1];
450 r = _uccase_size - 1;
451 }
452 return _uccase_lookup(code, l, r, field);
453 }
454
455 ac_uint4
uctolower(ac_uint4 code)456 uctolower(ac_uint4 code)
457 {
458 int field;
459 long l, r;
460
461 if (ucislower(code))
462 return code;
463
464 if (ucisupper(code)) {
465 /*
466 * The character is upper case.
467 */
468 field = 1;
469 l = 0;
470 r = _uccase_len[0] - 1;
471 } else {
472 /*
473 * The character is title case.
474 */
475 field = 2;
476 l = _uccase_len[0] + _uccase_len[1];
477 r = _uccase_size - 1;
478 }
479 return _uccase_lookup(code, l, r, field);
480 }
481
482 ac_uint4
uctotitle(ac_uint4 code)483 uctotitle(ac_uint4 code)
484 {
485 int field;
486 long l, r;
487
488 if (ucistitle(code))
489 return code;
490
491 /*
492 * The offset will always be the same for converting to title case.
493 */
494 field = 2;
495
496 if (ucisupper(code)) {
497 /*
498 * The character is upper case.
499 */
500 l = 0;
501 r = _uccase_len[0] - 1;
502 } else {
503 /*
504 * The character is lower case.
505 */
506 l = _uccase_len[0];
507 r = (l + _uccase_len[1]) - 1;
508 }
509 return _uccase_lookup(code, l, r, field);
510 }
511
512 /**************************************************************************
513 *
514 * Support for compositions.
515 *
516 **************************************************************************/
517
518 #if !HARDCODE_DATA
519
520 static ac_uint4 _uccomp_size;
521 static ac_uint4 *_uccomp_data;
522
523 /*
524 * Return -1 on error, 0 if okay
525 */
526 static int
_uccomp_load(char * paths,int reload)527 _uccomp_load(char *paths, int reload)
528 {
529 FILE *in;
530 ac_uint4 size, i;
531 _ucheader_t hdr;
532
533 if (_uccomp_size > 0) {
534 if (!reload)
535 /*
536 * The compositions have already been loaded.
537 */
538 return 0;
539
540 free((char *) _uccomp_data);
541 _uccomp_size = 0;
542 }
543
544 if ((in = _ucopenfile(paths, "comp.dat", "rb")) == 0)
545 return -1;
546
547 /*
548 * Load the header.
549 */
550 fread((char *) &hdr, sizeof(_ucheader_t), 1, in);
551
552 if (hdr.bom == 0xfffe) {
553 hdr.cnt = endian_short(hdr.cnt);
554 hdr.size.bytes = endian_long(hdr.size.bytes);
555 }
556
557 _uccomp_size = hdr.cnt;
558 _uccomp_data = (ac_uint4 *) malloc(hdr.size.bytes);
559
560 /*
561 * Read the composition data in.
562 */
563 size = hdr.size.bytes / sizeof(ac_uint4);
564 fread((char *) _uccomp_data, sizeof(ac_uint4), size, in);
565
566 /*
567 * Do an endian swap if necessary.
568 */
569 if (hdr.bom == 0xfffe) {
570 for (i = 0; i < size; i++)
571 _uccomp_data[i] = endian_long(_uccomp_data[i]);
572 }
573
574 /*
575 * Assume that the data is ordered on count, so that all compositions
576 * of length 2 come first. Only handling length 2 for now.
577 */
578 for (i = 1; i < size; i += 4)
579 if (_uccomp_data[i] != 2)
580 break;
581 _uccomp_size = i - 1;
582
583 fclose(in);
584 return 0;
585 }
586
587 static void
_uccomp_unload(void)588 _uccomp_unload(void)
589 {
590 if (_uccomp_size == 0)
591 return;
592
593 free((char *) _uccomp_data);
594 _uccomp_size = 0;
595 }
596 #endif
597
598 int
uccomp(ac_uint4 node1,ac_uint4 node2,ac_uint4 * comp)599 uccomp(ac_uint4 node1, ac_uint4 node2, ac_uint4 *comp)
600 {
601 int l, r, m;
602
603 l = 0;
604 r = _uccomp_size - 1;
605
606 while (l <= r) {
607 m = ((r + l) >> 1);
608 m -= m & 3;
609 if (node1 > _uccomp_data[m+2])
610 l = m + 4;
611 else if (node1 < _uccomp_data[m+2])
612 r = m - 4;
613 else if (node2 > _uccomp_data[m+3])
614 l = m + 4;
615 else if (node2 < _uccomp_data[m+3])
616 r = m - 4;
617 else {
618 *comp = _uccomp_data[m];
619 return 1;
620 }
621 }
622 return 0;
623 }
624
625 int
uccomp_hangul(ac_uint4 * str,int len)626 uccomp_hangul(ac_uint4 *str, int len)
627 {
628 const int SBase = 0xAC00, LBase = 0x1100,
629 VBase = 0x1161, TBase = 0x11A7,
630 LCount = 19, VCount = 21, TCount = 28,
631 NCount = VCount * TCount, /* 588 */
632 SCount = LCount * NCount; /* 11172 */
633
634 int i, rlen;
635 ac_uint4 ch, last, lindex, sindex;
636
637 last = str[0];
638 rlen = 1;
639 for ( i = 1; i < len; i++ ) {
640 ch = str[i];
641
642 /* check if two current characters are L and V */
643 lindex = last - LBase;
644 if (lindex < (ac_uint4) LCount) {
645 ac_uint4 vindex = ch - VBase;
646 if (vindex < (ac_uint4) VCount) {
647 /* make syllable of form LV */
648 last = SBase + (lindex * VCount + vindex) * TCount;
649 str[rlen-1] = last; /* reset last */
650 continue;
651 }
652 }
653
654 /* check if two current characters are LV and T */
655 sindex = last - SBase;
656 if (sindex < (ac_uint4) SCount
657 && (sindex % TCount) == 0)
658 {
659 ac_uint4 tindex = ch - TBase;
660 if (tindex <= (ac_uint4) TCount) {
661 /* make syllable of form LVT */
662 last += tindex;
663 str[rlen-1] = last; /* reset last */
664 continue;
665 }
666 }
667
668 /* if neither case was true, just add the character */
669 last = ch;
670 str[rlen] = ch;
671 rlen++;
672 }
673 return rlen;
674 }
675
676 int
uccanoncomp(ac_uint4 * str,int len)677 uccanoncomp(ac_uint4 *str, int len)
678 {
679 int i, stpos, copos;
680 ac_uint4 cl, prevcl, st, ch, co;
681
682 st = str[0];
683 stpos = 0;
684 copos = 1;
685 prevcl = uccombining_class(st) == 0 ? 0 : 256;
686
687 for (i = 1; i < len; i++) {
688 ch = str[i];
689 cl = uccombining_class(ch);
690 if (uccomp(st, ch, &co) && (prevcl < cl || prevcl == 0))
691 st = str[stpos] = co;
692 else {
693 if (cl == 0) {
694 stpos = copos;
695 st = ch;
696 }
697 prevcl = cl;
698 str[copos++] = ch;
699 }
700 }
701
702 return uccomp_hangul(str, copos);
703 }
704
705 /**************************************************************************
706 *
707 * Support for decompositions.
708 *
709 **************************************************************************/
710
711 #if !HARDCODE_DATA
712
713 static ac_uint4 _ucdcmp_size;
714 static ac_uint4 *_ucdcmp_nodes;
715 static ac_uint4 *_ucdcmp_decomp;
716
717 static ac_uint4 _uckdcmp_size;
718 static ac_uint4 *_uckdcmp_nodes;
719 static ac_uint4 *_uckdcmp_decomp;
720
721 /*
722 * Return -1 on error, 0 if okay
723 */
724 static int
_ucdcmp_load(char * paths,int reload)725 _ucdcmp_load(char *paths, int reload)
726 {
727 FILE *in;
728 ac_uint4 size, i;
729 _ucheader_t hdr;
730
731 if (_ucdcmp_size > 0) {
732 if (!reload)
733 /*
734 * The decompositions have already been loaded.
735 */
736 return 0;
737
738 free((char *) _ucdcmp_nodes);
739 _ucdcmp_size = 0;
740 }
741
742 if ((in = _ucopenfile(paths, "decomp.dat", "rb")) == 0)
743 return -1;
744
745 /*
746 * Load the header.
747 */
748 fread((char *) &hdr, sizeof(_ucheader_t), 1, in);
749
750 if (hdr.bom == 0xfffe) {
751 hdr.cnt = endian_short(hdr.cnt);
752 hdr.size.bytes = endian_long(hdr.size.bytes);
753 }
754
755 _ucdcmp_size = hdr.cnt << 1;
756 _ucdcmp_nodes = (ac_uint4 *) malloc(hdr.size.bytes);
757 _ucdcmp_decomp = _ucdcmp_nodes + (_ucdcmp_size + 1);
758
759 /*
760 * Read the decomposition data in.
761 */
762 size = hdr.size.bytes / sizeof(ac_uint4);
763 fread((char *) _ucdcmp_nodes, sizeof(ac_uint4), size, in);
764
765 /*
766 * Do an endian swap if necessary.
767 */
768 if (hdr.bom == 0xfffe) {
769 for (i = 0; i < size; i++)
770 _ucdcmp_nodes[i] = endian_long(_ucdcmp_nodes[i]);
771 }
772 fclose(in);
773 return 0;
774 }
775
776 /*
777 * Return -1 on error, 0 if okay
778 */
779 static int
_uckdcmp_load(char * paths,int reload)780 _uckdcmp_load(char *paths, int reload)
781 {
782 FILE *in;
783 ac_uint4 size, i;
784 _ucheader_t hdr;
785
786 if (_uckdcmp_size > 0) {
787 if (!reload)
788 /*
789 * The decompositions have already been loaded.
790 */
791 return 0;
792
793 free((char *) _uckdcmp_nodes);
794 _uckdcmp_size = 0;
795 }
796
797 if ((in = _ucopenfile(paths, "kdecomp.dat", "rb")) == 0)
798 return -1;
799
800 /*
801 * Load the header.
802 */
803 fread((char *) &hdr, sizeof(_ucheader_t), 1, in);
804
805 if (hdr.bom == 0xfffe) {
806 hdr.cnt = endian_short(hdr.cnt);
807 hdr.size.bytes = endian_long(hdr.size.bytes);
808 }
809
810 _uckdcmp_size = hdr.cnt << 1;
811 _uckdcmp_nodes = (ac_uint4 *) malloc(hdr.size.bytes);
812 _uckdcmp_decomp = _uckdcmp_nodes + (_uckdcmp_size + 1);
813
814 /*
815 * Read the decomposition data in.
816 */
817 size = hdr.size.bytes / sizeof(ac_uint4);
818 fread((char *) _uckdcmp_nodes, sizeof(ac_uint4), size, in);
819
820 /*
821 * Do an endian swap if necessary.
822 */
823 if (hdr.bom == 0xfffe) {
824 for (i = 0; i < size; i++)
825 _uckdcmp_nodes[i] = endian_long(_uckdcmp_nodes[i]);
826 }
827 fclose(in);
828 return 0;
829 }
830
831 static void
_ucdcmp_unload(void)832 _ucdcmp_unload(void)
833 {
834 if (_ucdcmp_size == 0)
835 return;
836
837 /*
838 * Only need to free the offsets because the memory is allocated as a
839 * single block.
840 */
841 free((char *) _ucdcmp_nodes);
842 _ucdcmp_size = 0;
843 }
844
845 static void
_uckdcmp_unload(void)846 _uckdcmp_unload(void)
847 {
848 if (_uckdcmp_size == 0)
849 return;
850
851 /*
852 * Only need to free the offsets because the memory is allocated as a
853 * single block.
854 */
855 free((char *) _uckdcmp_nodes);
856 _uckdcmp_size = 0;
857 }
858 #endif
859
860 int
ucdecomp(ac_uint4 code,ac_uint4 * num,ac_uint4 ** decomp)861 ucdecomp(ac_uint4 code, ac_uint4 *num, ac_uint4 **decomp)
862 {
863 long l, r, m;
864
865 if (code < _ucdcmp_nodes[0]) {
866 return 0;
867 }
868
869 l = 0;
870 r = _ucdcmp_nodes[_ucdcmp_size] - 1;
871
872 while (l <= r) {
873 /*
874 * Determine a "mid" point and adjust to make sure the mid point is at
875 * the beginning of a code+offset pair.
876 */
877 m = (l + r) >> 1;
878 m -= (m & 1);
879 if (code > _ucdcmp_nodes[m])
880 l = m + 2;
881 else if (code < _ucdcmp_nodes[m])
882 r = m - 2;
883 else if (code == _ucdcmp_nodes[m]) {
884 *num = _ucdcmp_nodes[m + 3] - _ucdcmp_nodes[m + 1];
885 *decomp = (ac_uint4*)&_ucdcmp_decomp[_ucdcmp_nodes[m + 1]];
886 return 1;
887 }
888 }
889 return 0;
890 }
891
892 int
uckdecomp(ac_uint4 code,ac_uint4 * num,ac_uint4 ** decomp)893 uckdecomp(ac_uint4 code, ac_uint4 *num, ac_uint4 **decomp)
894 {
895 long l, r, m;
896
897 if (code < _uckdcmp_nodes[0]) {
898 return 0;
899 }
900
901 l = 0;
902 r = _uckdcmp_nodes[_uckdcmp_size] - 1;
903
904 while (l <= r) {
905 /*
906 * Determine a "mid" point and adjust to make sure the mid point is at
907 * the beginning of a code+offset pair.
908 */
909 m = (l + r) >> 1;
910 m -= (m & 1);
911 if (code > _uckdcmp_nodes[m])
912 l = m + 2;
913 else if (code < _uckdcmp_nodes[m])
914 r = m - 2;
915 else if (code == _uckdcmp_nodes[m]) {
916 *num = _uckdcmp_nodes[m + 3] - _uckdcmp_nodes[m + 1];
917 *decomp = (ac_uint4*)&_uckdcmp_decomp[_uckdcmp_nodes[m + 1]];
918 return 1;
919 }
920 }
921 return 0;
922 }
923
924 int
ucdecomp_hangul(ac_uint4 code,ac_uint4 * num,ac_uint4 decomp[])925 ucdecomp_hangul(ac_uint4 code, ac_uint4 *num, ac_uint4 decomp[])
926 {
927 if (!ucishangul(code))
928 return 0;
929
930 code -= 0xac00;
931 decomp[0] = 0x1100 + (ac_uint4) (code / 588);
932 decomp[1] = 0x1161 + (ac_uint4) ((code % 588) / 28);
933 decomp[2] = 0x11a7 + (ac_uint4) (code % 28);
934 *num = (decomp[2] != 0x11a7) ? 3 : 2;
935
936 return 1;
937 }
938
939 /* mode == 0 for canonical, mode == 1 for compatibility */
940 static int
uccanoncompatdecomp(const ac_uint4 * in,int inlen,ac_uint4 ** out,int * outlen,short mode,void * ctx)941 uccanoncompatdecomp(const ac_uint4 *in, int inlen,
942 ac_uint4 **out, int *outlen, short mode, void *ctx)
943 {
944 int l, size;
945 unsigned i, j, k;
946 ac_uint4 num, class, *decomp, hangdecomp[3];
947
948 size = inlen * 2;
949 *out = (ac_uint4 *) ber_memalloc_x(size * sizeof(**out), ctx);
950 if (*out == NULL)
951 return *outlen = -1;
952
953 i = 0;
954 for (j = 0; j < (unsigned) inlen; j++) {
955 if (mode ? uckdecomp(in[j], &num, &decomp) : ucdecomp(in[j], &num, &decomp)) {
956 if ( size - i < num) {
957 size = inlen + i - j + num - 1;
958 *out = (ac_uint4 *) ber_memrealloc_x(*out, size * sizeof(**out), ctx );
959 if (*out == NULL)
960 return *outlen = -1;
961 }
962 for (k = 0; k < num; k++) {
963 class = uccombining_class(decomp[k]);
964 if (class == 0) {
965 (*out)[i] = decomp[k];
966 } else {
967 for (l = i; l > 0; l--)
968 if (class >= uccombining_class((*out)[l-1]))
969 break;
970 AC_MEMCPY(*out + l + 1, *out + l, (i - l) * sizeof(**out));
971 (*out)[l] = decomp[k];
972 }
973 i++;
974 }
975 } else if (ucdecomp_hangul(in[j], &num, hangdecomp)) {
976 if (size - i < num) {
977 size = inlen + i - j + num - 1;
978 *out = (ac_uint4 *) ber_memrealloc_x(*out, size * sizeof(**out), ctx);
979 if (*out == NULL)
980 return *outlen = -1;
981 }
982 for (k = 0; k < num; k++) {
983 (*out)[i] = hangdecomp[k];
984 i++;
985 }
986 } else {
987 if (size - i < 1) {
988 size = inlen + i - j;
989 *out = (ac_uint4 *) ber_memrealloc_x(*out, size * sizeof(**out), ctx);
990 if (*out == NULL)
991 return *outlen = -1;
992 }
993 class = uccombining_class(in[j]);
994 if (class == 0) {
995 (*out)[i] = in[j];
996 } else {
997 for (l = i; l > 0; l--)
998 if (class >= uccombining_class((*out)[l-1]))
999 break;
1000 AC_MEMCPY(*out + l + 1, *out + l, (i - l) * sizeof(**out));
1001 (*out)[l] = in[j];
1002 }
1003 i++;
1004 }
1005 }
1006 return *outlen = i;
1007 }
1008
1009 int
uccanondecomp(const ac_uint4 * in,int inlen,ac_uint4 ** out,int * outlen,void * ctx)1010 uccanondecomp(const ac_uint4 *in, int inlen,
1011 ac_uint4 **out, int *outlen, void *ctx)
1012 {
1013 return uccanoncompatdecomp(in, inlen, out, outlen, 0, ctx);
1014 }
1015
1016 int
uccompatdecomp(const ac_uint4 * in,int inlen,ac_uint4 ** out,int * outlen,void * ctx)1017 uccompatdecomp(const ac_uint4 *in, int inlen,
1018 ac_uint4 **out, int *outlen, void *ctx)
1019 {
1020 return uccanoncompatdecomp(in, inlen, out, outlen, 1, ctx);
1021 }
1022
1023 /**************************************************************************
1024 *
1025 * Support for combining classes.
1026 *
1027 **************************************************************************/
1028
1029 #if !HARDCODE_DATA
1030 static ac_uint4 _uccmcl_size;
1031 static ac_uint4 *_uccmcl_nodes;
1032
1033 /*
1034 * Return -1 on error, 0 if okay
1035 */
1036 static int
_uccmcl_load(char * paths,int reload)1037 _uccmcl_load(char *paths, int reload)
1038 {
1039 FILE *in;
1040 ac_uint4 i;
1041 _ucheader_t hdr;
1042
1043 if (_uccmcl_size > 0) {
1044 if (!reload)
1045 /*
1046 * The combining classes have already been loaded.
1047 */
1048 return 0;
1049
1050 free((char *) _uccmcl_nodes);
1051 _uccmcl_size = 0;
1052 }
1053
1054 if ((in = _ucopenfile(paths, "cmbcl.dat", "rb")) == 0)
1055 return -1;
1056
1057 /*
1058 * Load the header.
1059 */
1060 fread((char *) &hdr, sizeof(_ucheader_t), 1, in);
1061
1062 if (hdr.bom == 0xfffe) {
1063 hdr.cnt = endian_short(hdr.cnt);
1064 hdr.size.bytes = endian_long(hdr.size.bytes);
1065 }
1066
1067 _uccmcl_size = hdr.cnt * 3;
1068 _uccmcl_nodes = (ac_uint4 *) malloc(hdr.size.bytes);
1069
1070 /*
1071 * Read the combining classes in.
1072 */
1073 fread((char *) _uccmcl_nodes, sizeof(ac_uint4), _uccmcl_size, in);
1074
1075 /*
1076 * Do an endian swap if necessary.
1077 */
1078 if (hdr.bom == 0xfffe) {
1079 for (i = 0; i < _uccmcl_size; i++)
1080 _uccmcl_nodes[i] = endian_long(_uccmcl_nodes[i]);
1081 }
1082 fclose(in);
1083 return 0;
1084 }
1085
1086 static void
_uccmcl_unload(void)1087 _uccmcl_unload(void)
1088 {
1089 if (_uccmcl_size == 0)
1090 return;
1091
1092 free((char *) _uccmcl_nodes);
1093 _uccmcl_size = 0;
1094 }
1095 #endif
1096
1097 ac_uint4
uccombining_class(ac_uint4 code)1098 uccombining_class(ac_uint4 code)
1099 {
1100 long l, r, m;
1101
1102 l = 0;
1103 r = _uccmcl_size - 1;
1104
1105 while (l <= r) {
1106 m = (l + r) >> 1;
1107 m -= (m % 3);
1108 if (code > _uccmcl_nodes[m + 1])
1109 l = m + 3;
1110 else if (code < _uccmcl_nodes[m])
1111 r = m - 3;
1112 else if (code >= _uccmcl_nodes[m] && code <= _uccmcl_nodes[m + 1])
1113 return _uccmcl_nodes[m + 2];
1114 }
1115 return 0;
1116 }
1117
1118 /**************************************************************************
1119 *
1120 * Support for numeric values.
1121 *
1122 **************************************************************************/
1123
1124 #if !HARDCODE_DATA
1125 static ac_uint4 *_ucnum_nodes;
1126 static ac_uint4 _ucnum_size;
1127 static short *_ucnum_vals;
1128
1129 /*
1130 * Return -1 on error, 0 if okay
1131 */
1132 static int
_ucnumb_load(char * paths,int reload)1133 _ucnumb_load(char *paths, int reload)
1134 {
1135 FILE *in;
1136 ac_uint4 size, i;
1137 _ucheader_t hdr;
1138
1139 if (_ucnum_size > 0) {
1140 if (!reload)
1141 /*
1142 * The numbers have already been loaded.
1143 */
1144 return 0;
1145
1146 free((char *) _ucnum_nodes);
1147 _ucnum_size = 0;
1148 }
1149
1150 if ((in = _ucopenfile(paths, "num.dat", "rb")) == 0)
1151 return -1;
1152
1153 /*
1154 * Load the header.
1155 */
1156 fread((char *) &hdr, sizeof(_ucheader_t), 1, in);
1157
1158 if (hdr.bom == 0xfffe) {
1159 hdr.cnt = endian_short(hdr.cnt);
1160 hdr.size.bytes = endian_long(hdr.size.bytes);
1161 }
1162
1163 _ucnum_size = hdr.cnt;
1164 _ucnum_nodes = (ac_uint4 *) malloc(hdr.size.bytes);
1165 _ucnum_vals = (short *) (_ucnum_nodes + _ucnum_size);
1166
1167 /*
1168 * Read the combining classes in.
1169 */
1170 fread((char *) _ucnum_nodes, sizeof(unsigned char), hdr.size.bytes, in);
1171
1172 /*
1173 * Do an endian swap if necessary.
1174 */
1175 if (hdr.bom == 0xfffe) {
1176 for (i = 0; i < _ucnum_size; i++)
1177 _ucnum_nodes[i] = endian_long(_ucnum_nodes[i]);
1178
1179 /*
1180 * Determine the number of values that have to be adjusted.
1181 */
1182 size = (hdr.size.bytes -
1183 (_ucnum_size * (sizeof(ac_uint4) << 1))) /
1184 sizeof(short);
1185
1186 for (i = 0; i < size; i++)
1187 _ucnum_vals[i] = endian_short(_ucnum_vals[i]);
1188 }
1189 fclose(in);
1190 return 0;
1191 }
1192
1193 static void
_ucnumb_unload(void)1194 _ucnumb_unload(void)
1195 {
1196 if (_ucnum_size == 0)
1197 return;
1198
1199 free((char *) _ucnum_nodes);
1200 _ucnum_size = 0;
1201 }
1202 #endif
1203
1204 int
ucnumber_lookup(ac_uint4 code,struct ucnumber * num)1205 ucnumber_lookup(ac_uint4 code, struct ucnumber *num)
1206 {
1207 long l, r, m;
1208 short *vp;
1209
1210 l = 0;
1211 r = _ucnum_size - 1;
1212 while (l <= r) {
1213 /*
1214 * Determine a "mid" point and adjust to make sure the mid point is at
1215 * the beginning of a code+offset pair.
1216 */
1217 m = (l + r) >> 1;
1218 m -= (m & 1);
1219 if (code > _ucnum_nodes[m])
1220 l = m + 2;
1221 else if (code < _ucnum_nodes[m])
1222 r = m - 2;
1223 else {
1224 vp = (short *)_ucnum_vals + _ucnum_nodes[m + 1];
1225 num->numerator = (int) *vp++;
1226 num->denominator = (int) *vp;
1227 return 1;
1228 }
1229 }
1230 return 0;
1231 }
1232
1233 int
ucdigit_lookup(ac_uint4 code,int * digit)1234 ucdigit_lookup(ac_uint4 code, int *digit)
1235 {
1236 long l, r, m;
1237 short *vp;
1238
1239 l = 0;
1240 r = _ucnum_size - 1;
1241 while (l <= r) {
1242 /*
1243 * Determine a "mid" point and adjust to make sure the mid point is at
1244 * the beginning of a code+offset pair.
1245 */
1246 m = (l + r) >> 1;
1247 m -= (m & 1);
1248 if (code > _ucnum_nodes[m])
1249 l = m + 2;
1250 else if (code < _ucnum_nodes[m])
1251 r = m - 2;
1252 else {
1253 vp = (short *)_ucnum_vals + _ucnum_nodes[m + 1];
1254 if (*vp == *(vp + 1)) {
1255 *digit = *vp;
1256 return 1;
1257 }
1258 return 0;
1259 }
1260 }
1261 return 0;
1262 }
1263
1264 struct ucnumber
ucgetnumber(ac_uint4 code)1265 ucgetnumber(ac_uint4 code)
1266 {
1267 struct ucnumber num;
1268
1269 /*
1270 * Initialize with some arbitrary value, because the caller simply cannot
1271 * tell for sure if the code is a number without calling the ucisnumber()
1272 * macro before calling this function.
1273 */
1274 num.numerator = num.denominator = -111;
1275
1276 (void) ucnumber_lookup(code, &num);
1277
1278 return num;
1279 }
1280
1281 int
ucgetdigit(ac_uint4 code)1282 ucgetdigit(ac_uint4 code)
1283 {
1284 int dig;
1285
1286 /*
1287 * Initialize with some arbitrary value, because the caller simply cannot
1288 * tell for sure if the code is a number without calling the ucisdigit()
1289 * macro before calling this function.
1290 */
1291 dig = -111;
1292
1293 (void) ucdigit_lookup(code, &dig);
1294
1295 return dig;
1296 }
1297
1298 /**************************************************************************
1299 *
1300 * Setup and cleanup routines.
1301 *
1302 **************************************************************************/
1303
1304 #if HARDCODE_DATA
ucdata_load(char * paths,int masks)1305 int ucdata_load(char *paths, int masks) { return 0; }
ucdata_unload(int masks)1306 void ucdata_unload(int masks) { }
ucdata_reload(char * paths,int masks)1307 int ucdata_reload(char *paths, int masks) { return 0; }
1308 #else
1309 /*
1310 * Return 0 if okay, negative on error
1311 */
1312 int
ucdata_load(char * paths,int masks)1313 ucdata_load(char *paths, int masks)
1314 {
1315 int error = 0;
1316
1317 if (masks & UCDATA_CTYPE)
1318 error |= _ucprop_load(paths, 0) < 0 ? UCDATA_CTYPE : 0;
1319 if (masks & UCDATA_CASE)
1320 error |= _uccase_load(paths, 0) < 0 ? UCDATA_CASE : 0;
1321 if (masks & UCDATA_DECOMP)
1322 error |= _ucdcmp_load(paths, 0) < 0 ? UCDATA_DECOMP : 0;
1323 if (masks & UCDATA_CMBCL)
1324 error |= _uccmcl_load(paths, 0) < 0 ? UCDATA_CMBCL : 0;
1325 if (masks & UCDATA_NUM)
1326 error |= _ucnumb_load(paths, 0) < 0 ? UCDATA_NUM : 0;
1327 if (masks & UCDATA_COMP)
1328 error |= _uccomp_load(paths, 0) < 0 ? UCDATA_COMP : 0;
1329 if (masks & UCDATA_KDECOMP)
1330 error |= _uckdcmp_load(paths, 0) < 0 ? UCDATA_KDECOMP : 0;
1331
1332 return -error;
1333 }
1334
1335 void
ucdata_unload(int masks)1336 ucdata_unload(int masks)
1337 {
1338 if (masks & UCDATA_CTYPE)
1339 _ucprop_unload();
1340 if (masks & UCDATA_CASE)
1341 _uccase_unload();
1342 if (masks & UCDATA_DECOMP)
1343 _ucdcmp_unload();
1344 if (masks & UCDATA_CMBCL)
1345 _uccmcl_unload();
1346 if (masks & UCDATA_NUM)
1347 _ucnumb_unload();
1348 if (masks & UCDATA_COMP)
1349 _uccomp_unload();
1350 if (masks & UCDATA_KDECOMP)
1351 _uckdcmp_unload();
1352 }
1353
1354 /*
1355 * Return 0 if okay, negative on error
1356 */
1357 int
ucdata_reload(char * paths,int masks)1358 ucdata_reload(char *paths, int masks)
1359 {
1360 int error = 0;
1361
1362 if (masks & UCDATA_CTYPE)
1363 error |= _ucprop_load(paths, 1) < 0 ? UCDATA_CTYPE : 0;
1364 if (masks & UCDATA_CASE)
1365 error |= _uccase_load(paths, 1) < 0 ? UCDATA_CASE : 0;
1366 if (masks & UCDATA_DECOMP)
1367 error |= _ucdcmp_load(paths, 1) < 0 ? UCDATA_DECOMP : 0;
1368 if (masks & UCDATA_CMBCL)
1369 error |= _uccmcl_load(paths, 1) < 0 ? UCDATA_CMBCL : 0;
1370 if (masks & UCDATA_NUM)
1371 error |= _ucnumb_load(paths, 1) < 0 ? UCDATA_NUM : 0;
1372 if (masks & UCDATA_COMP)
1373 error |= _uccomp_load(paths, 1) < 0 ? UCDATA_COMP : 0;
1374 if (masks & UCDATA_KDECOMP)
1375 error |= _uckdcmp_load(paths, 1) < 0 ? UCDATA_KDECOMP : 0;
1376
1377 return -error;
1378 }
1379 #endif
1380
1381 #ifdef TEST
1382
1383 void
main(void)1384 main(void)
1385 {
1386 int dig;
1387 ac_uint4 i, lo, *dec;
1388 struct ucnumber num;
1389
1390 /* ucdata_setup("."); */
1391
1392 if (ucisweak(0x30))
1393 printf("WEAK\n");
1394 else
1395 printf("NOT WEAK\n");
1396
1397 printf("LOWER 0x%04lX\n", uctolower(0xff3a));
1398 printf("UPPER 0x%04lX\n", uctoupper(0xff5a));
1399
1400 if (ucisalpha(0x1d5))
1401 printf("ALPHA\n");
1402 else
1403 printf("NOT ALPHA\n");
1404
1405 if (ucisupper(0x1d5)) {
1406 printf("UPPER\n");
1407 lo = uctolower(0x1d5);
1408 printf("0x%04lx\n", lo);
1409 lo = uctotitle(0x1d5);
1410 printf("0x%04lx\n", lo);
1411 } else
1412 printf("NOT UPPER\n");
1413
1414 if (ucistitle(0x1d5))
1415 printf("TITLE\n");
1416 else
1417 printf("NOT TITLE\n");
1418
1419 if (uciscomposite(0x1d5))
1420 printf("COMPOSITE\n");
1421 else
1422 printf("NOT COMPOSITE\n");
1423
1424 if (ucdecomp(0x1d5, &lo, &dec)) {
1425 for (i = 0; i < lo; i++)
1426 printf("0x%04lx ", dec[i]);
1427 putchar('\n');
1428 }
1429
1430 if ((lo = uccombining_class(0x41)) != 0)
1431 printf("0x41 CCL %ld\n", lo);
1432
1433 if (ucisxdigit(0xfeff))
1434 printf("0xFEFF HEX DIGIT\n");
1435 else
1436 printf("0xFEFF NOT HEX DIGIT\n");
1437
1438 if (ucisdefined(0x10000))
1439 printf("0x10000 DEFINED\n");
1440 else
1441 printf("0x10000 NOT DEFINED\n");
1442
1443 if (ucnumber_lookup(0x30, &num)) {
1444 if (num.denominator != 1)
1445 printf("UCNUMBER: 0x30 = %d/%d\n", num.numerator, num.denominator);
1446 else
1447 printf("UCNUMBER: 0x30 = %d\n", num.numerator);
1448 } else
1449 printf("UCNUMBER: 0x30 NOT A NUMBER\n");
1450
1451 if (ucnumber_lookup(0xbc, &num)) {
1452 if (num.denominator != 1)
1453 printf("UCNUMBER: 0xbc = %d/%d\n", num.numerator, num.denominator);
1454 else
1455 printf("UCNUMBER: 0xbc = %d\n", num.numerator);
1456 } else
1457 printf("UCNUMBER: 0xbc NOT A NUMBER\n");
1458
1459
1460 if (ucnumber_lookup(0xff19, &num)) {
1461 if (num.denominator != 1)
1462 printf("UCNUMBER: 0xff19 = %d/%d\n", num.numerator, num.denominator);
1463 else
1464 printf("UCNUMBER: 0xff19 = %d\n", num.numerator);
1465 } else
1466 printf("UCNUMBER: 0xff19 NOT A NUMBER\n");
1467
1468 if (ucnumber_lookup(0x4e00, &num)) {
1469 if (num.denominator != 1)
1470 printf("UCNUMBER: 0x4e00 = %d/%d\n", num.numerator, num.denominator);
1471 else
1472 printf("UCNUMBER: 0x4e00 = %d\n", num.numerator);
1473 } else
1474 printf("UCNUMBER: 0x4e00 NOT A NUMBER\n");
1475
1476 if (ucdigit_lookup(0x06f9, &dig))
1477 printf("UCDIGIT: 0x6f9 = %d\n", dig);
1478 else
1479 printf("UCDIGIT: 0x6f9 NOT A NUMBER\n");
1480
1481 dig = ucgetdigit(0x0969);
1482 printf("UCGETDIGIT: 0x969 = %d\n", dig);
1483
1484 num = ucgetnumber(0x30);
1485 if (num.denominator != 1)
1486 printf("UCGETNUMBER: 0x30 = %d/%d\n", num.numerator, num.denominator);
1487 else
1488 printf("UCGETNUMBER: 0x30 = %d\n", num.numerator);
1489
1490 num = ucgetnumber(0xbc);
1491 if (num.denominator != 1)
1492 printf("UCGETNUMBER: 0xbc = %d/%d\n", num.numerator, num.denominator);
1493 else
1494 printf("UCGETNUMBER: 0xbc = %d\n", num.numerator);
1495
1496 num = ucgetnumber(0xff19);
1497 if (num.denominator != 1)
1498 printf("UCGETNUMBER: 0xff19 = %d/%d\n", num.numerator, num.denominator);
1499 else
1500 printf("UCGETNUMBER: 0xff19 = %d\n", num.numerator);
1501
1502 /* ucdata_cleanup(); */
1503 exit(0);
1504 }
1505
1506 #endif /* TEST */
1507