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;
636 if (mdb->tag[0] == Mchar_table
637 && mdb->tag[2] != Mnil
638 && (mdb->tag[1] == Mstring || mdb->tag[1] == Mtext
639 || mdb->tag[1] == Msymbol || mdb->tag[1] == Minteger
640 || mdb->tag[1] == Mplist))
641 mchar__define_prop (mdb->tag[2], mdb->tag[1], mdb);
646 register_databases_in_files (MSymbol tags[4], glob_t *globbuf)
649 MPlist *load_key = mplist ();
653 for (i = 0; i < globbuf->gl_pathc; i++)
655 if (! (fp = fopen (globbuf->gl_pathv[i], "r")))
657 plist = mplist__from_file (fp, load_key);
661 if (MPLIST_PLIST_P (plist))
666 for (j = 0, pl = MPLIST_PLIST (plist); j < 4 && MPLIST_SYMBOL_P (pl);
667 j++, pl = MPLIST_NEXT (pl))
668 tags2[j] = MPLIST_SYMBOL (pl);
671 for (j = 0; j < 4; j++)
672 if (tags[j] == Masterisk ? tags2[j] == Mnil
673 : (tags[j] != Mnil && tags[j] != tags2[j]))
676 register_database (tags2, load_database, globbuf->gl_pathv[i], 1);
678 M17N_OBJECT_UNREF (plist);
680 M17N_OBJECT_UNREF (load_key);
686 /** List of database directories. */
687 MPlist *mdatabase__dir_list;
692 MDatabaseInfo *dir_info;
695 Mchar_table = msymbol ("char-table");
696 Masterisk = msymbol ("*");
698 mdatabase__dir_list = mplist ();
699 /** The macro M17NDIR specifies a directory where the system-wide
700 MDB_DIR file exists. */
701 mplist_set (mdatabase__dir_list, Mt, get_dir_info (M17NDIR));
703 /* The variable mdatabase_dir specifies a directory where an
704 application program specific MDB_DIR file exists. */
705 if (mdatabase_dir && strlen (mdatabase_dir) > 0)
706 mplist_push (mdatabase__dir_list, Mt, get_dir_info (mdatabase_dir));
708 /* The environment variable M17NDIR specifies a directory where a
709 user specific MDB_DIR file exists. */
710 path = getenv ("M17NDIR");
711 if (path && strlen (path) > 0)
712 mplist_push (mdatabase__dir_list, Mt, get_dir_info (path));
715 /* If the env var M17NDIR is not set, check "~/.m17n.d". */
716 char *home = getenv ("HOME");
720 && (len = strlen (home))
721 && (path = alloca (len + 9)))
724 if (path[len - 1] != PATH_SEPARATOR)
725 path[len++] = PATH_SEPARATOR;
726 strcpy (path + len, ".m17n.d");
727 dir_info = get_dir_info (path);
728 mplist_push (mdatabase__dir_list, Mt, dir_info);
731 mplist_push (mdatabase__dir_list, Mt, get_dir_info (NULL));
734 mdatabase__finder = ((void *(*) (MSymbol, MSymbol, MSymbol, MSymbol))
736 mdatabase__loader = (void *(*) (void *)) mdatabase_load;
738 mdatabase__list = mplist ();
739 mdatabase__update ();
744 mdatabase__fini (void)
746 MPlist *plist, *p0, *p1, *p2, *p3;
748 MPLIST_DO (plist, mdatabase__dir_list)
749 free_db_info (MPLIST_VAL (plist));
750 M17N_OBJECT_UNREF (mdatabase__dir_list);
752 /* MDATABASE_LIST ::= ((TAG0 (TAG1 (TAG2 (TAG3 t:MDB) ...) ...) ...) ...) */
753 MPLIST_DO (plist, mdatabase__list)
755 p0 = MPLIST_PLIST (plist);
756 /* P0 ::= (TAG0 (TAG1 (TAG2 (TAG3 t:MDB) ...) ...) ...) */
757 MPLIST_DO (p0, MPLIST_NEXT (p0))
759 p1 = MPLIST_PLIST (p0);
760 /* P1 ::= (TAG1 (TAG2 (TAG3 t:MDB) ...) ...) */
761 MPLIST_DO (p1, MPLIST_NEXT (p1))
763 p2 = MPLIST_PLIST (p1);
764 /* P2 ::= (TAG2 (TAG3 t:MDB) ...) */
765 MPLIST_DO (p2, MPLIST_NEXT (p2))
769 p3 = MPLIST_PLIST (p2); /* P3 ::= (TAG3 t:MDB) */
770 p3 = MPLIST_NEXT (p3);
771 mdb = MPLIST_VAL (p3);
772 if (mdb->loader == load_database)
773 free_db_info (mdb->extra_info);
779 M17N_OBJECT_UNREF (mdatabase__list);
783 mdatabase__update (void)
785 MPlist *plist, *p0, *p1, *p2, *p3;
786 char path[PATH_MAX + 1];
787 MDatabaseInfo *dir_info;
790 /* At first, mark all databases defined automatically from mdb.dir
791 file(s) as "disabled". */
792 MPLIST_DO (plist, mdatabase__list)
794 p0 = MPLIST_PLIST (plist);
795 /* P0 ::= (TAG0 (TAG1 (TAG2 (TAG3 MDB) ...) ...) ...) */
796 MPLIST_DO (p0, MPLIST_NEXT (p0))
798 p1 = MPLIST_PLIST (p0);
799 MPLIST_DO (p1, MPLIST_NEXT (p1))
801 p2 = MPLIST_PLIST (p1);
802 MPLIST_DO (p2, MPLIST_NEXT (p2))
804 MDatabaseInfo *db_info;
806 p3 = MPLIST_PLIST (p2);
807 p3 = MPLIST_NEXT (p3);
808 db_info = MPLIST_VAL (p3);
809 if (db_info->status == MDB_STATUS_AUTO)
810 db_info->status = MDB_STATUS_DISABLED;
816 /* Update elements of mdatabase__dir_list. */
817 MPLIST_DO (plist, mdatabase__dir_list)
819 dir_info = MPLIST_VAL (plist);
820 dir_info->status = ((dir_info->filename
821 && stat (dir_info->filename, &statbuf) == 0
822 && (statbuf.st_mode & S_IFDIR))
823 ? MDB_STATUS_AUTO : MDB_STATUS_DISABLED);
826 MPLIST_DO (plist, mdatabase__dir_list)
828 MDatabaseInfo *dir_info = MPLIST_VAL (plist);
833 if (dir_info->status == MDB_STATUS_DISABLED)
835 if (! GEN_PATH (path, dir_info->filename, dir_info->len,
836 MDB_DIR, MDB_DIR_LEN))
838 if (stat (path, &statbuf) < 0)
840 if (dir_info->time >= statbuf.st_mtime)
842 dir_info->time = statbuf.st_mtime;
843 if (! (fp = fopen (path, "r")))
845 pl = mplist__from_file (fp, NULL);
855 int with_wildcard = 0;
857 if (! MPLIST_PLIST_P (p))
859 for (i = 0, p1 = MPLIST_PLIST (p); i < 4 && MPLIST_SYMBOL_P (p1);
860 i++, p1 = MPLIST_NEXT (p1))
861 with_wildcard |= ((tags[i] = MPLIST_SYMBOL (p1)) == Masterisk);
863 || tags[0] == Masterisk
864 || ! MPLIST_MTEXT_P (p1))
868 mt = MPLIST_MTEXT (p1);
869 if (mt->format >= MTEXT_FORMAT_UTF_16LE)
870 mtext__adjust_format (mt, MTEXT_FORMAT_UTF_8);
871 nbytes = mtext_nbytes (mt);
872 if (nbytes > PATH_MAX)
874 memcpy (path, MTEXT_DATA (mt), nbytes);
881 if (tags[0] == Mchar_table || tags[0] == Mcharset)
883 if (path[0] == PATH_SEPARATOR)
885 if (glob (path, GLOB_NOSORT, NULL, &globbuf))
887 register_databases_in_files (tags, &globbuf);
891 MPLIST_DO (dlist, mdatabase__dir_list)
893 MDatabaseInfo *d_info = MPLIST_VAL (dlist);
895 if (d_info->status == MDB_STATUS_DISABLED)
897 if (! GEN_PATH (path, d_info->filename, d_info->len,
898 MTEXT_DATA (mt), nbytes))
900 if (glob (path, GLOB_NOSORT, NULL, &globbuf))
902 register_databases_in_files (tags, &globbuf);
908 register_database (tags, load_database, path, 1);
911 M17N_OBJECT_UNREF (pl);
916 mdatabase__load_for_keys (MDatabase *mdb, MPlist *keys)
918 int mdebug_mask = MDEBUG_DATABASE;
919 MDatabaseInfo *db_info;
925 if (mdb->loader != load_database
926 || mdb->tag[0] == Mchar_table
927 || mdb->tag[0] == Mcharset)
928 MERROR (MERROR_DB, NULL);
929 MDEBUG_PRINT1 (" [DATABASE] loading <%s>.\n",
930 gen_database_name (name, mdb->tag));
931 db_info = mdb->extra_info;
932 filename = get_database_file (db_info, NULL);
933 if (! filename || ! (fp = fopen (filename, "r")))
934 MERROR (MERROR_DB, NULL);
935 plist = mplist__from_file (fp, keys);
941 /* Check if the database MDB should be reloaded or not. It returns:
943 1: The database has not been updated since it was loaded last
946 0: The database has never been loaded or has been updated
947 since it was loaded last time.
949 -1: The database is not loadable at the moment. */
952 mdatabase__check (MDatabase *mdb)
954 MDatabaseInfo *db_info = (MDatabaseInfo *) mdb->extra_info;
957 if (! get_database_file (db_info, &buf))
959 if (db_info->time < buf.st_mtime)
964 /* Search directories in mdatabase__dir_list for file FILENAME. If
965 the file exist, return the absolute pathname. If FILENAME is
966 already absolute, return a copy of it. */
969 mdatabase__find_file (char *filename)
972 MDatabaseInfo db_info;
974 if (filename[0] == PATH_SEPARATOR)
975 return (stat (filename, &buf) == 0 ? filename : NULL);
976 db_info.filename = filename;
977 db_info.len = strlen (filename);
979 db_info.absolute_filename = NULL;
980 if (! get_database_file (&db_info, &buf)
981 || stat (db_info.absolute_filename, &buf) < 0)
983 return db_info.absolute_filename;
987 mdatabase__file (MDatabase *mdb)
989 MDatabaseInfo *db_info;
991 if (mdb->loader != load_database)
993 db_info = mdb->extra_info;
994 return get_database_file (db_info, NULL);
998 mdatabase__lock (MDatabase *mdb)
1000 MDatabaseInfo *db_info;
1006 if (mdb->loader != load_database)
1008 db_info = mdb->extra_info;
1009 if (db_info->lock_file)
1011 file = get_database_file (db_info, NULL);
1014 len = strlen (file);
1015 db_info->uniq_file = malloc (len + 35);
1016 if (! db_info->uniq_file)
1018 db_info->lock_file = malloc (len + 5);
1019 if (! db_info->lock_file)
1021 free (db_info->uniq_file);
1024 sprintf (db_info->uniq_file, "%s.%X.%X", db_info->absolute_filename,
1025 (unsigned) time (NULL), (unsigned) getpid ());
1026 sprintf (db_info->lock_file, "%s.LCK", db_info->absolute_filename);
1028 fp = fopen (db_info->uniq_file, "w");
1031 free (db_info->uniq_file);
1032 free (db_info->lock_file);
1033 db_info->lock_file = NULL;
1037 if (link (db_info->uniq_file, db_info->lock_file) < 0
1038 && (stat (db_info->uniq_file, &buf) < 0
1039 || buf.st_nlink != 2))
1041 unlink (db_info->uniq_file);
1042 unlink (db_info->lock_file);
1043 free (db_info->uniq_file);
1044 free (db_info->lock_file);
1045 db_info->lock_file = NULL;
1052 mdatabase__save (MDatabase *mdb, MPlist *data)
1054 MDatabaseInfo *db_info;
1060 if (mdb->loader != load_database)
1062 db_info = mdb->extra_info;
1063 if (! db_info->lock_file)
1065 file = get_database_file (db_info, NULL);
1069 if (mplist__serialize (mt, data, 1) < 0)
1071 M17N_OBJECT_UNREF (mt);
1074 fp = fopen (db_info->uniq_file, "w");
1077 M17N_OBJECT_UNREF (mt);
1080 mconv_encode_stream (msymbol ("utf-8"), mt, fp);
1081 M17N_OBJECT_UNREF (mt);
1083 if ((ret = rename (db_info->uniq_file, file)) < 0)
1084 unlink (db_info->uniq_file);
1085 free (db_info->uniq_file);
1086 db_info->uniq_file = NULL;
1091 mdatabase__unlock (MDatabase *mdb)
1093 MDatabaseInfo *db_info;
1095 if (mdb->loader != load_database)
1097 db_info = mdb->extra_info;
1098 if (! db_info->lock_file)
1100 unlink (db_info->lock_file);
1101 free (db_info->lock_file);
1102 db_info->lock_file = NULL;
1103 if (db_info->uniq_file)
1104 free (db_info->uniq_file);
1109 #endif /* !FOR_DOXYGEN || DOXYGEN_INTERNAL_MODULE */
1114 /*** @addtogroup m17nDatabase */
1119 @brief Directory for application specific data.
1121 If an application program wants to provide a data specific to the
1122 program or a data overriding what supplied by the m17n database,
1123 it must set this variable to a name of directory that contains the
1124 data files before it calls the macro M17N_INIT (). The directory
1125 may contain a file "mdb.dir" which contains a list of data
1126 definitions in the format described in @ref mdbDir "mdbDir(5)".
1128 The default value is NULL. */
1130 @brief ¥¢¥×¥ê¥±¡¼¥·¥ç¥ó¸ÇͤΥǡ¼¥¿Íѥǥ£¥ì¥¯¥È¥ê.
1132 ¥¢¥×¥ê¥±¡¼¥·¥ç¥ó¥×¥í¥°¥é¥à¤¬¡¢¤½¤Î¥×¥í¥°¥é¥à¸ÇͤΥǡ¼¥¿¤ä m17n
1133 ¥Ç¡¼¥¿¥Ù¡¼¥¹¤ò¾å½ñ¤¤¹¤ë¥Ç¡¼¥¿¤òÄ󶡤¹¤ë¾ì¹ç¤Ë¤Ï¡¢¥Þ¥¯¥í M17N_INIT ()
1134 ¤ò¸Æ¤ÖÁ°¤Ë¤³¤ÎÊÑ¿ô¤ò¥Ç¡¼¥¿¥Õ¥¡¥¤¥ë¤ò´Þ¤à¥Ç¥£¥ì¥¯¥È¥ê̾¤Ë¥»¥Ã¥È¤·¤Ê¤¯¤Æ¤Ï¤Ê¤é¤Ê¤¤¡£¥Ç¥£¥ì¥¯¥È¥ê¤Ë¤Ï
1135 "mdb.dir" ¥Õ¥¡¥¤¥ë¤ò¤ª¤¯¤³¤È¤¬¤Ç¤¤ë¡£¤½¤Î"mdb.dir"¥Õ¥¡¥¤¥ë¤Ë¤Ï¡¢
1136 @ref mdbDir "mdbDir(5)" ¤ÇÀâÌÀ¤µ¤ì¤Æ¤¤¤ë¥Õ¥©¡¼¥Þ¥Ã¥È¤Ç¥Ç¡¼¥¿ÄêµÁ¤Î¥ê¥¹¥È¤òµ½Ò¤¹¤ë¡£
1138 ¥Ç¥Õ¥©¥ë¥È¤ÎÃÍ¤Ï NULL ¤Ç¤¢¤ë¡£ */
1140 char *mdatabase_dir;
1144 @brief Look for a data in the database.
1146 The mdatabase_find () function searches the m17n database for a
1147 data who has tags $TAG0 through $TAG3, and returns a pointer to
1148 the data. If such a data is not found, it returns @c NULL. */
1151 @brief ¥Ç¡¼¥¿¥Ù¡¼¥¹Ãæ¤Î¥Ç¡¼¥¿¤òõ¤¹.
1153 ´Ø¿ô mdatabase_find () ¤Ï¡¢ m17n ¸À¸ì¾ðÊó¥Ù¡¼¥¹Ãæ¤Ç $TAG0 ¤«¤é
1154 $TAG3 ¤Þ¤Ç¤Î¥¿¥°¤ò»ý¤Ä¥Ç¡¼¥¿¤òõ¤·¡¢¤½¤ì¤Ø¤Î¥Ý¥¤¥ó¥¿¤òÊÖ¤¹¡£¤½¤Î¤è¤¦¤Ê¥Ç¡¼¥¿¤¬¤Ê¤±¤ì¤Ð
1157 @latexonly \IPAlabel{mdatabase_find} @endlatexonly */
1160 mdatabase_find (MSymbol tag0, MSymbol tag1, MSymbol tag2, MSymbol tag3)
1164 mdatabase__update ();
1165 tags[0] = tag0, tags[1] = tag1, tags[2] = tag2, tags[3] = tag3;
1166 return find_database (tags);
1171 @brief Return a data list of the m17n database.
1173 The mdatabase_list () function searches the m17n database for data
1174 who have tags $TAG0 through $TAG3, and returns their list by a
1175 plist. The value #Mnil in $TAGn means a wild card that matches
1176 any tag. Each element of the plist has key #Mt and value a
1177 pointer to type #MDatabase. */
1179 @brief m17n ¥Ç¡¼¥¿¥Ù¡¼¥¹¤Î¥Ç¡¼¥¿¥ê¥¹¥È¤òÊÖ¤¹.
1181 ´Ø¿ô mdatabase_list () ¤Ï m17n ¥Ç¡¼¥¿¥Ù¡¼¥¹Ã椫¤é $TAG0 ¤«¤é$TAG3
1182 ¤Þ¤Ç¤Î¥¿¥°¤ò»ý¤Ä¥Ç¡¼¥¿¤òõ¤·¡¢¤½¤Î¥ê¥¹¥È¤òplist ¤È¤·¤ÆÊÖ¤¹¡£ $TAGn ¤¬ #Mnil
1183 ¤Ç¤¢¤Ã¤¿¾ì¹ç¤Ë¤Ï¡¢Ç¤°Õ¤Î¥¿¥°¤Ë¥Þ¥Ã¥Á¤¹¤ë¥ï¥¤¥ë¥É¥«¡¼¥É¤È¤·¤Æ¼è¤ê°·¤ï¤ì¤ë¡£ÊÖ¤µ¤ì¤ë
1184 plist ¤Î³ÆÍ×ÁǤϥ¡¼ ¤È¤·¤Æ #Mt ¤ò¡¢ÃͤȤ·¤Æ #MDatabase ·¿¤Ø¤Î¥Ý¥¤¥ó¥¿¤ò»ý¤Ä¡£ */
1187 mdatabase_list (MSymbol tag0, MSymbol tag1, MSymbol tag2, MSymbol tag3)
1189 MPlist *plist = mplist (), *pl = plist;
1190 MPlist *p, *p0, *p1, *p2, *p3;
1192 mdatabase__update ();
1194 MPLIST_DO (p, mdatabase__list)
1196 p0 = MPLIST_PLIST (p);
1197 /* P0 ::= (TAG0 (TAG1 (TAG2 (TAG3 MDB) ...) ...) ...) */
1198 if (tag0 != Mnil && MPLIST_SYMBOL (p0) != tag0)
1200 MPLIST_DO (p0, MPLIST_NEXT (p0))
1202 p1 = MPLIST_PLIST (p0);
1203 if (tag1 != Mnil && MPLIST_SYMBOL (p1) != tag1)
1205 MPLIST_DO (p1, MPLIST_NEXT (p1))
1207 p2 = MPLIST_PLIST (p1);
1208 if (tag2 != Mnil && MPLIST_SYMBOL (p2) != tag2)
1210 MPLIST_DO (p2, MPLIST_NEXT (p2))
1212 p3 = MPLIST_PLIST (p2);
1213 if (tag3 != Mnil && MPLIST_SYMBOL (p3) != tag3)
1215 p3 = MPLIST_NEXT (p3);
1216 pl = mplist_add (pl, Mt, MPLIST_VAL (p3));
1221 if (MPLIST_TAIL_P (plist))
1223 M17N_OBJECT_UNREF (plist);
1231 @brief Define a data of the m17n database.
1233 The mdatabase_define () function defines a data that has tags
1234 $TAG0 through $TAG3 and additional information $EXTRA_INFO.
1236 $LOADER is a pointer to a function that loads the data from the
1237 database. This function is called from the mdatabase_load ()
1238 function with the two arguments $TAGS and $EXTRA_INFO. Here,
1239 $TAGS is the array of $TAG0 through $TAG3.
1241 If $LOADER is @c NULL, the default loader of the m17n library is
1242 used. In this case, $EXTRA_INFO must be a string specifying a
1243 filename that contains the data.
1246 If the operation was successful, mdatabase_define () returns a
1247 pointer to the defined data, which can be used as an argument to
1248 mdatabase_load (). Otherwise, it returns @c NULL. */
1251 @brief m17n ¥Ç¡¼¥¿¥Ù¡¼¥¹¤Î¥Ç¡¼¥¿¤òÄêµÁ¤¹¤ë.
1253 ´Ø¿ô mdatabase_define () ¤Ï $TAG0 ¤«¤é $TAG3 ¤Þ¤Ç¤Î¥¿¥°¤ª¤è¤ÓÉղþðÊó
1254 $EXTRA_INFO ¤ò»ý¤Ä¥Ç¡¼¥¿¤òÄêµÁ¤¹¤ë¡£
1256 $LOADER ¤Ï¤½¤Î¥Ç¡¼¥¿¤Î¥í¡¼¥É¤ËÍѤ¤¤é¤ì¤ë´Ø¿ô¤Ø¤Î¥Ý¥¤¥ó¥¿¤Ç¤¢¤ë¡£¤³¤Î´Ø¿ô¤Ï
1257 mdatabase_load () ¤«¤é $TAGS ¤È $EXTRA_INFO ¤È¤¤¤¦Æó¤Ä¤Î°ú¿ôÉÕ¤¤Ç¸Æ¤Ó½Ð¤µ¤ì¤ë¡£¤³¤³¤Ç
1258 $TAGS ¤Ï $TAG0 ¤«¤é $TAG3 ¤Þ¤Ç¤ÎÇÛÎó¤Ç¤¢¤ë¡£
1260 ¤â¤· $LOADER ¤¬ @c NULL ¤Ê¤é¡¢m17n ¥é¥¤¥Ö¥é¥êɸ½à¤Î¥í¡¼¥À¤¬»È¤ï¤ì¤ë¡£¤³¤Î¾ì¹ç¤Ë¤Ï
1261 $EXTRA_INFO ¤Ï¥Ç¡¼¥¿¤ò´Þ¤à¥Õ¥¡¥¤¥ë̾¤Ç¤Ê¤¯¤Æ¤Ï¤Ê¤é¤Ê¤¤¡£
1264 ½èÍý¤ËÀ®¸ù¤¹¤ì¤Ð mdatabase_define ()
1265 ¤ÏÄêµÁ¤µ¤ì¤¿¥Ç¡¼¥¿¥Ù¡¼¥¹¤Ø¤Î¥Ý¥¤¥ó¥¿¤òÊÖ¤¹¡£¤³¤Î¥Ý¥¤¥ó¥¿¤Ï´Ø¿ô mdatabase_load ()
1266 ¤Î°ú¿ô¤È¤·¤ÆÍѤ¤¤ë¤³¤È¤¬¤Ç¤¤ë¡£¤½¤¦¤Ç¤Ê¤±¤ì¤Ð @c NULL ¤òÊÖ¤¹¡£
1268 @latexonly \IPAlabel{mdatabase_define} @endlatexonly */
1272 mdatabase_load (), mdatabase_define () */
1275 mdatabase_define (MSymbol tag0, MSymbol tag1, MSymbol tag2, MSymbol tag3,
1276 void *(*loader) (MSymbol *, void *),
1282 tags[0] = tag0, tags[1] = tag1, tags[2] = tag2, tags[3] = tag3;
1284 loader = load_database;
1285 mdb = register_database (tags, loader, extra_info, 0);
1291 @brief Load a data from the database.
1293 The mdatabase_load () function loads a data specified in $MDB and
1294 returns the contents. The type of contents depends on the type of
1297 If the data is of the @e plist @e type, this function returns a
1298 pointer to @e plist.
1300 If the database is of the @e chartable @e type, it returns a
1301 chartable. The default value of the chartable is set according to
1302 the second tag of the data as below:
1304 @li If the tag is #Msymbol, the default value is #Mnil.
1305 @li If the tag is #Minteger, the default value is -1.
1306 @li Otherwise, the default value is @c NULL.
1308 If the data is of the @e charset @e type, it returns a plist of length 2
1309 (keys are both #Mt). The value of the first element is an array
1310 of integers that maps code points to the corresponding character
1311 codes. The value of the second element is a chartable of integers
1312 that does the reverse mapping. The charset must be defined in
1317 @brief ¥Ç¡¼¥¿¥Ù¡¼¥¹¤«¤é¥Ç¡¼¥¿¤ò¥í¡¼¥É¤¹¤ë.
1319 ´Ø¿ô mdatabase_load () ¤Ï $MDB
1320 ¤¬»Ø¤¹¥Ç¡¼¥¿¤ò¥í¡¼¥É¤·¡¢¤½¤ÎÃæ¿È¤òÊÖ¤¹¡£ÊÖ¤µ¤ì¤ë¤â¤Î¤Ï¥Ç¡¼¥¿¤Î¥¿¥¤¥×¤Ë¤è¤Ã¤Æ°Û¤Ê¤ë¡£
1322 ¥Ç¡¼¥¿¤¬ @e plist¥¿¥¤¥× ¤Ê¤é¤Ð¡¢ @e plist ¤Ø¤Î¥Ý¥¤¥ó¥¿¤òÊÖ¤¹¡£
1324 ¥Ç¡¼¥¿¤¬ @e chartable¥¿¥¤¥× ¤Ê¤é¤Ðʸ»ú¥Æ¡¼¥Ö¥ë¤òÊÖ¤¹¡£
1325 ʸ»ú¥Æ¡¼¥Ö¥ë¤Î¥Ç¥Õ¥©¥ë¥ÈÃͤϡ¢¥Ç¡¼¥¿¤ÎÂè2¥¿¥°¤Ë¤è¤Ã¤Æ°Ê²¼¤Î¤è¤¦¤Ë·è¤Þ¤ë¡£
1327 @li ¥¿¥°¤¬ #Msymbol ¤Ê¤é¡¢¥Ç¥Õ¥©¥ë¥ÈÃÍ¤Ï #Mnil
1328 @li ¥¿¥°¤¬ #Minteger ¤Ê¤é¡¢¥Ç¥Õ¥©¥ë¥ÈÃÍ¤Ï -1
1329 @li ¤½¤ì°Ê³°¤Ê¤é¡¢¥Ç¥Õ¥©¥ë¥ÈÃÍ¤Ï @c NULL
1331 ¥Ç¡¼¥¿¤¬ @e charset¥¿¥¤¥× ¤Ê¤é¤ÐŤµ 2 ¤Î plist ¤òÊÖ¤¹¡Ê¥¡¼¤Ï¶¦¤Ë#Mt ¡Ë¡£
1332 ºÇ½é¤ÎÍ×ÁǤÎÃͤϥ³¡¼¥É¥Ý¥¤¥ó¥È¤òÂбþ¤¹¤ëʸ»ú¥³¡¼¥É¤Ë¥Þ¥Ã¥×¤¹¤ëÀ°¿ô¤ÎÇÛÎó¤Ç¤¢¤ë¡£
1333 £²ÈÖÌܤÎÍ×ÁǤÎÃͤϵդΥޥåפò¤¹¤ëʸ»ú¥Æ¡¼¥Ö¥ë¤Ç¤¢¤ë¡£
1334 ¤³¤Îʸ»ú¥»¥Ã¥È¤Ïͽ¤áÄêµÁ¤µ¤ì¤Æ¤¤¤Ê¤±¤ì¤Ð¤Ê¤é¤Ê¤¤¡£
1336 @latexonly \IPAlabel{mdatabase_load} @endlatexonly
1341 mdatabase_load (), mdatabase_define () */
1344 mdatabase_load (MDatabase *mdb)
1346 int mdebug_mask = MDEBUG_DATABASE;
1349 MDEBUG_PRINT1 (" [DATABASE] loading <%s>.\n",
1350 gen_database_name (buf, mdb->tag));
1351 return (*mdb->loader) (mdb->tag, mdb->extra_info);
1356 @brief Get tags of a data.
1358 The mdatabase_tag () function returns an array of tags (symbols)
1359 that identify the data in $MDB. The length of the array is
1363 @brief ¥Ç¡¼¥¿¤Î¥¿¥°¤òÆÀ¤ë.
1365 ´Ø¿ô mdatabase_tag () ¤Ï¡¢¥Ç¡¼¥¿ $MDB ¤Î¥¿¥°¡Ê¥·¥ó¥Ü¥ë¡Ë¤ÎÇÛÎó¤òÊÖ¤¹¡£ÇÛÎó¤ÎŤµ¤Ï
1368 @latexonly \IPAlabel{mdatabase_tag} @endlatexonly */
1371 mdatabase_tag (MDatabase *mdb)