1 /* libhangul
2 * Copyright (C) 2005-2009 Choe Hwanjin
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 */
18
19 #ifdef HAVE_CONFIG_H
20 #include <config.h>
21 #endif
22
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <unistd.h>
26
27 #ifdef HAVE_MMAP
28 #include <sys/mman.h>
29 #endif
30
31 #include <limits.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35
36 #include "hangul.h"
37 #include "hangulinternals.h"
38
39 #ifndef TRUE
40 #define TRUE 1
41 #endif
42
43 #ifndef FALSE
44 #define FALSE 0
45 #endif
46
47 /**
48 * @defgroup hanjadictionary 한자 사전 검색 기능
49 *
50 * @section hanjadictionaryusage 한자 사전 루틴의 사용 방법
51 * libhangul에서는 한자 사전 파일과 그 사전 파일을 검색할 수 있는 몇가지
52 * 함수의 셋을 제공한다. 여기에서 사용되는 모든 스트링은 UTF-8 인코딩을
53 * 사용한다. libhangul에서 사용하는 한자 사전 파일의 포맷은
54 * @ref HanjaTable 섹션을 참조한다.
55 *
56 * 그 개략적인 사용 방법은 다음과 같다.
57 *
58 * @code
59 // 지정된 위치의 한자 사전 파일을 로딩한다.
60 // 아래 코드에서는 libhangul의 한자 사전 파일을 로딩하기 위해서
61 // NULL을 argument로 준다.
62 HanjaTable* table = hanja_table_load(NULL);
63
64 // "삼국사기"에 해당하는 한자를 찾는다.
65 HanjaList* list = hanja_table_match_exact(table, "삼국사기");
66 if (list != NULL) {
67 int i;
68 int n = hanja_list_get_size(list);
69 for (i = 0; i < n; ++i) {
70 const char* hanja = hanja_list_get_nth_value(list);
71 printf("한자: %s\n", hanja);
72 }
73 hanja_list_delete(list);
74 }
75
76 hanja_table_delete(table);
77
78 * @endcode
79 */
80
81 /**
82 * @file hanja.c
83 */
84
85 /**
86 * @ingroup hanjadictionary
87 * @typedef Hanja
88 * @brief 한자 사전 검색 결과의 최소 단위
89 *
90 * Hanja 오브젝트는 한자 사전 파일의 각 엔트리에 해당한다.
91 * 각 엔트리는 키(key), 밸류(value) 페어로 볼 수 있는데, libhangul에서는
92 * 약간 확장을 하여 설명(comment)도 포함하고 있다.
93 * 한자 사전 포맷은 @ref HanjaTable 부분을 참조한다.
94 *
95 * 한자 사전을 검색하면 결과는 Hanja 오브젝트의 리스트 형태로 전달된다.
96 * @ref HanjaList에서 각 엔트리의 내용을 하나씩 확인할 수 있다.
97 * Hanja의 멤버는 직접 참조할 수 없고, hanja_get_key(), hanja_get_value(),
98 * hanja_get_comment() 함수로 찾아볼 수 있다.
99 * char 스트링으로 전달되는 내용은 모두 UTF-8 인코딩으로 되어 있다.
100 */
101
102 /**
103 * @ingroup hanjadictionary
104 * @typedef HanjaList
105 * @brief 한자 사전의 검색 결과를 전달하는데 사용하는 오브젝트
106 *
107 * 한자 사전의 검색 함수를 사용하면 이 타입으로 결과를 리턴한다.
108 * 이 오브젝트에서 hanja_list_get_nth()함수를 이용하여 검색 결과를
109 * 이터레이션할 수 있다. 내부 구현 내용은 외부로 노출되어 있지 않다.
110 * @ref HanjaList가 가지고 있는 아이템들은 accessor 함수들을 이용해서 참조한다.
111 *
112 * 참조: hanja_list_get_nth(), hanja_list_get_nth_key(),
113 * hanja_list_get_nth_value(), hanja_list_get_nth_comment()
114 */
115
116 /**
117 * @ingroup hanjadictionary
118 * @typedef HanjaTable
119 * @brief 한자 사전을 관리하는데 사용하는 오브젝트
120 *
121 * libhangul에서 한자 사전을 관리하는데 사용하는 오브젝트로
122 * 내부 구현 내용은 외부로 노출되어 있지 않다.
123 *
124 * libhangul에서 사용하는 한자 사전 파일의 포맷은 다음과 같은 형식이다.
125 *
126 * @code
127 * # comment
128 * key1:value1:comment1
129 * key2:value2:comment2
130 * key3:value3:comment3
131 * ...
132 * @endcode
133 *
134 * 각 필드는 @b @c : 으로 구분하고, 첫번째 필드는 각 한자를 찾을 키값이고
135 * 두번째 필드는 그 키값에 해당하는 한자 스트링, 세번째 필드는 이 키와
136 * 값에 대한 설명이다. #으로 시작하는 라인은 주석으로 무시된다.
137 *
138 * 실제 예를 들면 다음과 같은 식이다.
139 *
140 * @code
141 * 삼국사기:三國史記:삼국사기
142 * 한자:漢字:한자
143 * @endcode
144 *
145 * 그 내용은 키값에 대해서 sorting 되어야 있어야 한다.
146 * 파일의 인코딩은 UTF-8이어야 한다.
147 */
148
149 typedef struct _HanjaIndex HanjaIndex;
150
151 typedef struct _HanjaPair HanjaPair;
152 typedef struct _HanjaPairArray HanjaPairArray;
153
154 struct _Hanja {
155 uint32_t key_offset;
156 uint32_t value_offset;
157 uint32_t comment_offset;
158 };
159
160 struct _HanjaList {
161 char* key;
162 size_t len;
163 size_t alloc;
164 const Hanja** items;
165 };
166
167 struct _HanjaIndex {
168 unsigned offset;
169 char key[8];
170 };
171
172 struct _HanjaTable {
173 HanjaIndex* keytable;
174 unsigned nkeys;
175 unsigned key_size;
176 FILE* file;
177 };
178
179 struct _HanjaPair {
180 ucschar first;
181 ucschar second;
182 };
183
184 struct _HanjaPairArray {
185 ucschar key;
186 const HanjaPair* pairs;
187 };
188
189 #include "hanjacompatible.h"
190
191 static const char utf8_skip_table[256] = {
192 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
193 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
194 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
195 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
196 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
197 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
198 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
199 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,1,1
200 };
201
utf8_char_len(const char * p)202 static inline int utf8_char_len(const char *p)
203 {
204 return utf8_skip_table[*(const unsigned char*)p];
205 }
206
utf8_next(const char * str)207 static inline const char* utf8_next(const char *str)
208 {
209 int n = utf8_char_len(str);
210
211 while (n > 0) {
212 str++;
213 if (*str == '\0')
214 return str;
215 n--;
216 }
217
218 return str;
219 }
220
utf8_prev(const char * str,const char * p)221 static inline char* utf8_prev(const char *str, const char *p)
222 {
223 for (--p; p >= str; --p) {
224 if ((*p & 0xc0) != 0x80)
225 break;
226 }
227 return (char*)p;
228 }
229
230 /* hanja searching functions */
231 static Hanja *
hanja_new(const char * key,const char * value,const char * comment)232 hanja_new(const char *key, const char *value, const char *comment)
233 {
234 Hanja* hanja;
235 size_t size;
236 size_t keylen;
237 size_t valuelen;
238 size_t commentlen;
239 char* p;
240
241 keylen = strlen(key) + 1;
242 valuelen = strlen(value) + 1;
243 if (comment != NULL)
244 commentlen = strlen(comment) + 1;
245 else
246 commentlen = 1;
247
248 size = sizeof(*hanja) + keylen + valuelen + commentlen;
249 hanja = malloc(size);
250 if (hanja == NULL)
251 return NULL;
252
253 p = (char*)hanja + sizeof(*hanja);
254 strcpy(p, key);
255 p += keylen;
256 strcpy(p, value);
257 p += valuelen;
258 if (comment != NULL)
259 strcpy(p, comment);
260 else
261 *p = '\0';
262 p += valuelen;
263
264 hanja->key_offset = sizeof(*hanja);
265 hanja->value_offset = sizeof(*hanja) + keylen;
266 hanja->comment_offset = sizeof(*hanja) + keylen + valuelen;
267
268 return hanja;
269 }
270
271 static void
hanja_delete(Hanja * hanja)272 hanja_delete(Hanja* hanja)
273 {
274 free(hanja);
275 }
276
277 /**
278 * @ingroup hanjadictionary
279 * @brief @ref Hanja의 키를 찾아본다.
280 * @return @a hanja 오브젝트의 키, UTF-8
281 *
282 * 일반적으로 @ref Hanja 아이템의 키는 한글이다.
283 * 리턴되는 스트링은 @a hanja 오브젝트 내부적으로 관리하는 데이터로
284 * 수정하거나 free 되어서는 안된다.
285 */
286 const char*
hanja_get_key(const Hanja * hanja)287 hanja_get_key(const Hanja* hanja)
288 {
289 if (hanja != NULL) {
290 const char* p = (const char*)hanja;
291 return p + hanja->key_offset;
292 }
293 return NULL;
294 }
295
296 /**
297 * @ingroup hanjadictionary
298 * @brief @ref Hanja의 값을 찾아본다.
299 * @return @a hanja 오브젝트의 값, UTF-8
300 *
301 * 일반적으로 @ref Hanja 아이템의 값은 key에 대응되는 한자다.
302 * 리턴되는 스트링은 @a hanja 오브젝트 내부적으로 관리하는 데이터로
303 * 수정하거나 free되어서는 안된다.
304 */
305 const char*
hanja_get_value(const Hanja * hanja)306 hanja_get_value(const Hanja* hanja)
307 {
308 if (hanja != NULL) {
309 const char* p = (const char*)hanja;
310 return p + hanja->value_offset;
311 }
312 return NULL;
313 }
314
315 /**
316 * @ingroup hanjadictionary
317 * @brief @ref Hanja의 설명을 찾아본다.
318 * @return @a hanja 오브젝트의 comment 필드, UTF-8
319 *
320 * 일반적으로 @ref Hanja 아이템의 설명은 한글과 그 한자에 대한 설명이다.
321 * 파일에 따라서 내용이 없을 수 있다.
322 * 리턴되는 스트링은 @a hanja 오브젝트 내부적으로 관리하는 데이터로
323 * 수정하거나 free되어서는 안된다.
324 */
325 const char*
hanja_get_comment(const Hanja * hanja)326 hanja_get_comment(const Hanja* hanja)
327 {
328 if (hanja != NULL) {
329 const char* p = (const char*)hanja;
330 return p + hanja->comment_offset;
331 }
332 return NULL;
333 }
334
335 static HanjaList *
hanja_list_new(const char * key)336 hanja_list_new(const char *key)
337 {
338 HanjaList *list;
339
340 list = malloc(sizeof(*list));
341 if (list != NULL) {
342 list->key = strdup(key);
343 list->len = 0;
344 list->alloc = 1;
345 list->items = malloc(list->alloc * sizeof(list->items[0]));
346 if (list->items == NULL) {
347 free(list);
348 list = NULL;
349 }
350 }
351
352 return list;
353 }
354
355 static void
hanja_list_reserve(HanjaList * list,size_t n)356 hanja_list_reserve(HanjaList* list, size_t n)
357 {
358 size_t size = list->alloc;
359
360 if (n > SIZE_MAX / sizeof(list->items[0]) - list->len)
361 return;
362
363 while (size < list->len + n)
364 size *= 2;
365
366 if (size > SIZE_MAX / sizeof(list->items[0]))
367 return;
368
369 if (list->alloc < list->len + n) {
370 const Hanja** data;
371
372 data = realloc(list->items, size * sizeof(list->items[0]));
373 if (data != NULL) {
374 list->alloc = size;
375 list->items = data;
376 }
377 }
378 }
379
380 static void
hanja_list_append_n(HanjaList * list,const Hanja * hanja,int n)381 hanja_list_append_n(HanjaList* list, const Hanja* hanja, int n)
382 {
383 hanja_list_reserve(list, n);
384
385 if (list->alloc >= list->len + n) {
386 unsigned int i;
387 for (i = 0; i < n ; i++)
388 list->items[list->len + i] = hanja + i;
389 list->len += n;
390 }
391 }
392
393 static void
hanja_table_match(const HanjaTable * table,const char * key,HanjaList ** list)394 hanja_table_match(const HanjaTable* table,
395 const char* key, HanjaList** list)
396 {
397 int low, high, mid;
398 int res = -1;
399
400 low = 0;
401 high = table->nkeys - 1;
402
403 while (low < high) {
404 mid = (low + high) / 2;
405 res = strncmp(table->keytable[mid].key, key, table->key_size);
406 if (res < 0) {
407 low = mid + 1;
408 } else if (res > 0) {
409 high = mid - 1;
410 } else {
411 break;
412 }
413 }
414
415 if (res != 0) {
416 mid = low;
417 res = strncmp(table->keytable[mid].key, key, table->key_size);
418 }
419
420 if (res == 0) {
421 unsigned offset;
422 char buf[512];
423
424 offset = table->keytable[mid].offset;
425 fseek(table->file, offset, SEEK_SET);
426
427 while (fgets(buf, sizeof(buf), table->file) != NULL) {
428 char* save = NULL;
429 char* p = strtok_r(buf, ":", &save);
430 res = strcmp(p, key);
431 if (res == 0) {
432 char* value = strtok_r(NULL, ":", &save);
433 char* comment = strtok_r(NULL, "\r\n", &save);
434
435 Hanja* hanja = hanja_new(p, value, comment);
436
437 if (*list == NULL) {
438 *list = hanja_list_new(key);
439 }
440
441 hanja_list_append_n(*list, hanja, 1);
442 } else if (res > 0) {
443 break;
444 }
445 }
446 }
447 }
448
449 /**
450 * @ingroup hanjadictionary
451 * @brief 한자 사전 파일을 로딩하는 함수
452 * @param filename 로딩할 사전 파일의 위치, 또는 NULL
453 * @return 한자 사전 object 또는 NULL
454 *
455 * 이 함수는 한자 사전 파일을 로딩하는 함수로 @a filename으로 지정된
456 * 파일을 로딩한다. 한자 사전 파일은 libhangul에서 사용하는 포맷이어야 한다.
457 * 한자 사전 파일의 포맷에 대한 정보는 HanjaTable을 참조한다.
458 *
459 * @a filename은 locale에 따른 인코딩으로 되어 있어야 한다. UTF-8이 아닐 수
460 * 있으므로 주의한다.
461 *
462 * @a filename 에 NULL을 주면 libhangul에서 디폴트로 배포하는 사전을 로딩한다.
463 * 파일이 없거나, 포맷이 맞지 않으면 로딩에 실패하고 NULL을 리턴한다.
464 * 한자 사전이 더이상 필요없으면 hanja_table_delete() 함수로 삭제해야 한다.
465 */
466 HanjaTable*
hanja_table_load(const char * filename)467 hanja_table_load(const char* filename)
468 {
469 unsigned nkeys;
470 char buf[512];
471 int key_size = 5;
472 char last_key[8] = { '\0', };
473 char* save_ptr = NULL;
474 char* key;
475 long offset;
476 unsigned i;
477 FILE* file;
478 HanjaIndex* keytable;
479 HanjaTable* table;
480
481 if (filename == NULL)
482 filename = LIBHANGUL_DEFAULT_HANJA_DIC;
483
484 file = fopen(filename, "r");
485 if (file == NULL) {
486 return NULL;
487 }
488
489 nkeys = 0;
490 while (fgets(buf, sizeof(buf), file) != NULL) {
491 /* skip comments and empty lines */
492 if (buf[0] == '#' || buf[0] == '\r' || buf[0] == '\n' || buf[0] == '\0')
493 continue;
494
495 save_ptr = NULL;
496 key = strtok_r(buf, ":", &save_ptr);
497
498 if (key == NULL || strlen(key) == 0)
499 continue;
500
501 if (strncmp(last_key, key, key_size) != 0) {
502 nkeys++;
503 strncpy(last_key, key, key_size);
504 }
505 }
506
507 rewind(file);
508 keytable = malloc(nkeys * sizeof(keytable[0]));
509 memset(keytable, 0, nkeys * sizeof(keytable[0]));
510
511 i = 0;
512 offset = ftell(file);
513 while (fgets(buf, sizeof(buf), file) != NULL) {
514 /* skip comments and empty lines */
515 if (buf[0] == '#' || buf[0] == '\r' || buf[0] == '\n' || buf[0] == '\0')
516 continue;
517
518 save_ptr = NULL;
519 key = strtok_r(buf, ":", &save_ptr);
520
521 if (key == NULL || strlen(key) == 0)
522 continue;
523
524 if (strncmp(last_key, key, key_size) != 0) {
525 keytable[i].offset = offset;
526 strncpy(keytable[i].key, key, key_size);
527 strncpy(last_key, key, key_size);
528 i++;
529 }
530 offset = ftell(file);
531 }
532
533 table = malloc(sizeof(*table));
534 if (table == NULL) {
535 free(keytable);
536 fclose(file);
537 return NULL;
538 }
539
540 table->keytable = keytable;
541 table->nkeys = nkeys;
542 table->key_size = key_size;
543 table->file = file;
544
545 return table;
546 }
547
548 /**
549 * @ingroup hanjadictionary
550 * @brief 한자 사전 object를 free하는 함수
551 * @param table free할 한자 사전 object
552 */
553 void
hanja_table_delete(HanjaTable * table)554 hanja_table_delete(HanjaTable *table)
555 {
556 if (table != NULL) {
557 free(table->keytable);
558 fclose(table->file);
559 free(table);
560 }
561 }
562
563 /**
564 * @ingroup hanjadictionary
565 * @brief 한자 사전에서 매치되는 키를 가진 엔트리를 찾는 함수
566 * @param table 한자 사전 object
567 * @param key 찾을 키, UTF-8 인코딩
568 * @return 찾은 결과를 HanjaList object로 리턴한다. 찾은 것이 없거나 에러가
569 * 있으면 NULL을 리턴한다.
570 *
571 * @a key 값과 같은 키를 가진 엔트리를 검색한다.
572 * 리턴된 결과는 다 사용하고 나면 반드시 hanja_list_delete() 함수로 free해야
573 * 한다.
574 */
575 HanjaList*
hanja_table_match_exact(const HanjaTable * table,const char * key)576 hanja_table_match_exact(const HanjaTable* table, const char *key)
577 {
578 HanjaList* ret = NULL;
579
580 if (key == NULL || key[0] == '\0' || table == NULL)
581 return NULL;
582
583 hanja_table_match(table, key, &ret);
584
585 return ret;
586 }
587
588 /**
589 * @ingroup hanjadictionary
590 * @brief 한자 사전에서 앞부분이 매치되는 키를 가진 엔트리를 찾는 함수
591 * @param table 한자 사전 object
592 * @param key 찾을 키, UTF-8 인코딩
593 * @return 찾은 결과를 HanjaList object로 리턴한다. 찾은 것이 없거나 에러가
594 * 있으면 NULL을 리턴한다.
595 *
596 * @a key 값과 같거나 앞부분이 같은 키를 가진 엔트리를 검색한다.
597 * 그리고 key를 뒤에서부터 한자씩 줄여가면서 검색을 계속한다.
598 * 예로 들면 "삼국사기"를 검색하면 "삼국사기", "삼국사", "삼국", "삼"을
599 * 각각 모두 검색한다.
600 * 리턴된 결과는 다 사용하고 나면 반드시 hanja_list_delete() 함수로 free해야
601 * 한다.
602 */
603 HanjaList*
hanja_table_match_prefix(const HanjaTable * table,const char * key)604 hanja_table_match_prefix(const HanjaTable* table, const char *key)
605 {
606 char* p;
607 char* newkey;
608 HanjaList* ret = NULL;
609
610 if (key == NULL || key[0] == '\0' || table == NULL)
611 return NULL;
612
613 newkey = strdup(key);
614 if (newkey == NULL)
615 return NULL;
616
617 p = strchr(newkey, '\0');
618 while (newkey[0] != '\0') {
619 hanja_table_match(table, newkey, &ret);
620 p = utf8_prev(newkey, p);
621 p[0] = '\0';
622 }
623 free(newkey);
624
625 return ret;
626 }
627
628 /**
629 * @ingroup hanjadictionary
630 * @brief 한자 사전에서 뒷부분이 매치되는 키를 가진 엔트리를 찾는 함수
631 * @param table 한자 사전 object
632 * @param key 찾을 키, UTF-8 인코딩
633 * @return 찾은 결과를 HanjaList object로 리턴한다. 찾은 것이 없거나 에러가
634 * 있으면 NULL을 리턴한다.
635 *
636 * @a key 값과 같거나 뒷부분이 같은 키를 가진 엔트리를 검색한다.
637 * 그리고 key를 앞에서부터 한자씩 줄여가면서 검색을 계속한다.
638 * 예로 들면 "삼국사기"를 검색하면 "삼국사기", "국사기", "사기", "기"를
639 * 각각 모두 검색한다.
640 * 리턴된 결과는 다 사용하고 나면 반드시 hanja_list_delete() 함수로 free해야
641 * 한다.
642 */
643 HanjaList*
hanja_table_match_suffix(const HanjaTable * table,const char * key)644 hanja_table_match_suffix(const HanjaTable* table, const char *key)
645 {
646 const char* p;
647 HanjaList* ret = NULL;
648
649 if (key == NULL || key[0] == '\0' || table == NULL)
650 return NULL;
651
652 p = key;
653 while (p[0] != '\0') {
654 hanja_table_match(table, p, &ret);
655 p = utf8_next(p);
656 }
657
658 return ret;
659 }
660
661 /**
662 * @ingroup hanjadictionary
663 * @brief @ref HanjaList가 가지고 있는 아이템의 갯수를 구하는 함수
664 */
665 int
hanja_list_get_size(const HanjaList * list)666 hanja_list_get_size(const HanjaList *list)
667 {
668 if (list != NULL)
669 return list->len;
670 return 0;
671 }
672
673 /**
674 * @ingroup hanjadictionary
675 * @brief @ref HanjaList가 생성될때 검색함수에서 사용한 키를 구하는 함수
676 * @return @ref HanjaList의 key 스트링
677 *
678 * 한자 사전 검색 함수로 HanjaList를 생성하면 HanjaList는 그 검색할때 사용한
679 * 키를 기억하고 있다. 이 값을 확인할때 사용한다.
680 * 주의할 점은, 각 Hanja 아이템들은 각각의 키를 가지고 있지만, 이것이
681 * 반드시 @ref HanjaList와 일치하지는 않는다는 것이다.
682 * 검색할 당시에 사용한 함수가 prefix나 suffix계열이면 더 짧은 키로도
683 * 검색하기 때문에 @ref HanjaList의 키와 검색 결과의 키와 다른 것들도
684 * 가지고 있게 된다.
685 *
686 * 리턴된 스트링 포인터는 @ref HanjaList에서 관리하는 스트링으로
687 * 수정하거나 free해서는 안된다.
688 */
689 const char*
hanja_list_get_key(const HanjaList * list)690 hanja_list_get_key(const HanjaList *list)
691 {
692 if (list != NULL)
693 return list->key;
694 return NULL;
695 }
696
697 /**
698 * @ingroup hanjadictionary
699 * @brief @ref HanjaList 의 n번째 @ref Hanja 아이템의 포인터를 구하는 함수
700 * @param list @ref HanjaList를 가리키는 포인터
701 * @param n 참조할 아이템의 인덱스
702 * @return @ref Hanja를 가리키는 포인터
703 *
704 * 이 함수는 @a list가 가리키는 @ref HanjaList의 n번째 @ref Hanja 오브젝트를
705 * 가리키는 포인터를 리턴한다.
706 * @ref HanjaList 의 각 아이템은 정수형 인덱스로 각각 참조할 수 있다.
707 * @ref HanjaList 가 가진 엔트리 갯수를 넘어서는 인덱스를 주면 NULL을 리턴한다.
708 * 리턴된 @ref Hanja 오브젝트는 @ref HanjaList가 관리하는 오브젝트로 free하거나
709 * 수정해서는 안된다.
710 *
711 * 다음의 예제는 list로 주어진 @ref HanjaList 의 모든 값을 프린트 하는
712 * 코드다.
713 *
714 * @code
715 * int i;
716 * int n = hanja_list_get_size(list);
717 * for (i = 0; i < n; i++) {
718 * Hanja* hanja = hanja_list_get_nth(i);
719 * const char* value = hanja_get_value(hanja);
720 * printf("Hanja: %s\n", value);
721 * // 또는 hanja에서 다른 정보를 참조하거나
722 * // 다른 작업을 할 수도 있다.
723 * }
724 * @endcode
725 */
726 const Hanja*
hanja_list_get_nth(const HanjaList * list,unsigned int n)727 hanja_list_get_nth(const HanjaList *list, unsigned int n)
728 {
729 if (list != NULL) {
730 if (n < list->len)
731 return list->items[n];
732 }
733 return NULL;
734 }
735
736 /**
737 * @ingroup hanjadictionary
738 * @brief @ref HanjaList 의 n번째 아이템의 키를 구하는 함수
739 * @return n번째 아이템의 키, UTF-8
740 *
741 * HanjaList_get_nth()의 convenient 함수
742 */
743 const char*
hanja_list_get_nth_key(const HanjaList * list,unsigned int n)744 hanja_list_get_nth_key(const HanjaList *list, unsigned int n)
745 {
746 const Hanja* hanja = hanja_list_get_nth(list, n);
747 return hanja_get_key(hanja);
748 }
749
750 /**
751 * @ingroup hanjadictionary
752 * @brief @ref HanjaList의 n번째 아이템의 값를 구하는 함수
753 * @return n번째 아이템의 값(value), UTF-8
754 *
755 * HanjaList_get_nth()의 convenient 함수
756 */
757 const char*
hanja_list_get_nth_value(const HanjaList * list,unsigned int n)758 hanja_list_get_nth_value(const HanjaList *list, unsigned int n)
759 {
760 const Hanja* hanja = hanja_list_get_nth(list, n);
761 return hanja_get_value(hanja);
762 }
763
764 /**
765 * @ingroup hanjadictionary
766 * @brief @ref HanjaList의 n번째 아이템의 설명을 구하는 함수
767 * @return n번째 아이템의 설명(comment), UTF-8
768 *
769 * HanjaList_get_nth()의 convenient 함수
770 */
771 const char*
hanja_list_get_nth_comment(const HanjaList * list,unsigned int n)772 hanja_list_get_nth_comment(const HanjaList *list, unsigned int n)
773 {
774 const Hanja* hanja = hanja_list_get_nth(list, n);
775 return hanja_get_comment(hanja);
776 }
777
778 /**
779 * @ingroup hanjadictionary
780 * @brief 한자 사전 검색 함수가 리턴한 결과를 free하는 함수
781 * @param list free할 @ref HanjaList
782 *
783 * libhangul의 모든 한자 사전 검색 루틴이 리턴한 결과는 반드시
784 * 이 함수로 free해야 한다.
785 */
786 void
hanja_list_delete(HanjaList * list)787 hanja_list_delete(HanjaList *list)
788 {
789 if (list) {
790 size_t i;
791 for (i = 0; i < list->len; i++) {
792 hanja_delete((Hanja*)list->items[i]);
793 }
794 free(list->items);
795 free(list->key);
796 free(list);
797 }
798 }
799
800 static int
compare_pair(const void * a,const void * b)801 compare_pair(const void* a, const void* b)
802 {
803 const ucschar* c = a;
804 const HanjaPair* y = b;
805
806 return *c - y->first;
807 }
808
809 size_t
hanja_compatibility_form(ucschar * hanja,const ucschar * hangul,size_t n)810 hanja_compatibility_form(ucschar* hanja, const ucschar* hangul, size_t n)
811 {
812 size_t i;
813 size_t nconverted;
814
815 if (hangul == NULL || hanja == NULL)
816 return 0;
817
818 nconverted = 0;
819 for (i = 0; i < n && hangul[i] != 0 && hanja[i] != 0; i++) {
820 HanjaPairArray* p;
821
822 p = bsearch(&hanja[i],
823 hanja_unified_to_compat_table,
824 N_ELEMENTS(hanja_unified_to_compat_table),
825 sizeof(hanja_unified_to_compat_table[0]),
826 compare_pair);
827 if (p != NULL) {
828 const HanjaPair* pair = p->pairs;
829 while (pair->first != 0) {
830 if (pair->first == hangul[i]) {
831 hanja[i] = pair->second;
832 nconverted++;
833 break;
834 }
835 pair++;
836 }
837 }
838 }
839
840 return nconverted;
841 }
842
843 size_t
hanja_unified_form(ucschar * str,size_t n)844 hanja_unified_form(ucschar* str, size_t n)
845 {
846 size_t i;
847 size_t nconverted;
848
849 if (str == NULL)
850 return 0;
851
852 nconverted = 0;
853 for (i = 0; i < n && str[i] != 0; i++) {
854 if (str[i] >= 0xF900 && str[i] <= 0xFA0B) {
855 str[i] = hanja_compat_to_unified_table[str[i] - 0xF900];
856 nconverted++;
857 }
858 }
859
860 return nconverted;
861 }
862