1 /* database.c -- database module.
2 Copyright (C) 2003, 2004
3 National Institute of Advanced Industrial Science and Technology (AIST)
4 Registration Number H15PRO112
6 This file is part of the m17n library.
8 The m17n library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU Lesser General Public License
10 as published by the Free Software Foundation; either version 2.1 of
11 the License, or (at your option) any later version.
13 The m17n library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public
19 License along with the m17n library; if not, write to the Free
20 Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
24 @addtogroup m17nDatabase
25 @brief The m17n database and API for it.
27 The m17n library acquires various kinds of information
28 from data in the <i> m17n database</i> on demand. Application
29 programs can also add/load their original data to/from the m17n
30 database by setting the variable #mdatabase_dir to an
31 application-specific directory and storing data in it. Users can
32 overwrite those data by storing preferable data in the directory
33 specified by the environment variable "M17NDIR", or if it is not
34 set, in the directory "~/.m17n.d".
36 The m17n database contains multiple heterogeneous data, and each
37 data is identified by four tags; TAG0, TAG1, TAG2, TAG3. Each tag
40 TAG0 specifies the type of data stored in the database as below.
43 If TAG0 is #Mchar_table, the data is of the @e chartable @e
44 type and provides information about each character. In this case,
45 TAG1 specifies the type of the information and must be #Msymbol,
46 #Minteger, #Mstring, #Mtext, or #Mplist. TAG2 and TAG3 can be any
50 If TAG0 is #Mcharset, the data is of the @e charset @e type
51 and provides a decode/encode mapping table for a charset. In this
52 case, TAG1 must be a symbol representing a charset. TAG2 and TAG3
56 If TAG0 is neither #Mchar_table nor #Mcharset, the data is of
57 the @e plist @e type. See the documentation of the
58 mdatabase_load () function for the details.
59 In this case, TAG1, TAG2, and TAG3 can be any symbols.
61 The notation \<TAG0, TAG1, TAG2, TAG3\> means a data with those
64 Application programs first calls the mdatabase_find () function to
65 get a pointer to an object of the type #MDatabase. That object
66 holds information about the specified data. When it is
67 successfully returned, the mdatabase_load () function loads the
68 data. The implementation of the structure #MDatabase is
69 concealed from application programs.
73 @addtogroup m17nDatabase
74 @brief m17n ¥Ç¡¼¥¿¥Ù¡¼¥¹¤Ë¤È¤½¤ì¤Ë´Ø¤¹¤ë API.
76 m17n ¥é¥¤¥Ö¥é¥ê¤ÏɬÍפ˱þ¤¸¤ÆưŪ¤Ë @e m17n @e ¥Ç¡¼¥¿¥Ù¡¼¥¹
77 ¤«¤é¾ðÊó¤ò¼èÆÀ¤¹¤ë¡£¤Þ¤¿¥¢¥×¥ê¥±¡¼¥·¥ç¥ó¥×¥í¥°¥é¥à¤â¡¢Æȼ«¤Î¥Ç¡¼¥¿¤ò
78 m17n ¥Ç¡¼¥¿¥Ù¡¼¥¹¤ËÄɲä·¡¢¤½¤ì¤òưŪ¤Ë¼èÆÀ¤¹¤ë¤³¤È¤¬¤Ç¤¤ë¡£
79 ¥¢¥×¥ê¥±¡¼¥·¥ç¥ó¥×¥í¥°¥é¥à¤¬Æȼ«¤Î¥Ç¡¼¥¿¤òÄɲᦼèÆÀ¤¹¤ë¤Ë¤Ï¡¢ÊÑ¿ô
80 #mdatabase_dir ¤Ë¤½¤Î¥¢¥×¥ê¥±¡¼¥·¥ç¥ó¸ÇͤΥǥ£¥ì¥¯¥È¥ê¤ò¥»¥Ã¥È¤·¡¢
81 ¤½¤ÎÃæ¤Ë¥Ç¡¼¥¿¤ò³ÊǼ¤¹¤ë¡£¥æ¡¼¥¶¤¬¤½¤Î¥Ç¡¼¥¿¤ò¥ª¡¼¥Ð¡¼¥é¥¤¥È¤·¤¿¤¤
82 ¤È¤¤Ï¡¢´Ä¶ÊÑ¿ô "M17NDIR" ¤Ç»ØÄꤵ¤ì¤ë¥Ç¥£¥ì¥¯¥È¥ê¡Ê»ØÄꤵ¤ì¤Æ¤¤¤Ê
83 ¤¤¤È¤¤Ï "~/.m17n.d" ¤È¤¤¤¦¥Ç¥£¥ì¥¯¥È¥ê¡Ë¤ËÊ̤Υǡ¼¥¿¤òÃÖ¤¯¡£
86 ¥Ç¡¼¥¿¥Ù¡¼¥¹¤Ë¤ÏÊ£¿ô¤Î¿Íͤʥǡ¼¥¿¤¬´Þ¤Þ¤ì¤Æ¤ª¤ê¡¢³Æ¥Ç¡¼¥¿¤Ï
87 TAG0, TAG1, TAG2, TAG3¡Ê¤¹¤Ù¤Æ¥·¥ó¥Ü¥ë¡Ë¤Î£´¤Ä¤Î¥¿¥°¤Ë¤è¤Ã¤Æ¼±Ê̤µ¤ì¤ë¡£
89 TAG0 ¤Ë¤è¤Ã¤Æ¡¢¥Ç¡¼¥¿¥Ù¡¼¥¹Æâ¤Î¥Ç¡¼¥¿¤Î¥¿¥¤¥×¤Ï¼¡¤Î¤è¤¦¤Ë»ØÄꤵ¤ì¤ë¡£
92 TAG0 ¤¬ #Mchar_table ¤Ç¤¢¤ë¥Ç¡¼¥¿¤Ï @e chartable¥¿¥¤¥×
93 ¤È¸Æ¤Ð¤ì¡¢³Æʸ»ú¤Ë´Ø¤¹¤ë¾ðÊó¤òÄ󶡤¹¤ë¡£¤³¤Î¾ì¹ç
94 TAG1 ¤Ï¾ðÊó¤Î¼ïÎà¤ò»ØÄꤹ¤ë¥·¥ó¥Ü¥ë¤Ç¤¢¤ê¡¢#Msymbol, #Minteger, #Mstring,
95 #Mtext, #Mplist ¤Î¤¤¤º¤ì¤«¤Ç¤¢¤ë¡£TAG2 ¤È TAG3 ¤ÏǤ°Õ¤Î¥·¥ó¥Ü¥ë¤Ç¤è¤¤¡£
98 TAG0 ¤¬ #Mcharset ¤Ç¤¢¤ë¥Ç¡¼¥¿¤Ï @e charset¥¿¥¤¥×
99 ¤È¸Æ¤Ð¤ì¡¢Ê¸»ú¥»¥Ã¥ÈÍѤΥǥ³¡¼¥É¡¿¥¨¥ó¥³¡¼¥É¥Þ¥Ã¥×¤òÄ󶡤¹¤ë¡£¤³¤Î¾ì¹ç TAG1
100 ¤Ïʸ»ú¥»¥Ã¥È¤Î¥·¥ó¥Ü¥ë¤Ç¤Ê¤±¤ì¤Ð¤Ê¤é¤Ê¤¤¡£TAG2 ¤È TAG3
101 ¤ÏǤ°Õ¤Î¥·¥ó¥Ü¥ë¤Ç¤è¤¤¡£
104 TAG0 ¤¬ #Mchar_table ¤Ç¤â #Mcharset ¤Ç¤â¤Ê¤¤¾ì¹ç¡¢¤½¤Î¥Ç¡¼¥¿¤Ï @e
105 plist¥¿¥¤¥× ¤Ç¤¢¤ë¡£¾ÜºÙ¤Ë´Ø¤·¤Æ¤Ï´Ø¿ô mdatabase_load ()
106 ¤ÎÀâÌÀ¤ò»²¾È¤Î¤³¤È¡£¤³¤Î¾ì¹ç TAG1¡¢TAG2¡¢TAG3 ¤ÏǤ°Õ¤Î¥·¥ó¥Ü¥ë¤Ç¤è¤¤¡£
108 ÆÃÄê¤Î¥¿¥°¤ò»ý¤Ä¥Ç¡¼¥¿¥Ù¡¼¥¹¤ò \<TAG0, TAG1, TAG2, TAG3\>
111 ¥¢¥×¥ê¥±¡¼¥·¥ç¥ó¥×¥í¥°¥é¥à¤Ï¡¢¤Þ¤º´Ø¿ô mdatabase_find ()
112 ¤ò»È¤Ã¤Æ¥Ç¡¼¥¿¥Ù¡¼¥¹¤Ë´Ø¤¹¤ë¾ðÊó¤òÊÝ»ý¤¹¤ë¥ª¥Ö¥¸¥§¥¯¥È¡Ê#MDatabase
113 ·¿¡Ë¤Ø¤Î¥Ý¥¤¥ó¥¿¤òÆÀ¤ë¡£¤½¤ì¤ËÀ®¸ù¤·¤¿¤é¡¢ mdatabase_load ()
114 ¤Ë¤è¤Ã¤Æ¼ÂºÝ¤Ë¥Ç¡¼¥¿¥Ù¡¼¥¹¤ò¥í¡¼¥É¤¹¤ë¡£¹½Â¤ÂÎ #MDatabase
115 ¼«¿È¤¬¤É¤¦¼ÂÁõ¤µ¤ì¤Æ¤¤¤ë¤«¤Ï¡¢¥¢¥×¥ê¥±¡¼¥·¥ç¥ó¥×¥í¥°¥é¥à¤«¤é¤Ï¸«¤¨¤Ê¤¤¡£
117 @latexonly \IPAlabel{database} @endlatexonly
122 #if !defined (FOR_DOXYGEN) || defined (DOXYGEN_INTERNAL_MODULE)
123 /*** @addtogroup m17nInternal
131 #include <sys/types.h>
132 #include <sys/stat.h>
139 #include "m17n-misc.h"
140 #include "internal.h"
142 #include "character.h"
144 #include "database.h"
148 /** The file containing a list of databases. */
149 #define MDB_DIR "mdb.dir"
150 /** Length of MDB_DIR. */
151 #define MDB_DIR_LEN 7
153 #define MAX_TIME(TIME1, TIME2) ((TIME1) >= (TIME2) ? (TIME1) : (TIME2))
155 #define GEN_PATH(path, dir, dir_len, file, file_len) \
156 (dir_len + file_len > PATH_MAX ? 0 \
157 : (memcpy (path, dir, dir_len), \
158 memcpy (path + dir_len, file, file_len), \
159 path[dir_len + file_len] = '\0', 1))
161 static MSymbol Masterisk;
163 /** Structure for a data in the m17n database. */
167 /** Tags to identify the data. <tag>[0] specifies the type of
168 database. If it is #Mchar_table, the type is @e chartable, if
169 it is #Mcharset, the type is @e charset, otherwise the type is
173 void *(*loader) (MSymbol *tags, void *extra_info);
175 /** The meaning of the value is dependent on <loader>. If <loader>
176 is load_database (), the value is a string of the file name that
177 contains the data. */
181 static MPlist *mdatabase__list;
184 read_number (char *buf, int *i)
193 while (c && isspace (c)) c = buf[idx++];
199 for (idx++, c = 0; (n = hex_mnemonic[(unsigned) buf[idx]]) < 16;
213 n = escape_mnemonic[c];
217 while (buf[idx] && buf[idx++] != '\'');
221 else if (hex_mnemonic[c] < 10)
226 while ((n = hex_mnemonic[(unsigned) buf[idx]]) < 10)
227 c = (c * 10) + n, idx++;
233 /** Load a data of type @c chartable from the file FD, and return the
234 newly created chartable. */
237 load_chartable (FILE *fp, MSymbol type)
245 MERROR (MERROR_DB, NULL);
247 table = mchartable (type, (type == Msymbol ? (void *) Mnil
248 : type == Minteger ? (void *) -1
255 for (len = 0; len < 1023 && (c = getc (fp)) != EOF && c != '\n'; len++)
258 if (hex_mnemonic[(unsigned) buf[0]] >= 10)
259 /* skip comment/invalid line */
262 from = read_number (buf, &i);
264 i++, to = read_number (buf, &i);
267 if (from < 0 || to < from)
270 while (buf[i] && isspace ((unsigned) buf[i])) i++;
277 /* VAL is a C-string. */
278 if (! (val = strdup (buf + i)))
279 MEMORY_FULL (MERROR_DB);
281 else if (type == Minteger)
283 /* VAL is an integer. */
289 n = read_number (buf, &i);
292 val = (void *) (n * positive);
294 else if (type == Mtext)
296 /* VAL is an M-text. */
299 mt = mconv_decode_buffer (Mcoding_utf_8,
300 (unsigned char *) (buf + i),
305 while ((c = read_number (buf, &i)) >= 0)
306 mt = mtext_cat_char (mt, c);
310 else if (type == Msymbol)
314 while (*p && ! isspace (*p))
316 if (*p == '\\' && p[1] != '\0')
318 memmove (p, p + 1, buf + len - (p + 1));
324 if (! strcmp (buf + i, "nil"))
327 val = (void *) msymbol (buf + i);
329 else if (type == Mplist)
331 val = (void *) mplist__from_string ((unsigned char *) buf + i,
338 mchartable_set (table, from, val);
340 mchartable_set_range (table, from, to, val);
345 M17N_OBJECT_UNREF (table);
346 MERROR (MERROR_DB, NULL);
350 /** Load a data of type @c charset from the file FD. */
353 load_charset (FILE *fp, MSymbol charset_name)
355 MCharset *charset = MCHARSET (charset_name);
364 MERROR (MERROR_DB, NULL);
365 size = (charset->code_range[15]
366 - (charset->min_code - charset->code_range_min_code));
367 MTABLE_MALLOC (decoder, size, MERROR_DB);
368 for (i = 0; i < size; i++)
370 encoder = mchartable (Minteger, (void *) MCHAR_INVALID_CODE);
372 while ((c = getc (fp)) != EOF)
374 unsigned code1, code2, c1, c2;
379 fgets (buf, 256, fp);
382 if (sscanf (buf, "0x%x-0x%x 0x%x", &code1, &code2, &c1) == 3)
384 idx1 = CODE_POINT_TO_INDEX (charset, code1);
387 idx2 = CODE_POINT_TO_INDEX (charset, code2);
390 c2 = c1 + (idx2 - idx1);
392 else if (sscanf (buf, "0x%x 0x%x", &code1, &c1) == 2)
394 idx1 = idx2 = CODE_POINT_TO_INDEX (charset, code1);
401 if (idx1 >= 0 && idx2 >= 0)
404 mchartable_set (encoder, c1, (void *) code1);
405 for (idx1++, c1++; idx1 <= idx2; idx1++, c1++)
407 code1 = INDEX_TO_CODE_POINT (charset, idx1);
409 mchartable_set (encoder, c1, (void *) code1);
419 M17N_OBJECT_UNREF (encoder);
423 mplist_add (plist, Mt, decoder);
424 mplist_add (plist, Mt, encoder);
429 gen_database_name (char *buf, MSymbol *tags)
433 strcpy (buf, msymbol_name (tags[0]));
434 for (i = 1; i < 4; i++)
437 strcat (buf, msymbol_name (tags[i]));
443 find_file (MDatabaseInfo *db_info, struct stat *buf)
446 char path[PATH_MAX + 1];
448 MPLIST_DO (plist, mdatabase__dir_list)
450 MDatabaseInfo *dir_info = MPLIST_VAL (plist);
452 if (dir_info->status != MDB_STATUS_DISABLED
453 && GEN_PATH (path, dir_info->filename, dir_info->len,
454 db_info->filename, db_info->len)
455 && stat (path, buf) == 0)
456 return strdup (path);
462 /* Return the absolute file name for DB_INFO->filename. If BUF is
463 non-NULL, store the result of `stat' call in it. It returns NULL
464 if no absolute file name was found. */
467 get_database_file (MDatabaseInfo *db_info, struct stat *buf)
469 if (db_info->status == MDB_STATUS_DISABLED)
471 if (db_info->absolute_filename)
474 stat (db_info->absolute_filename, buf);
478 struct stat stat_buf;
479 struct stat *statbuf = buf ? buf : &stat_buf;
481 db_info->absolute_filename = find_file (db_info, statbuf);
484 return db_info->absolute_filename;
488 load_database (MSymbol *tags, void *extra_info)
490 MDatabaseInfo *db_info = extra_info;
492 char *filename = get_database_file (db_info, NULL);
495 if (! filename || ! (fp = fopen (filename, "r")))
496 MERROR (MERROR_DB, NULL);
498 if (tags[0] == Mchar_table)
499 value = load_chartable (fp, tags[1]);
500 else if (tags[0] == Mcharset)
501 value = load_charset (fp, tags[1]);
503 value = mplist__from_file (fp, NULL);
507 MERROR (MERROR_DB, NULL);
508 db_info->time = time (NULL);
513 /** Return a newly allocated MDatabaseInfo for DIRNAME. */
515 static MDatabaseInfo *
516 get_dir_info (char *dirname)
518 MDatabaseInfo *dir_info;
520 MSTRUCT_CALLOC (dir_info, MERROR_DB);
523 int len = strlen (dirname);
524 MTABLE_MALLOC (dir_info->filename, len + 2, MERROR_DB);
525 memcpy (dir_info->filename, dirname, len + 1);
526 /* Append PATH_SEPARATOR if DIRNAME doesn't end with it. */
527 if (dir_info->filename[len - 1] != PATH_SEPARATOR)
529 dir_info->filename[len] = PATH_SEPARATOR;
530 dir_info->filename[++len] = '\0';
533 dir_info->status = MDB_STATUS_AUTO;
536 dir_info->status = MDB_STATUS_DISABLED;
541 find_database (MSymbol tags[4])
546 if (! mdatabase__list)
548 for (i = 0, plist = mdatabase__list; i < 4; i++)
550 plist = mplist__assq (plist, tags[i]);
553 plist = MPLIST_PLIST (plist);
554 plist = MPLIST_NEXT (plist);
556 return MPLIST_VAL (plist);
560 free_db_info (MDatabaseInfo *db_info)
562 free (db_info->filename);
563 if (db_info->absolute_filename
564 && db_info->filename != db_info->absolute_filename)
565 free (db_info->absolute_filename);
570 register_database (MSymbol tags[4], void *(*loader) (MSymbol *, void *),
571 void *extra_info, enum MDatabaseStatus status)
573 MDatabase *mdb = find_database (tags);
574 MDatabaseInfo *db_info = NULL;
578 if (loader == load_database)
579 db_info = mdb->extra_info;
586 MSTRUCT_MALLOC (mdb, MERROR_DB);
587 for (i = 0; i < 4; i++)
588 mdb->tag[i] = tags[i];
589 mdb->loader = loader;
590 if (loader == load_database)
592 MSTRUCT_CALLOC (db_info, MERROR_DB);
593 mdb->extra_info = db_info;
596 mdb->extra_info = extra_info;
597 if (! mdatabase__list)
598 mdatabase__list = mplist ();
599 for (i = 0, plist = mdatabase__list; i < 4; i++)
601 MPlist *pl = mplist__assq (plist, tags[i]);
604 pl = MPLIST_PLIST (pl);
608 mplist_add (pl, Msymbol, tags[i]);
609 mplist_push (plist, Mplist, pl);
610 M17N_OBJECT_UNREF (pl);
612 plist = MPLIST_NEXT (pl);
614 mplist_push (plist, Mt, mdb);
619 db_info->status = status;
620 if (! db_info->filename
621 || strcmp (db_info->filename, (char *) extra_info) != 0)
623 if (db_info->filename)
624 free (db_info->filename);
625 if (db_info->absolute_filename
626 && db_info->filename != db_info->absolute_filename)
627 free (db_info->absolute_filename);
628 db_info->filename = strdup ((char *) extra_info);
629 db_info->len = strlen ((char *) extra_info);
632 if (db_info->filename[0] == PATH_SEPARATOR)
633 db_info->absolute_filename = db_info->filename;
635 db_info->absolute_filename = NULL;
638 if (mdb->tag[0] == Mchar_table
639 && mdb->tag[2] != Mnil
640 && (mdb->tag[1] == Mstring || mdb->tag[1] == Mtext
641 || mdb->tag[1] == Msymbol || mdb->tag[1] == Minteger
642 || mdb->tag[1] == Mplist))
643 mchar__define_prop (mdb->tag[2], mdb->tag[1], mdb);
648 register_databases_in_files (MSymbol tags[4], glob_t *globbuf)
651 MPlist *load_key = mplist ();
655 for (i = 0; i < globbuf->gl_pathc; i++)
657 if (! (fp = fopen (globbuf->gl_pathv[i], "r")))
659 plist = mplist__from_file (fp, load_key);
663 if (MPLIST_PLIST_P (plist))
668 for (j = 0, pl = MPLIST_PLIST (plist); j < 4 && MPLIST_SYMBOL_P (pl);
669 j++, pl = MPLIST_NEXT (pl))
670 tags2[j] = MPLIST_SYMBOL (pl);
673 for (j = 0; j < 4; j++)
674 if (tags[j] == Masterisk ? tags2[j] == Mnil
675 : (tags[j] != Mnil && tags[j] != tags2[j]))
678 register_database (tags2, load_database, globbuf->gl_pathv[i], 1);
680 M17N_OBJECT_UNREF (plist);
682 M17N_OBJECT_UNREF (load_key);
688 /** List of database directories. */
689 MPlist *mdatabase__dir_list;
694 MDatabaseInfo *dir_info;
697 Mchar_table = msymbol ("char-table");
698 Masterisk = msymbol ("*");
700 mdatabase__dir_list = mplist ();
701 /** The macro M17NDIR specifies a directory where the system-wide
702 MDB_DIR file exists. */
703 mplist_set (mdatabase__dir_list, Mt, get_dir_info (M17NDIR));
705 /* The variable mdatabase_dir specifies a directory where an
706 application program specific MDB_DIR file exists. */
707 if (mdatabase_dir && strlen (mdatabase_dir) > 0)
708 mplist_push (mdatabase__dir_list, Mt, get_dir_info (mdatabase_dir));
710 /* The environment variable M17NDIR specifies a directory where a
711 user specific MDB_DIR file exists. */
712 path = getenv ("M17NDIR");
713 if (path && strlen (path) > 0)
714 mplist_push (mdatabase__dir_list, Mt, get_dir_info (path));
717 /* If the env var M17NDIR is not set, check "~/.m17n.d". */
718 char *home = getenv ("HOME");
722 && (len = strlen (home))
723 && (path = alloca (len + 9)))
726 if (path[len - 1] != PATH_SEPARATOR)
727 path[len++] = PATH_SEPARATOR;
728 strcpy (path + len, ".m17n.d");
729 dir_info = get_dir_info (path);
730 mplist_push (mdatabase__dir_list, Mt, dir_info);
733 mplist_push (mdatabase__dir_list, Mt, get_dir_info (NULL));
736 mdatabase__finder = ((void *(*) (MSymbol, MSymbol, MSymbol, MSymbol))
738 mdatabase__loader = (void *(*) (void *)) mdatabase_load;
740 mdatabase__list = mplist ();
741 mdatabase__update ();
746 mdatabase__fini (void)
748 MPlist *plist, *p0, *p1, *p2, *p3;
750 MPLIST_DO (plist, mdatabase__dir_list)
751 free_db_info (MPLIST_VAL (plist));
752 M17N_OBJECT_UNREF (mdatabase__dir_list);
754 /* MDATABASE_LIST ::= ((TAG0 (TAG1 (TAG2 (TAG3 t:MDB) ...) ...) ...) ...) */
755 MPLIST_DO (plist, mdatabase__list)
757 p0 = MPLIST_PLIST (plist);
758 /* P0 ::= (TAG0 (TAG1 (TAG2 (TAG3 t:MDB) ...) ...) ...) */
759 MPLIST_DO (p0, MPLIST_NEXT (p0))
761 p1 = MPLIST_PLIST (p0);
762 /* P1 ::= (TAG1 (TAG2 (TAG3 t:MDB) ...) ...) */
763 MPLIST_DO (p1, MPLIST_NEXT (p1))
765 p2 = MPLIST_PLIST (p1);
766 /* P2 ::= (TAG2 (TAG3 t:MDB) ...) */
767 MPLIST_DO (p2, MPLIST_NEXT (p2))
771 p3 = MPLIST_PLIST (p2); /* P3 ::= (TAG3 t:MDB) */
772 p3 = MPLIST_NEXT (p3);
773 mdb = MPLIST_VAL (p3);
774 if (mdb->loader == load_database)
775 free_db_info (mdb->extra_info);
781 M17N_OBJECT_UNREF (mdatabase__list);
785 mdatabase__update (void)
787 MPlist *plist, *p0, *p1, *p2, *p3;
788 char path[PATH_MAX + 1];
789 MDatabaseInfo *dir_info;
792 /* At first, mark all databases defined automatically from mdb.dir
793 file(s) as "disabled". */
794 MPLIST_DO (plist, mdatabase__list)
796 p0 = MPLIST_PLIST (plist);
797 /* P0 ::= (TAG0 (TAG1 (TAG2 (TAG3 MDB) ...) ...) ...) */
798 MPLIST_DO (p0, MPLIST_NEXT (p0))
800 p1 = MPLIST_PLIST (p0);
801 MPLIST_DO (p1, MPLIST_NEXT (p1))
803 p2 = MPLIST_PLIST (p1);
804 MPLIST_DO (p2, MPLIST_NEXT (p2))
806 MDatabaseInfo *db_info;
808 p3 = MPLIST_PLIST (p2);
809 p3 = MPLIST_NEXT (p3);
810 db_info = MPLIST_VAL (p3);
811 if (db_info->status == MDB_STATUS_AUTO)
812 db_info->status = MDB_STATUS_DISABLED;
818 /* Update elements of mdatabase__dir_list. */
819 MPLIST_DO (plist, mdatabase__dir_list)
821 dir_info = MPLIST_VAL (plist);
822 dir_info->status = ((dir_info->filename
823 && stat (dir_info->filename, &statbuf) == 0
824 && (statbuf.st_mode & S_IFDIR))
825 ? MDB_STATUS_AUTO : MDB_STATUS_DISABLED);
828 MPLIST_DO (plist, mdatabase__dir_list)
830 MDatabaseInfo *dir_info = MPLIST_VAL (plist);
835 if (dir_info->status == MDB_STATUS_DISABLED)
837 if (! GEN_PATH (path, dir_info->filename, dir_info->len,
838 MDB_DIR, MDB_DIR_LEN))
840 if (stat (path, &statbuf) < 0)
842 if (dir_info->time >= statbuf.st_mtime)
844 dir_info->time = statbuf.st_mtime;
845 if (! (fp = fopen (path, "r")))
847 pl = mplist__from_file (fp, NULL);
857 int with_wildcard = 0;
859 if (! MPLIST_PLIST_P (p))
861 for (i = 0, p1 = MPLIST_PLIST (p); i < 4 && MPLIST_SYMBOL_P (p1);
862 i++, p1 = MPLIST_NEXT (p1))
863 with_wildcard |= ((tags[i] = MPLIST_SYMBOL (p1)) == Masterisk);
865 || tags[0] == Masterisk
866 || ! MPLIST_MTEXT_P (p1))
870 mt = MPLIST_MTEXT (p1);
871 if (mt->format >= MTEXT_FORMAT_UTF_16LE)
872 mtext__adjust_format (mt, MTEXT_FORMAT_UTF_8);
873 nbytes = mtext_nbytes (mt);
874 if (nbytes > PATH_MAX)
876 memcpy (path, MTEXT_DATA (mt), nbytes);
883 if (tags[0] == Mchar_table || tags[0] == Mcharset)
885 if (path[0] == PATH_SEPARATOR)
887 if (glob (path, GLOB_NOSORT, NULL, &globbuf))
889 register_databases_in_files (tags, &globbuf);
893 MPLIST_DO (dlist, mdatabase__dir_list)
895 MDatabaseInfo *d_info = MPLIST_VAL (dlist);
897 if (d_info->status == MDB_STATUS_DISABLED)
899 if (! GEN_PATH (path, d_info->filename, d_info->len,
900 MTEXT_DATA (mt), nbytes))
902 if (glob (path, GLOB_NOSORT, NULL, &globbuf))
904 register_databases_in_files (tags, &globbuf);
910 register_database (tags, load_database, path, 1);
913 M17N_OBJECT_UNREF (pl);
918 mdatabase__load_for_keys (MDatabase *mdb, MPlist *keys)
920 int mdebug_mask = MDEBUG_DATABASE;
921 MDatabaseInfo *db_info;
927 if (mdb->loader != load_database
928 || mdb->tag[0] == Mchar_table
929 || mdb->tag[0] == Mcharset)
930 MERROR (MERROR_DB, NULL);
931 MDEBUG_PRINT1 (" [DATABASE] loading <%s>.\n",
932 gen_database_name (name, mdb->tag));
933 db_info = mdb->extra_info;
934 filename = get_database_file (db_info, NULL);
935 if (! filename || ! (fp = fopen (filename, "r")))
936 MERROR (MERROR_DB, NULL);
937 plist = mplist__from_file (fp, keys);
943 /* Check if the database MDB should be reloaded or not. It returns:
945 1: The database has not been updated since it was loaded last
948 0: The database has never been loaded or has been updated
949 since it was loaded last time.
951 -1: The database is not loadable at the moment. */
954 mdatabase__check (MDatabase *mdb)
956 MDatabaseInfo *db_info = (MDatabaseInfo *) mdb->extra_info;
959 if (! get_database_file (db_info, &buf))
961 if (db_info->time < buf.st_mtime)
966 /* Search directories in mdatabase__dir_list for file FILENAME. If
967 the file exist, return the absolute pathname. If FILENAME is
968 already absolute, return a copy of it. */
971 mdatabase__find_file (char *filename)
974 MDatabaseInfo db_info;
976 if (filename[0] == PATH_SEPARATOR)
977 return (stat (filename, &buf) == 0 ? filename : NULL);
978 db_info.filename = filename;
979 db_info.len = strlen (filename);
981 db_info.absolute_filename = NULL;
982 if (! get_database_file (&db_info, &buf)
983 || stat (db_info.absolute_filename, &buf) < 0)
985 return db_info.absolute_filename;
989 mdatabase__file (MDatabase *mdb)
991 MDatabaseInfo *db_info;
993 if (mdb->loader != load_database)
995 db_info = mdb->extra_info;
996 return get_database_file (db_info, NULL);
1000 mdatabase__lock (MDatabase *mdb)
1002 MDatabaseInfo *db_info;
1008 if (mdb->loader != load_database)
1010 db_info = mdb->extra_info;
1011 if (db_info->lock_file)
1013 file = get_database_file (db_info, NULL);
1016 len = strlen (file);
1017 db_info->uniq_file = malloc (len + 35);
1018 if (! db_info->uniq_file)
1020 db_info->lock_file = malloc (len + 5);
1021 if (! db_info->lock_file)
1023 free (db_info->uniq_file);
1026 sprintf (db_info->uniq_file, "%s.%X.%X", db_info->absolute_filename,
1027 (unsigned) time (NULL), (unsigned) getpid ());
1028 sprintf (db_info->lock_file, "%s.LCK", db_info->absolute_filename);
1030 fp = fopen (db_info->uniq_file, "w");
1033 free (db_info->uniq_file);
1034 free (db_info->lock_file);
1035 db_info->lock_file = NULL;
1039 if (link (db_info->uniq_file, db_info->lock_file) < 0
1040 && (stat (db_info->uniq_file, &buf) < 0
1041 || buf.st_nlink != 2))
1043 unlink (db_info->uniq_file);
1044 unlink (db_info->lock_file);
1045 free (db_info->uniq_file);
1046 free (db_info->lock_file);
1047 db_info->lock_file = NULL;
1054 mdatabase__save (MDatabase *mdb, MPlist *data)
1056 MDatabaseInfo *db_info;
1062 if (mdb->loader != load_database)
1064 db_info = mdb->extra_info;
1065 if (! db_info->lock_file)
1067 file = get_database_file (db_info, NULL);
1071 if (mplist__serialize (mt, data, 1) < 0)
1073 M17N_OBJECT_UNREF (mt);
1076 fp = fopen (db_info->uniq_file, "w");
1079 M17N_OBJECT_UNREF (mt);
1082 mconv_encode_stream (msymbol ("utf-8"), mt, fp);
1083 M17N_OBJECT_UNREF (mt);
1085 if ((ret = rename (db_info->uniq_file, file)) < 0)
1086 unlink (db_info->uniq_file);
1087 free (db_info->uniq_file);
1088 db_info->uniq_file = NULL;
1093 mdatabase__unlock (MDatabase *mdb)
1095 MDatabaseInfo *db_info;
1097 if (mdb->loader != load_database)
1099 db_info = mdb->extra_info;
1100 if (! db_info->lock_file)
1102 unlink (db_info->lock_file);
1103 free (db_info->lock_file);
1104 db_info->lock_file = NULL;
1105 if (db_info->uniq_file)
1107 unlink (db_info->uniq_file);
1108 free (db_info->uniq_file);
1114 #endif /* !FOR_DOXYGEN || DOXYGEN_INTERNAL_MODULE */
1119 /*** @addtogroup m17nDatabase */
1124 @brief Directory for application specific data.
1126 If an application program wants to provide a data specific to the
1127 program or a data overriding what supplied by the m17n database,
1128 it must set this variable to a name of directory that contains the
1129 data files before it calls the macro M17N_INIT (). The directory
1130 may contain a file "mdb.dir" which contains a list of data
1131 definitions in the format described in @ref mdbDir "mdbDir(5)".
1133 The default value is NULL. */
1135 @brief ¥¢¥×¥ê¥±¡¼¥·¥ç¥ó¸ÇͤΥǡ¼¥¿Íѥǥ£¥ì¥¯¥È¥ê.
1137 ¥¢¥×¥ê¥±¡¼¥·¥ç¥ó¥×¥í¥°¥é¥à¤¬¡¢¤½¤Î¥×¥í¥°¥é¥à¸ÇͤΥǡ¼¥¿¤ä m17n
1138 ¥Ç¡¼¥¿¥Ù¡¼¥¹¤ò¾å½ñ¤¤¹¤ë¥Ç¡¼¥¿¤òÄ󶡤¹¤ë¾ì¹ç¤Ë¤Ï¡¢¥Þ¥¯¥í M17N_INIT ()
1139 ¤ò¸Æ¤ÖÁ°¤Ë¤³¤ÎÊÑ¿ô¤ò¥Ç¡¼¥¿¥Õ¥¡¥¤¥ë¤ò´Þ¤à¥Ç¥£¥ì¥¯¥È¥ê̾¤Ë¥»¥Ã¥È¤·¤Ê¤¯¤Æ¤Ï¤Ê¤é¤Ê¤¤¡£¥Ç¥£¥ì¥¯¥È¥ê¤Ë¤Ï
1140 "mdb.dir" ¥Õ¥¡¥¤¥ë¤ò¤ª¤¯¤³¤È¤¬¤Ç¤¤ë¡£¤½¤Î"mdb.dir"¥Õ¥¡¥¤¥ë¤Ë¤Ï¡¢
1141 @ref mdbDir "mdbDir(5)" ¤ÇÀâÌÀ¤µ¤ì¤Æ¤¤¤ë¥Õ¥©¡¼¥Þ¥Ã¥È¤Ç¥Ç¡¼¥¿ÄêµÁ¤Î¥ê¥¹¥È¤òµ½Ò¤¹¤ë¡£
1143 ¥Ç¥Õ¥©¥ë¥È¤ÎÃÍ¤Ï NULL ¤Ç¤¢¤ë¡£ */
1145 char *mdatabase_dir;
1149 @brief Look for a data in the database.
1151 The mdatabase_find () function searches the m17n database for a
1152 data who has tags $TAG0 through $TAG3, and returns a pointer to
1153 the data. If such a data is not found, it returns @c NULL. */
1156 @brief ¥Ç¡¼¥¿¥Ù¡¼¥¹Ãæ¤Î¥Ç¡¼¥¿¤òõ¤¹.
1158 ´Ø¿ô mdatabase_find () ¤Ï¡¢ m17n ¸À¸ì¾ðÊó¥Ù¡¼¥¹Ãæ¤Ç $TAG0 ¤«¤é
1159 $TAG3 ¤Þ¤Ç¤Î¥¿¥°¤ò»ý¤Ä¥Ç¡¼¥¿¤òõ¤·¡¢¤½¤ì¤Ø¤Î¥Ý¥¤¥ó¥¿¤òÊÖ¤¹¡£¤½¤Î¤è¤¦¤Ê¥Ç¡¼¥¿¤¬¤Ê¤±¤ì¤Ð
1162 @latexonly \IPAlabel{mdatabase_find} @endlatexonly */
1165 mdatabase_find (MSymbol tag0, MSymbol tag1, MSymbol tag2, MSymbol tag3)
1169 mdatabase__update ();
1170 tags[0] = tag0, tags[1] = tag1, tags[2] = tag2, tags[3] = tag3;
1171 return find_database (tags);
1176 @brief Return a data list of the m17n database.
1178 The mdatabase_list () function searches the m17n database for data
1179 who have tags $TAG0 through $TAG3, and returns their list by a
1180 plist. The value #Mnil in $TAGn means a wild card that matches
1181 any tag. Each element of the plist has key #Mt and value a
1182 pointer to type #MDatabase. */
1184 @brief m17n ¥Ç¡¼¥¿¥Ù¡¼¥¹¤Î¥Ç¡¼¥¿¥ê¥¹¥È¤òÊÖ¤¹.
1186 ´Ø¿ô mdatabase_list () ¤Ï m17n ¥Ç¡¼¥¿¥Ù¡¼¥¹Ã椫¤é $TAG0 ¤«¤é$TAG3
1187 ¤Þ¤Ç¤Î¥¿¥°¤ò»ý¤Ä¥Ç¡¼¥¿¤òõ¤·¡¢¤½¤Î¥ê¥¹¥È¤òplist ¤È¤·¤ÆÊÖ¤¹¡£ $TAGn ¤¬ #Mnil
1188 ¤Ç¤¢¤Ã¤¿¾ì¹ç¤Ë¤Ï¡¢Ç¤°Õ¤Î¥¿¥°¤Ë¥Þ¥Ã¥Á¤¹¤ë¥ï¥¤¥ë¥É¥«¡¼¥É¤È¤·¤Æ¼è¤ê°·¤ï¤ì¤ë¡£ÊÖ¤µ¤ì¤ë
1189 plist ¤Î³ÆÍ×ÁǤϥ¡¼ ¤È¤·¤Æ #Mt ¤ò¡¢ÃͤȤ·¤Æ #MDatabase ·¿¤Ø¤Î¥Ý¥¤¥ó¥¿¤ò»ý¤Ä¡£ */
1192 mdatabase_list (MSymbol tag0, MSymbol tag1, MSymbol tag2, MSymbol tag3)
1194 MPlist *plist = mplist (), *pl = plist;
1195 MPlist *p, *p0, *p1, *p2, *p3;
1197 mdatabase__update ();
1199 MPLIST_DO (p, mdatabase__list)
1201 p0 = MPLIST_PLIST (p);
1202 /* P0 ::= (TAG0 (TAG1 (TAG2 (TAG3 MDB) ...) ...) ...) */
1203 if (tag0 != Mnil && MPLIST_SYMBOL (p0) != tag0)
1205 MPLIST_DO (p0, MPLIST_NEXT (p0))
1207 p1 = MPLIST_PLIST (p0);
1208 if (tag1 != Mnil && MPLIST_SYMBOL (p1) != tag1)
1210 MPLIST_DO (p1, MPLIST_NEXT (p1))
1212 p2 = MPLIST_PLIST (p1);
1213 if (tag2 != Mnil && MPLIST_SYMBOL (p2) != tag2)
1215 MPLIST_DO (p2, MPLIST_NEXT (p2))
1217 p3 = MPLIST_PLIST (p2);
1218 if (tag3 != Mnil && MPLIST_SYMBOL (p3) != tag3)
1220 p3 = MPLIST_NEXT (p3);
1221 pl = mplist_add (pl, Mt, MPLIST_VAL (p3));
1226 if (MPLIST_TAIL_P (plist))
1228 M17N_OBJECT_UNREF (plist);
1236 @brief Define a data of the m17n database.
1238 The mdatabase_define () function defines a data that has tags
1239 $TAG0 through $TAG3 and additional information $EXTRA_INFO.
1241 $LOADER is a pointer to a function that loads the data from the
1242 database. This function is called from the mdatabase_load ()
1243 function with the two arguments $TAGS and $EXTRA_INFO. Here,
1244 $TAGS is the array of $TAG0 through $TAG3.
1246 If $LOADER is @c NULL, the default loader of the m17n library is
1247 used. In this case, $EXTRA_INFO must be a string specifying a
1248 filename that contains the data.
1251 If the operation was successful, mdatabase_define () returns a
1252 pointer to the defined data, which can be used as an argument to
1253 mdatabase_load (). Otherwise, it returns @c NULL. */
1256 @brief m17n ¥Ç¡¼¥¿¥Ù¡¼¥¹¤Î¥Ç¡¼¥¿¤òÄêµÁ¤¹¤ë.
1258 ´Ø¿ô mdatabase_define () ¤Ï $TAG0 ¤«¤é $TAG3 ¤Þ¤Ç¤Î¥¿¥°¤ª¤è¤ÓÉղþðÊó
1259 $EXTRA_INFO ¤ò»ý¤Ä¥Ç¡¼¥¿¤òÄêµÁ¤¹¤ë¡£
1261 $LOADER ¤Ï¤½¤Î¥Ç¡¼¥¿¤Î¥í¡¼¥É¤ËÍѤ¤¤é¤ì¤ë´Ø¿ô¤Ø¤Î¥Ý¥¤¥ó¥¿¤Ç¤¢¤ë¡£¤³¤Î´Ø¿ô¤Ï
1262 mdatabase_load () ¤«¤é $TAGS ¤È $EXTRA_INFO ¤È¤¤¤¦Æó¤Ä¤Î°ú¿ôÉÕ¤¤Ç¸Æ¤Ó½Ð¤µ¤ì¤ë¡£¤³¤³¤Ç
1263 $TAGS ¤Ï $TAG0 ¤«¤é $TAG3 ¤Þ¤Ç¤ÎÇÛÎó¤Ç¤¢¤ë¡£
1265 ¤â¤· $LOADER ¤¬ @c NULL ¤Ê¤é¡¢m17n ¥é¥¤¥Ö¥é¥êɸ½à¤Î¥í¡¼¥À¤¬»È¤ï¤ì¤ë¡£¤³¤Î¾ì¹ç¤Ë¤Ï
1266 $EXTRA_INFO ¤Ï¥Ç¡¼¥¿¤ò´Þ¤à¥Õ¥¡¥¤¥ë̾¤Ç¤Ê¤¯¤Æ¤Ï¤Ê¤é¤Ê¤¤¡£
1269 ½èÍý¤ËÀ®¸ù¤¹¤ì¤Ð mdatabase_define ()
1270 ¤ÏÄêµÁ¤µ¤ì¤¿¥Ç¡¼¥¿¥Ù¡¼¥¹¤Ø¤Î¥Ý¥¤¥ó¥¿¤òÊÖ¤¹¡£¤³¤Î¥Ý¥¤¥ó¥¿¤Ï´Ø¿ô mdatabase_load ()
1271 ¤Î°ú¿ô¤È¤·¤ÆÍѤ¤¤ë¤³¤È¤¬¤Ç¤¤ë¡£¤½¤¦¤Ç¤Ê¤±¤ì¤Ð @c NULL ¤òÊÖ¤¹¡£
1273 @latexonly \IPAlabel{mdatabase_define} @endlatexonly */
1277 mdatabase_load (), mdatabase_define () */
1280 mdatabase_define (MSymbol tag0, MSymbol tag1, MSymbol tag2, MSymbol tag3,
1281 void *(*loader) (MSymbol *, void *),
1287 tags[0] = tag0, tags[1] = tag1, tags[2] = tag2, tags[3] = tag3;
1289 loader = load_database;
1290 mdb = register_database (tags, loader, extra_info, 0);
1296 @brief Load a data from the database.
1298 The mdatabase_load () function loads a data specified in $MDB and
1299 returns the contents. The type of contents depends on the type of
1302 If the data is of the @e plist @e type, this function returns a
1303 pointer to @e plist.
1305 If the database is of the @e chartable @e type, it returns a
1306 chartable. The default value of the chartable is set according to
1307 the second tag of the data as below:
1309 @li If the tag is #Msymbol, the default value is #Mnil.
1310 @li If the tag is #Minteger, the default value is -1.
1311 @li Otherwise, the default value is @c NULL.
1313 If the data is of the @e charset @e type, it returns a plist of length 2
1314 (keys are both #Mt). The value of the first element is an array
1315 of integers that maps code points to the corresponding character
1316 codes. The value of the second element is a chartable of integers
1317 that does the reverse mapping. The charset must be defined in
1322 @brief ¥Ç¡¼¥¿¥Ù¡¼¥¹¤«¤é¥Ç¡¼¥¿¤ò¥í¡¼¥É¤¹¤ë.
1324 ´Ø¿ô mdatabase_load () ¤Ï $MDB
1325 ¤¬»Ø¤¹¥Ç¡¼¥¿¤ò¥í¡¼¥É¤·¡¢¤½¤ÎÃæ¿È¤òÊÖ¤¹¡£ÊÖ¤µ¤ì¤ë¤â¤Î¤Ï¥Ç¡¼¥¿¤Î¥¿¥¤¥×¤Ë¤è¤Ã¤Æ°Û¤Ê¤ë¡£
1327 ¥Ç¡¼¥¿¤¬ @e plist¥¿¥¤¥× ¤Ê¤é¤Ð¡¢ @e plist ¤Ø¤Î¥Ý¥¤¥ó¥¿¤òÊÖ¤¹¡£
1329 ¥Ç¡¼¥¿¤¬ @e chartable¥¿¥¤¥× ¤Ê¤é¤Ðʸ»ú¥Æ¡¼¥Ö¥ë¤òÊÖ¤¹¡£
1330 ʸ»ú¥Æ¡¼¥Ö¥ë¤Î¥Ç¥Õ¥©¥ë¥ÈÃͤϡ¢¥Ç¡¼¥¿¤ÎÂè2¥¿¥°¤Ë¤è¤Ã¤Æ°Ê²¼¤Î¤è¤¦¤Ë·è¤Þ¤ë¡£
1332 @li ¥¿¥°¤¬ #Msymbol ¤Ê¤é¡¢¥Ç¥Õ¥©¥ë¥ÈÃÍ¤Ï #Mnil
1333 @li ¥¿¥°¤¬ #Minteger ¤Ê¤é¡¢¥Ç¥Õ¥©¥ë¥ÈÃÍ¤Ï -1
1334 @li ¤½¤ì°Ê³°¤Ê¤é¡¢¥Ç¥Õ¥©¥ë¥ÈÃÍ¤Ï @c NULL
1336 ¥Ç¡¼¥¿¤¬ @e charset¥¿¥¤¥× ¤Ê¤é¤ÐŤµ 2 ¤Î plist ¤òÊÖ¤¹¡Ê¥¡¼¤Ï¶¦¤Ë#Mt ¡Ë¡£
1337 ºÇ½é¤ÎÍ×ÁǤÎÃͤϥ³¡¼¥É¥Ý¥¤¥ó¥È¤òÂбþ¤¹¤ëʸ»ú¥³¡¼¥É¤Ë¥Þ¥Ã¥×¤¹¤ëÀ°¿ô¤ÎÇÛÎó¤Ç¤¢¤ë¡£
1338 £²ÈÖÌܤÎÍ×ÁǤÎÃͤϵդΥޥåפò¤¹¤ëʸ»ú¥Æ¡¼¥Ö¥ë¤Ç¤¢¤ë¡£
1339 ¤³¤Îʸ»ú¥»¥Ã¥È¤Ïͽ¤áÄêµÁ¤µ¤ì¤Æ¤¤¤Ê¤±¤ì¤Ð¤Ê¤é¤Ê¤¤¡£
1341 @latexonly \IPAlabel{mdatabase_load} @endlatexonly
1346 mdatabase_load (), mdatabase_define () */
1349 mdatabase_load (MDatabase *mdb)
1351 int mdebug_mask = MDEBUG_DATABASE;
1354 MDEBUG_PRINT1 (" [DATABASE] loading <%s>.\n",
1355 gen_database_name (buf, mdb->tag));
1356 return (*mdb->loader) (mdb->tag, mdb->extra_info);
1361 @brief Get tags of a data.
1363 The mdatabase_tag () function returns an array of tags (symbols)
1364 that identify the data in $MDB. The length of the array is
1368 @brief ¥Ç¡¼¥¿¤Î¥¿¥°¤òÆÀ¤ë.
1370 ´Ø¿ô mdatabase_tag () ¤Ï¡¢¥Ç¡¼¥¿ $MDB ¤Î¥¿¥°¡Ê¥·¥ó¥Ü¥ë¡Ë¤ÎÇÛÎó¤òÊÖ¤¹¡£ÇÛÎó¤ÎŤµ¤Ï
1373 @latexonly \IPAlabel{mdatabase_tag} @endlatexonly */
1376 mdatabase_tag (MDatabase *mdb)