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