(update_database_list): Call mchar__define_prop if
[m17n/m17n-lib.git] / src / database.c
1 /* database.c -- database module.
2    Copyright (C) 2003, 2004
3      National Institute of Advanced Industrial Science and Technology (AIST)
4      Registration Number H15PRO112
5
6    This file is part of the m17n library.
7
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.
12
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.
17
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
21    02111-1307, USA.  */
22
23 /***en
24     @addtogroup m17nDatabase
25     @brief The m17n database and API for it.
26
27     The m17n library dynamically acquires various kinds of information
28     in need from data in the <i> m17n database</i>.  Application
29     programs can also add/load their original data to/from the m17n
30     database.  The m17n database contains multiple heterogeneous data,
31     and each data is identified by four tags; TAG0, TAG1, TAG2, TAG3.
32     Each tag must be a symbol.
33
34     TAG0 specifies the type of data stored in the database as below.
35
36     @li
37     If TAG0 is #Mchar_table, the data is of the @e chartable @e
38     type and provides information about each character.  In this case,
39     TAG1 specifies the type of the information and must be #Msymbol,
40     #Minteger, #Mstring, #Mtext, or #Mplist.  TAG2 and TAG3 can be any
41     symbols.
42
43     @li
44     If TAG0 is #Mcharset, the data is of the @e charset @e type
45     and provides a decode/encode mapping table for a charset.  In this
46     case, TAG1 must be a symbol representing a charset.  TAG2 and TAG3
47     can be any symbols.
48
49     @li 
50     If TAG0 is neither #Mchar_table nor #Mcharset, the data is of
51     the @e plist @e type.  See the documentation of the 
52     mdatabase_load () function for the details.  
53     In this case, TAG1, TAG2, and TAG3 can be any symbols.
54
55     The notation \<TAG0, TAG1, TAG2, TAG3\> means a data with those
56     tags.
57
58     Application programs first calls the mdatabase_find () function to
59     get a pointer to an object of the type #MDatabase.  That object
60     holds information about the specified data.  When it is
61     successfully returned, the mdatabase_load () function loads the
62     data.  The implementation of the structure #MDatabase is
63     concealed from application programs.
64 */
65
66 /***ja
67     @addtogroup m17nDatabase
68     @brief m17n ¥Ç¡¼¥¿¥Ù¡¼¥¹¤Ë¤È¤½¤ì¤Ë´Ø¤¹¤ë API.
69
70     m17n ¥é¥¤¥Ö¥é¥ê¤ÏɬÍפ˱þ¤¸¤ÆưŪ¤Ë @e m17n @e ¥Ç¡¼¥¿¥Ù¡¼¥¹ 
71     ¤«¤é¾ðÊó¤ò¼èÆÀ¤¹¤ë¡£¤Þ¤¿¡¢¥¢¥×¥ê¥±¡¼¥·¥ç¥ó¥×¥í¥°¥é¥à¤âÆȼ«¤Î¥Ç¡¼¥¿¤ò 
72     m17n ¥Ç¡¼¥¿¥Ù¡¼¥¹¤ËÄɲä·¡¢¤½¤ì¤òưŪ¤Ë¼èÆÀ¤¹¤ë¤³¤È¤¬¤Ç¤­¤ë¡£m17n 
73     ¥Ç¡¼¥¿¥Ù¡¼¥¹¤Ë¤ÏÊ£¿ô¤Î¿Íͤʥǡ¼¥¿¤¬´Þ¤Þ¤ì¤Æ¤ª¤ê¡¢³Æ¥Ç¡¼¥¿¤Ï
74     TAG0, TAG1, TAG2, TAG3¡Ê¤¹¤Ù¤Æ¥·¥ó¥Ü¥ë¡Ë¤Î£´¤Ä¤Î¥¿¥°¤Ë¤è¤Ã¤Æ¼±Ê̤µ¤ì¤ë¡£
75
76     TAG0 ¤Ë¤è¤Ã¤Æ¡¢¥Ç¡¼¥¿¥Ù¡¼¥¹Æâ¤Î¥Ç¡¼¥¿¤Î¥¿¥¤¥×¤Ï¼¡¤Î¤è¤¦¤Ë»ØÄꤵ¤ì¤ë¡£
77
78     @li 
79     TAG0 ¤¬ #Mchar_table ¤Ç¤¢¤ë¥Ç¡¼¥¿¤Ï @e chartable¥¿¥¤¥× 
80     ¤È¸Æ¤Ð¤ì¡¢³Æʸ»ú¤Ë´Ø¤¹¤ë¾ðÊó¤òÄ󶡤¹¤ë¡£¤³¤Î¾ì¹ç
81     TAG1 ¤Ï¾ðÊó¤Î¼ïÎà¤ò»ØÄꤹ¤ë¥·¥ó¥Ü¥ë¤Ç¤¢¤ê¡¢#Msymbol, #Minteger, #Mstring,
82     #Mtext, #Mplist ¤Î¤¤¤º¤ì¤«¤Ç¤¢¤ë¡£TAG2 ¤È TAG3 ¤ÏǤ°Õ¤Î¥·¥ó¥Ü¥ë¤Ç¤è¤¤¡£
83
84     @li 
85     TAG0 ¤¬ #Mcharset ¤Ç¤¢¤ë¥Ç¡¼¥¿¤Ï @e charset¥¿¥¤¥× 
86     ¤È¸Æ¤Ð¤ì¡¢Ê¸»ú¥»¥Ã¥ÈÍѤΥǥ³¡¼¥É¡¿¥¨¥ó¥³¡¼¥É¥Þ¥Ã¥×¤òÄ󶡤¹¤ë¡£¤³¤Î¾ì¹ç TAG1
87     ¤Ïʸ»ú¥»¥Ã¥È¤Î¥·¥ó¥Ü¥ë¤Ç¤Ê¤±¤ì¤Ð¤Ê¤é¤Ê¤¤¡£TAG2 ¤È TAG3
88     ¤ÏǤ°Õ¤Î¥·¥ó¥Ü¥ë¤Ç¤è¤¤¡£
89
90     @li
91     TAG0 ¤¬ #Mchar_table ¤Ç¤â #Mcharset ¤Ç¤â¤Ê¤¤¾ì¹ç¡¢¤½¤Î¥Ç¡¼¥¿¤Ï @e
92     plist¥¿¥¤¥× ¤Ç¤¢¤ë¡£¾ÜºÙ¤Ë´Ø¤·¤Æ¤Ï´Ø¿ô mdatabase_load () 
93     ¤ÎÀâÌÀ¤ò»²¾È¤Î¤³¤È¡£¤³¤Î¾ì¹ç TAG1¡¢TAG2¡¢TAG3 ¤ÏǤ°Õ¤Î¥·¥ó¥Ü¥ë¤Ç¤è¤¤¡£
94
95     ÆÃÄê¤Î¥¿¥°¤ò»ý¤Ä¥Ç¡¼¥¿¥Ù¡¼¥¹¤ò \<TAG0, TAG1, TAG2, TAG3\> 
96     ¤È¤¤¤¦·Á¼°¤Çɽ¤¹¡£
97
98     ¥¢¥×¥ê¥±¡¼¥·¥ç¥ó¥×¥í¥°¥é¥à¤Ï¡¢¤Þ¤º´Ø¿ô mdatabase_find () 
99     ¤ò»È¤Ã¤Æ¥Ç¡¼¥¿¥Ù¡¼¥¹¤Ë´Ø¤¹¤ë¾ðÊó¤òÊÝ»ý¤¹¤ë¥ª¥Ö¥¸¥§¥¯¥È¡Ê#MDatabase
100     ·¿¡Ë¤Ø¤Î¥Ý¥¤¥ó¥¿¤òÆÀ¤ë¡£¤½¤ì¤ËÀ®¸ù¤·¤¿¤é¡¢ mdatabase_load () 
101     ¤Ë¤è¤Ã¤Æ¼ÂºÝ¤Ë¥Ç¡¼¥¿¥Ù¡¼¥¹¤ò¥í¡¼¥É¤¹¤ë¡£¹½Â¤ÂΠ#MDatabase 
102     ¼«¿È¤¬¤É¤¦¼ÂÁõ¤µ¤ì¤Æ¤¤¤ë¤«¤Ï¡¢¥¢¥×¥ê¥±¡¼¥·¥ç¥ó¥×¥í¥°¥é¥à¤«¤é¤Ï¸«¤¨¤Ê¤¤¡£
103
104     @latexonly \IPAlabel{database} @endlatexonly
105 */
106
107 /*=*/
108
109 #if !defined (FOR_DOXYGEN) || defined (DOXYGEN_INTERNAL_MODULE)
110 /*** @addtogroup m17nInternal
111      @{ */
112
113 #include <config.h>
114 #include <stdio.h>
115 #include <stdlib.h>
116 #include <string.h>
117 #include <ctype.h>
118 #include <sys/types.h>
119 #include <sys/stat.h>
120 #include <unistd.h>
121 #include <limits.h>
122 #include <glob.h>
123
124 #include "m17n.h"
125 #include "m17n-misc.h"
126 #include "internal.h"
127 #include "mtext.h"
128 #include "character.h"
129 #include "charset.h"
130 #include "database.h"
131 #include "coding.h"
132 #include "plist.h"
133
134 /** The file containing a list of databases.  */
135 #define MDB_DIR "mdb.dir"
136 #define MDB_DIR_LEN 8
137
138 #define MAX_TIME(TIME1, TIME2) ((TIME1) >= (TIME2) ? (TIME1) : (TIME2))
139
140 static MSymbol Masterisk;
141
142 /** Structure for a data in the m17n database.  */
143
144 struct MDatabase
145 {
146   /** Tags to identify the data.  <tag>[0] specifies the type of
147       database.  If it is #Mchar_table, the type is @e chartable, if
148       it is #Mcharset, the type is @e charset, otherwise the type is
149       @e plist.  */
150   MSymbol tag[4];
151
152   void *(*loader) (MSymbol *tags, void *extra_info);
153
154   /** The meaning of the value is dependent on <loader>.  If <loader>
155       is load_database (), the value is a string of the file name that
156       contains the data.  */
157   void *extra_info;
158 };
159
160 typedef struct
161 {
162   char *filename;
163   time_t time;
164 } MDatabaseInfo;
165
166 /** List of all data.  */
167 struct MDatabaseList
168 {
169   int size, inc, used;
170   MDatabase *mdbs;
171 };
172
173 static struct MDatabaseList mdb_list;
174
175
176 static int
177 read_number (char *buf, int *i)
178 {
179   int idx = *i;
180   int c = buf[idx++];
181   int n;
182
183   if (!c)
184     return -1;
185
186   while (c && isspace (c)) c = buf[idx++];
187
188   if (c == '0')
189     {
190       if (buf[idx] == 'x')
191         {
192           for (idx++, c = 0; (n = hex_mnemonic[(unsigned) buf[idx]]) < 16;
193                idx++)
194             c  = (c << 4) | n;
195           *i = idx;
196           return c;
197         }
198       c = 0;
199     }
200   else if (c == '\'')
201     {
202       c = buf[idx++];
203       if (c == '\\')
204         {
205           c = buf[idx++];
206           n = escape_mnemonic[c];
207           if (n != 255)
208             c = n;
209         }
210       while (buf[idx] && buf[idx++] != '\'');
211       *i = idx;
212       return c;
213     }
214   else if (hex_mnemonic[c] < 10)
215     c -= '0';
216   else
217     return -1;
218
219   while ((n = hex_mnemonic[(unsigned) buf[idx]]) < 10)
220     c = (c * 10) + n, idx++;
221   *i = idx;
222   return c;
223 }
224
225
226 /** Load a data of type @c chartable from the file FD, and return the
227     newly created chartable.  */
228
229 static void *
230 load_chartable (FILE *fp, MSymbol type)
231 {
232   int c, from, to;
233   char buf[1024];
234   void *val;
235   MCharTable *table;
236
237   if (! fp)
238     MERROR (MERROR_DB, NULL);
239
240   table = mchartable (type, (type == Msymbol ? (void *) Mnil
241                              : type == Minteger ? (void *) -1
242                              : NULL));
243
244   while (! feof (fp))
245     {
246       int i, len;
247
248       for (len = 0; len < 1023 && (c = getc (fp)) != EOF && c != '\n'; len++)
249         buf[len] = c;
250       buf[len] = '\0';    
251       if (hex_mnemonic[(unsigned) buf[0]] >= 10)
252         /* skip comment/invalid line */
253         continue;
254       i = 0;
255       from = read_number (buf, &i);
256       if (buf[i] == '-')
257         i++, to = read_number (buf, &i);
258       else
259         to = from;
260       if (from < 0 || to < from)
261         continue;
262
263       while (buf[i] && isspace ((unsigned) buf[i])) i++;
264       c = buf[i];
265       if (!c)
266         continue;
267
268       if (type == Mstring)
269         {
270           /* VAL is a C-string.  */
271           if (! (val = strdup (buf + i)))
272             MEMORY_FULL (MERROR_DB);
273         }
274       else if (type == Minteger)
275         {
276           /* VAL is an integer.  */
277           int positive = 1;
278           int n;
279
280           if (c == '-')
281             i++, positive = -1;
282           n = read_number (buf, &i);
283           if (n < 0)
284             goto label_error;
285           val = (void *) (n * positive);
286         }
287       else if (type == Mtext)
288         {
289           /* VAL is an M-text.  */
290           MText *mt;
291           if (c == '"')
292             mt = mconv_decode_buffer (Mcoding_utf_8,
293                                       (unsigned char *) (buf + i),
294                                       len - i - 1);
295           else
296             {
297               mt = mtext ();
298               while ((c = read_number (buf, &i)) >= 0)
299                 mt = mtext_cat_char (mt, c);
300             }
301           val = (void *) mt;
302         }
303       else if (type == Msymbol)
304         {
305           char *p = buf + i;
306
307           while (*p && ! isspace (*p)) 
308             {
309               if (*p == '\\' && p[1] != '\0')
310                 {
311                   memmove (p, p + 1, buf + len - (p + 1));
312                   len--;
313                 }
314               p++;
315             }
316           *p = '\0';
317           if (! strcmp (buf + i, "nil"))
318             val = (void *) Mnil;
319           else
320             val = (void *) msymbol (buf + i);
321         }
322       else if (type == Mplist)
323         {
324           val = (void *) mplist__from_string ((unsigned char *) buf + i,
325                                               strlen (buf + i));
326         }
327       else
328         val = NULL;
329
330       if (from == to)
331         mchartable_set (table, from, val);
332       else
333         mchartable_set_range (table, from, to, val);
334     }
335   return table;
336
337  label_error:
338   M17N_OBJECT_UNREF (table);
339   MERROR (MERROR_DB, NULL);
340 }
341
342
343 /** Load a data of type @c charset from the file FD.  */
344
345 static void *
346 load_charset (FILE *fp, MSymbol charset_name)
347 {
348   MCharset *charset = MCHARSET (charset_name);
349   int *decoder;
350   MCharTable *encoder;
351   int size;
352   int i, c;
353   int found = 0;
354   MPlist *plist;
355
356   if (! charset)
357     MERROR (MERROR_DB, NULL);
358   size = (charset->code_range[15]
359           - (charset->min_code - charset->code_range_min_code));
360   MTABLE_MALLOC (decoder, size, MERROR_DB);
361   for (i = 0; i < size; i++)
362     decoder[i] = -1;
363   encoder = mchartable (Minteger, (void *) MCHAR_INVALID_CODE);
364
365   while ((c = getc (fp)) != EOF)
366     {
367       unsigned code1, code2, c1, c2;
368       int idx1, idx2;
369       char buf[256];
370
371       ungetc (c, fp);
372       fgets (buf, 256, fp);
373       if (c != '#')
374         {
375           if (sscanf (buf, "0x%x-0x%x 0x%x", &code1, &code2, &c1) == 3)
376             {
377               idx1 = CODE_POINT_TO_INDEX (charset, code1);
378               if (idx1 >= size)
379                 continue;
380               idx2 = CODE_POINT_TO_INDEX (charset, code2);
381               if (idx2 >= size)
382                 idx2 = size - 1;
383               c2 = c1 + (idx2 - idx1);
384             }
385           else if (sscanf (buf, "0x%x 0x%x", &code1, &c1) == 2)
386             {
387               idx1 = idx2 = CODE_POINT_TO_INDEX (charset, code1);
388               if (idx1 >= size)
389                 continue;
390               c2 = c1;
391             }
392           else
393             continue;
394           if (idx1 >= 0 && idx2 >= 0)
395             {
396               decoder[idx1] = c1;
397               mchartable_set (encoder, c1, (void *) code1);
398               for (idx1++, c1++; idx1 <= idx2; idx1++, c1++)
399                 {
400                   code1 = INDEX_TO_CODE_POINT (charset, idx1);
401                   decoder[idx1] = c1;
402                   mchartable_set (encoder, c1, (void *) code1);
403                 }
404               found++;
405             }
406         }
407     }
408
409   if (! found)
410     {
411       free (decoder);
412       M17N_OBJECT_UNREF (encoder);
413       return NULL;
414     }
415   plist = mplist ();
416   mplist_add (plist, Mt, decoder);
417   mplist_add (plist, Mt, encoder);
418   return plist;
419 }
420
421 static char *
422 gen_database_name (char *buf, MSymbol *tags)
423 {
424   int i;
425
426   strcpy (buf, msymbol_name (tags[0]));
427   for (i = 1; i < 4; i++)
428     {
429       strcat (buf, ", ");
430       strcat (buf, msymbol_name (tags[i]));
431     }
432   return buf;
433 }
434
435 static FILE *
436 get_database_stream (MDatabaseInfo *db_info)
437 {
438   FILE *fp = NULL;
439   struct stat buf;
440
441   if (db_info->filename[0] == '/')
442     {
443       if (stat (db_info->filename, &buf) == 0
444           && (fp = fopen (db_info->filename, "r")))
445         db_info->time = MAX_TIME (buf.st_mtime, buf.st_ctime);
446     }
447   else
448     {
449       MPlist *plist;
450       char *path;
451       int filelen = strlen (db_info->filename);
452       USE_SAFE_ALLOCA;
453
454       MPLIST_DO (plist, mdatabase__dir_list)
455         {
456           MDatabaseInfo *dir_info = MPLIST_VAL (plist);
457           int require = strlen (dir_info->filename) + filelen + 1;
458
459           SAFE_ALLOCA (path, require);
460           strcpy (path, dir_info->filename);
461           strcat (path, db_info->filename);
462           if (stat (path, &buf) == 0
463               && (fp = fopen (path, "r")))
464             {
465               free (db_info->filename);
466               db_info->filename = strdup (path);
467               db_info->time = MAX_TIME (buf.st_mtime, buf.st_ctime);
468               break;
469             }
470         }
471       SAFE_FREE (path);
472     }
473   return fp;
474 }
475
476 static void *
477 load_database (MSymbol *tags, void *extra_info)
478 {
479   FILE *fp = get_database_stream ((MDatabaseInfo *) extra_info);
480   void *value;
481
482   if (! fp)
483     MERROR (MERROR_DB, NULL);
484
485   if (tags[0] == Mchar_table)
486     value = load_chartable (fp, tags[1]);
487   else if (tags[0] == Mcharset)
488     value = load_charset (fp, tags[1]);
489   else
490     value = mplist__from_file (fp, NULL);
491   fclose (fp);
492
493   if (! value)
494     MERROR (MERROR_DB, NULL);
495   return value;
496 }
497
498
499 /** If DIRNAME is a readable directory, allocate MDatabaseInfo and
500     copy DIRNAME to a newly allocated memory and return it.  If
501     DIRNAME does not end with a slash, append a slash to the new memory.  */
502
503 static MDatabaseInfo *
504 get_dir_info (char *dirname)
505 {
506   struct stat buf;
507   int len;
508   MDatabaseInfo *dir_info;
509
510   if (! dirname
511       || stat (dirname, &buf) < 0
512       || ! (buf.st_mode & S_IFDIR))
513     return NULL;
514
515   MSTRUCT_MALLOC (dir_info, MERROR_DB);
516   len = strlen (dirname);
517   MTABLE_MALLOC (dir_info->filename, len + 2, MERROR_DB);
518   memcpy (dir_info->filename, dirname, len + 1);
519   if (dir_info->filename[len - 1] != '/')
520     {
521       dir_info->filename[len] = '/';
522       dir_info->filename[len + 1] = '\0';
523     }
524   /* Set this to zero so that the first call of update_database_list
525      surely checks this directory.  */
526   dir_info->time = 0;
527   return dir_info;
528 }
529
530 static MDatabase *
531 find_database (MSymbol tag0, MSymbol tag1, MSymbol tag2, MSymbol tag3)
532 {
533   int i;
534
535   for (i = 0; i < mdb_list.used; i++)
536     {
537       MDatabase *mdb = mdb_list.mdbs + i;
538
539       if (tag0 == mdb->tag[0]
540           && tag1 == mdb->tag[1]
541           && tag2 == mdb->tag[2]
542           && tag3 == mdb->tag[3])
543         return mdb;
544     }
545   return NULL;
546 }
547
548 static void
549 update_database_list ()
550 {
551   MPlist *plist;
552   char *path;
553   USE_SAFE_ALLOCA;
554
555   /* Usually this avoids path to be reallocated.  */
556   SAFE_ALLOCA (path, 256);
557
558   MPLIST_DO (plist, mdatabase__dir_list)
559     {
560       MDatabaseInfo *dir_info = MPLIST_VAL (plist);
561       struct stat statbuf;
562       MPlist *pl, *p;
563       int i, j, len;
564       FILE *fp;
565
566       if (stat (dir_info->filename, &statbuf) < 0)
567         continue;
568       if (dir_info->time >= statbuf.st_ctime
569           && dir_info->time >= statbuf.st_mtime)
570         continue;
571       dir_info->time = MAX_TIME (statbuf.st_ctime, statbuf.st_mtime);
572       len = strlen (dir_info->filename);
573 #ifdef PATH_MAX
574       if (len + MDB_DIR_LEN >= PATH_MAX)
575         continue;
576 #endif  /* PATH_MAX */
577       SAFE_ALLOCA (path, len + MDB_DIR_LEN + 1);
578       memcpy (path, dir_info->filename, len);
579       memcpy (path + len, MDB_DIR, MDB_DIR_LEN);
580       path[len + MDB_DIR_LEN] = '\0';
581       if (! (fp = fopen (path, "r")))
582         continue;
583       pl = mplist__from_file (fp, NULL);
584       fclose (fp);
585       if (! pl)
586         continue;
587       MPLIST_DO (p, pl)
588         {
589           MDatabase mdb;
590           MPlist *p1;
591           MText *mt;
592           int nbytes;
593           int with_wildcard = 0;
594
595           if (! MPLIST_PLIST_P (p))
596             continue;
597           p1 = MPLIST_PLIST (p);
598           if (! MPLIST_SYMBOL_P (p1))
599               continue;
600           for (i = 0, p1 = MPLIST_PLIST (p);
601                i < 4 && MPLIST_KEY (p1) == Msymbol;
602                i++, p1 = MPLIST_NEXT (p1))
603             with_wildcard |= ((mdb.tag[i] = MPLIST_SYMBOL (p1)) == Masterisk);
604           if (i == 0
605               || mdb.tag[0] == Masterisk
606               || ! MPLIST_MTEXT_P (p1))
607             continue;
608           for (; i < 4; i++)
609             mdb.tag[i] = Mnil;
610           mdb.loader = load_database;
611           mt = MPLIST_MTEXT (p1);
612           if (mt->format >= MTEXT_FORMAT_UTF_16LE)
613             mtext__adjust_format (mt, MTEXT_FORMAT_UTF_8);
614           nbytes = mtext_nbytes (mt);
615 #ifdef PATH_MAX
616           if (nbytes > PATH_MAX)
617             continue;
618 #endif  /* PATH_MAX */
619           if (with_wildcard
620               && mdb.tag[0] != Mchar_table
621               && mdb.tag[0] != Mcharset)
622             {
623               glob_t globbuf;
624               MPlist *load_key;
625
626               SAFE_ALLOCA (path, len + nbytes + 1);
627               memcpy (path, dir_info->filename, len);
628               memcpy (path + len, mt->data, nbytes);
629               path[len + nbytes] = '\0';
630
631               if (glob (path, GLOB_NOSORT | GLOB_NOCHECK, NULL, &globbuf) != 0)
632                 continue;
633               load_key = mplist ();
634               for (i = 0; i < globbuf.gl_pathc; i++)
635                 {
636                   if (! (fp = fopen (globbuf.gl_pathv[i], "r")))
637                     continue;
638                   p1 = mplist__from_file (fp, load_key);
639                   fclose (fp);
640                   if (! p1)
641                     continue;
642                   if (MPLIST_PLIST_P (p1))
643                     {
644                       MPlist *p0;
645                       MDatabase mdb2;
646
647                       for (j = 0, p0 = MPLIST_PLIST (p1);
648                            j < 4 && MPLIST_SYMBOL_P (p0);
649                            j++, p0 = MPLIST_NEXT (p0))
650                         mdb2.tag[j] = MPLIST_SYMBOL (p0);
651                       for (; j < 4; j++)
652                         mdb2.tag[j] = Mnil;
653                       for (j = 0; j < 4; j++)
654                         if (mdb.tag[j] == Masterisk
655                             ? mdb2.tag[j] == Mnil
656                             : (mdb.tag[j] != Mnil && mdb.tag[j] != mdb2.tag[j]))
657                           break;
658                       if (j == 4
659                           && ! find_database (mdb2.tag[0], mdb2.tag[1],
660                                               mdb2.tag[2], mdb2.tag[3]))
661                         {
662                           mdb2.loader = load_database;
663                           mdb2.extra_info = calloc (1, sizeof (MDatabaseInfo));
664                           if (! mdb2.extra_info)
665                             MEMORY_FULL (MERROR_DB);
666                           ((MDatabaseInfo*) mdb2.extra_info)->filename
667                             = strdup (globbuf.gl_pathv[i]);
668                           MLIST_APPEND1 (&mdb_list, mdbs, mdb2, MERROR_DB);
669                           if (mdb2.tag[0] == Mchar_table
670                               && mdb2.tag[2] != Mnil
671                               && (mdb2.tag[1] == Mstring
672                                   || mdb2.tag[1] == Mtext
673                                   || mdb2.tag[1] == Msymbol
674                                   || mdb2.tag[1] == Minteger
675                                   || mdb2.tag[1] == Mplist))
676                             mchar__define_prop (mdb2.tag[2], mdb2.tag[1],
677                                                 (mdb_list.mdbs
678                                                  + mdb_list.used - 1));
679                         }
680                     }
681                   M17N_OBJECT_UNREF (p1);
682                 }
683               M17N_OBJECT_UNREF (load_key);
684               globfree (&globbuf);
685             }
686           else
687             {
688               if (find_database (mdb.tag[0], mdb.tag[1],
689                                  mdb.tag[2], mdb.tag[3]))
690                 continue;
691               SAFE_ALLOCA (path, nbytes + 1);
692               memcpy (path, mt->data, nbytes);
693               path[nbytes] = '\0';
694               mdb.extra_info = calloc (1, sizeof (MDatabaseInfo));
695               if (! mdb.extra_info)
696                 MEMORY_FULL (MERROR_DB);
697               ((MDatabaseInfo*) mdb.extra_info)->filename = strdup (path);
698               MLIST_APPEND1 (&mdb_list, mdbs, mdb, MERROR_DB);
699               if (mdb.tag[0] == Mchar_table
700                   && mdb.tag[2] != Mnil
701                   && (mdb.tag[1] == Mstring
702                       || mdb.tag[1] == Mtext
703                       || mdb.tag[1] == Msymbol
704                       || mdb.tag[1] == Minteger
705                       || mdb.tag[1] == Mplist))
706                 mchar__define_prop (mdb.tag[2], mdb.tag[1], 
707                                     mdb_list.mdbs + mdb_list.used - 1);
708             }
709         }
710       M17N_OBJECT_UNREF (pl);
711     }
712
713   SAFE_FREE (path);
714 }
715
716 \f
717 /* Internal API */
718
719 /** List of database directories.  */ 
720 MPlist *mdatabase__dir_list;
721
722 int
723 mdatabase__init ()
724 {
725   MDatabaseInfo *dir_info;
726
727   Mchar_table = msymbol ("char-table");
728   Masterisk = msymbol ("*");
729
730   mdatabase__dir_list = mplist ();
731   /** The macro M17NDIR specifies a directory where the system-wide
732     MDB_DIR file exists.  */
733   if ((dir_info = get_dir_info (M17NDIR)))
734     mplist_set (mdatabase__dir_list, Mt, dir_info);
735
736   /* The variable mdatabase_dir specifies a directory where an
737      application program specific MDB_DIR file exists.  */
738   if ((dir_info = get_dir_info (mdatabase_dir)))
739     mplist_push (mdatabase__dir_list, Mt, dir_info);
740
741   /* The environment variable M17NDIR (if non-NULL) specifies a
742      directory where a user specific MDB_DIR file exists.  */
743   if ((dir_info = get_dir_info (getenv ("M17NDIR"))))
744     mplist_push (mdatabase__dir_list, Mt, dir_info);
745
746   MLIST_INIT1 (&mdb_list, mdbs, 256);
747   update_database_list ();
748
749   mdatabase__finder = ((void *(*) (MSymbol, MSymbol, MSymbol, MSymbol))
750                        mdatabase_find);
751   mdatabase__loader = (void *(*) (void *)) mdatabase_load;
752
753   return 0;
754 }
755
756 void
757 mdatabase__fini (void)
758 {
759   int i;
760   MPlist *plist; 
761
762   MPLIST_DO (plist, mdatabase__dir_list)
763     {
764       MDatabaseInfo *dir_info = MPLIST_VAL (plist);
765
766       free (dir_info->filename);
767       free (dir_info);
768     }
769   M17N_OBJECT_UNREF (mdatabase__dir_list);
770
771   for (i = 0; i < mdb_list.used; i++)
772     {
773       MDatabase *mdb = mdb_list.mdbs + i;
774
775       if (mdb->loader == load_database)
776         {
777           MDatabaseInfo *db_info = mdb->extra_info;
778
779           free (db_info->filename);
780           free (db_info);
781         }
782     }
783   MLIST_FREE1 (&mdb_list, mdbs);
784 }
785
786 MPlist *
787 mdatabase__load_for_keys (MDatabase *mdb, MPlist *keys)
788 {
789   int mdebug_mask = MDEBUG_DATABASE;
790   FILE *fp;
791   MPlist *plist;
792   char buf[256];
793
794   if (mdb->loader != load_database
795       || mdb->tag[0] == Mchar_table
796       || mdb->tag[0] == Mcharset)
797     MERROR (MERROR_DB, NULL);
798   MDEBUG_PRINT1 (" [DATABASE] loading <%s>.\n",
799                  gen_database_name (buf, mdb->tag));
800   fp = get_database_stream ((MDatabaseInfo *) mdb->extra_info);
801   if (! fp)
802     MERROR (MERROR_DB, NULL);
803   plist = mplist__from_file (fp, keys);
804   fclose (fp);
805   return plist;
806 }
807
808
809 /* Check if the database MDB should be reloaded or not.  */
810
811 int
812 mdatabase__check (MDatabase *mdb)
813 {
814   MDatabaseInfo *db_info = (MDatabaseInfo *) mdb->extra_info;
815   struct stat buf;
816
817   if (stat (db_info->filename, &buf) < 0)
818     return -1;
819   return (db_info->time >= buf.st_ctime
820           && db_info->time >= buf.st_mtime);
821 }
822
823 /*** @} */
824 #endif /* !FOR_DOXYGEN || DOXYGEN_INTERNAL_MODULE */
825
826 \f
827 /* External API */
828
829 /*** @addtogroup m17nDatabase */
830 /*** @{ */
831
832 /*=*/
833 /***en
834     @brief Directory for application specific data.
835
836     If an application program wants to provide a data specific to the
837     program or a data overriding what supplied by the m17n database,
838     it must set this variable to a name of directory that contains the
839     data files before it calls the macro M17N_INIT ().  The directory
840     may contain a file "mdb.dir" which contains a list of data
841     definitions in the format described in @ref mdbDir "mdbDir(5)".
842
843     The default value is NULL.  */
844 /***ja
845     @brief ¥¢¥×¥ê¥±¡¼¥·¥ç¥ó¸ÇÍ­¤Î¥Ç¡¼¥¿Íѥǥ£¥ì¥¯¥È¥ê.
846
847     ¥¢¥×¥ê¥±¡¼¥·¥ç¥ó¥×¥í¥°¥é¥à¤¬¡¢¤½¤Î¥×¥í¥°¥é¥à¸ÇÍ­¤Î¥Ç¡¼¥¿¤ä m17n 
848     ¥Ç¡¼¥¿¥Ù¡¼¥¹¤ò¾å½ñ¤­¤¹¤ë¥Ç¡¼¥¿¤òÄ󶡤¹¤ë¾ì¹ç¤Ë¤Ï¡¢¥Þ¥¯¥í M17N_INIT () 
849     ¤ò¸Æ¤ÖÁ°¤Ë¤³¤ÎÊÑ¿ô¤ò¥Ç¡¼¥¿¥Õ¥¡¥¤¥ë¤ò´Þ¤à¥Ç¥£¥ì¥¯¥È¥ê̾¤Ë¥»¥Ã¥È¤·¤Ê¤¯¤Æ¤Ï¤Ê¤é¤Ê¤¤¡£¥Ç¥£¥ì¥¯¥È¥ê¤Ë¤Ï
850     "mdb.dir" ¥Õ¥¡¥¤¥ë¤ò¤ª¤¯¤³¤È¤¬¤Ç¤­¤ë¡£¤½¤Î"mdb.dir"¥Õ¥¡¥¤¥ë¤Ë¤Ï¡¢ 
851     @ref mdbDir "mdbDir(5)" ¤ÇÀâÌÀ¤µ¤ì¤Æ¤¤¤ë¥Õ¥©¡¼¥Þ¥Ã¥È¤Ç¥Ç¡¼¥¿ÄêµÁ¤Î¥ê¥¹¥È¤òµ­½Ò¤¹¤ë¡£
852
853     ¥Ç¥Õ¥©¥ë¥È¤ÎÃͤϠNULL ¤Ç¤¢¤ë¡£  */
854
855 char *mdatabase_dir;
856
857 /*=*/
858 /***en
859     @brief Look for a data in the database.
860
861     The mdatabase_find () function searches the m17n database for a
862     data who has tags $TAG0 through $TAG3, and returns a pointer to
863     the data.  If such a data is not found, it returns @c NULL.  */
864
865 /***ja
866     @brief ¥Ç¡¼¥¿¥Ù¡¼¥¹Ãæ¤Î¥Ç¡¼¥¿¤òõ¤¹.
867
868     ´Ø¿ô mdatabase_find () ¤Ï¡¢ m17n ¸À¸ì¾ðÊó¥Ù¡¼¥¹Ãæ¤Ç $TAG0 ¤«¤é 
869     $TAG3 ¤Þ¤Ç¤Î¥¿¥°¤ò»ý¤Ä¥Ç¡¼¥¿¤òõ¤·¡¢¤½¤ì¤Ø¤Î¥Ý¥¤¥ó¥¿¤òÊÖ¤¹¡£¤½¤Î¤è¤¦¤Ê¥Ç¡¼¥¿¤¬¤Ê¤±¤ì¤Ð
870     @c NULL ¤òÊÖ¤¹¡£
871
872     @latexonly \IPAlabel{mdatabase_find} @endlatexonly  */
873
874 MDatabase *
875 mdatabase_find (MSymbol tag0, MSymbol tag1, MSymbol tag2, MSymbol tag3)
876 {
877   update_database_list ();
878   return find_database (tag0, tag1, tag2, tag3);
879 }
880
881 /*=*/
882 /***en
883     @brief Return a data list of the m17n database.
884
885     The mdatabase_list () function searches the m17n database for data
886     who have tags $TAG0 through $TAG3, and returns their list by a
887     plist.  The value #Mnil in $TAGn means a wild card that matches
888     any tag.  Each element of the plist has key #Mt and value a
889     pointer to type #MDatabase.  */
890 /***ja
891     @brief m17n ¥Ç¡¼¥¿¥Ù¡¼¥¹¤Î¥Ç¡¼¥¿¥ê¥¹¥È¤òÊÖ¤¹.
892
893     ´Ø¿ô mdatabase_list () ¤Ï m17n ¥Ç¡¼¥¿¥Ù¡¼¥¹Ã椫¤é $TAG0 ¤«¤é$TAG3 
894     ¤Þ¤Ç¤Î¥¿¥°¤ò»ý¤Ä¥Ç¡¼¥¿¤òõ¤·¡¢¤½¤Î¥ê¥¹¥È¤òplist ¤È¤·¤ÆÊÖ¤¹¡£ $TAGn ¤¬ #Mnil
895     ¤Ç¤¢¤Ã¤¿¾ì¹ç¤Ë¤Ï¡¢Ç¤°Õ¤Î¥¿¥°¤Ë¥Þ¥Ã¥Á¤¹¤ë¥ï¥¤¥ë¥É¥«¡¼¥É¤È¤·¤Æ¼è¤ê°·¤ï¤ì¤ë¡£ÊÖ¤µ¤ì¤ë
896     plist ¤Î³ÆÍ×ÁǤϥ­¡¼ ¤È¤·¤Æ #Mt ¤ò¡¢ÃͤȤ·¤Æ #MDatabase ·¿¤Ø¤Î¥Ý¥¤¥ó¥¿¤ò»ý¤Ä¡£  */
897
898 MPlist *
899 mdatabase_list (MSymbol tag0, MSymbol tag1, MSymbol tag2, MSymbol tag3)
900 {
901   int i;
902   MPlist *plist = NULL, *pl;
903
904   update_database_list ();
905
906   for (i = 0; i < mdb_list.used; i++)
907     {
908       MDatabase *mdb = mdb_list.mdbs + i;
909
910       if ((tag0 == Mnil || tag0 == mdb->tag[0])
911           && (tag1 == Mnil || tag1 == mdb->tag[1])
912           && (tag2 == Mnil || tag2 == mdb->tag[2])
913           && (tag3 == Mnil || tag3 == mdb->tag[3]))
914         {
915           if (! plist)
916             plist = pl = mplist ();
917           pl = mplist_add (pl, Mt, mdb);
918         }
919     }
920   return plist;
921 }
922
923 /*=*/
924 /***en
925     @brief Define a data of the m17n database.
926
927     The mdatabase_define () function defines a data that has tags
928     $TAG0 through $TAG3 and additional information $EXTRA_INFO.
929
930     $LOADER is a pointer to a function that loads the data from the
931     database.  This function is called from the mdatabase_load ()
932     function with the two arguments $TAGS and $EXTRA_INFO.  Here,
933     $TAGS is the array of $TAG0 through $TAG3.
934
935     If $LOADER is @c NULL, the default loader of the m17n library is
936     used.  In this case, $EXTRA_INFO must be a string specifying a
937     filename that contains the data.
938
939     @return
940     If the operation was successful, mdatabase_define () returns a
941     pointer to the defined data, which can be used as an argument to
942     mdatabase_load ().  Otherwise, it returns @c NULL.  */
943
944 /***ja
945     @brief m17n ¥Ç¡¼¥¿¥Ù¡¼¥¹¤Î¥Ç¡¼¥¿¤òÄêµÁ¤¹¤ë.
946
947     ´Ø¿ô mdatabase_define () ¤Ï $TAG0 ¤«¤é $TAG3 ¤Þ¤Ç¤Î¥¿¥°¤ª¤è¤ÓÉղþðÊó 
948     $EXTRA_INFO ¤ò»ý¤Ä¥Ç¡¼¥¿¤òÄêµÁ¤¹¤ë¡£
949
950     $LOADER ¤Ï¤½¤Î¥Ç¡¼¥¿¤Î¥í¡¼¥É¤ËÍѤ¤¤é¤ì¤ë´Ø¿ô¤Ø¤Î¥Ý¥¤¥ó¥¿¤Ç¤¢¤ë¡£¤³¤Î´Ø¿ô¤Ï
951     mdatabase_load () ¤«¤é $TAGS ¤È $EXTRA_INFO ¤È¤¤¤¦Æó¤Ä¤Î°ú¿ôÉÕ¤­¤Ç¸Æ¤Ó½Ð¤µ¤ì¤ë¡£¤³¤³¤Ç 
952     $TAGS ¤Ï $TAG0 ¤«¤é $TAG3 ¤Þ¤Ç¤ÎÇÛÎó¤Ç¤¢¤ë¡£
953
954     ¤â¤· $LOADER ¤¬ @c NULL ¤Ê¤é¡¢m17n ¥é¥¤¥Ö¥é¥êɸ½à¤Î¥í¡¼¥À¤¬»È¤ï¤ì¤ë¡£¤³¤Î¾ì¹ç¤Ë¤Ï
955     $EXTRA_INFO ¤Ï¥Ç¡¼¥¿¤ò´Þ¤à¥Õ¥¡¥¤¥ë̾¤Ç¤Ê¤¯¤Æ¤Ï¤Ê¤é¤Ê¤¤¡£
956
957     @return
958     ½èÍý¤ËÀ®¸ù¤¹¤ì¤Ð mdatabase_define () 
959     ¤ÏÄêµÁ¤µ¤ì¤¿¥Ç¡¼¥¿¥Ù¡¼¥¹¤Ø¤Î¥Ý¥¤¥ó¥¿¤òÊÖ¤¹¡£¤³¤Î¥Ý¥¤¥ó¥¿¤Ï´Ø¿ô mdatabase_load () 
960     ¤Î°ú¿ô¤È¤·¤ÆÍѤ¤¤ë¤³¤È¤¬¤Ç¤­¤ë¡£¤½¤¦¤Ç¤Ê¤±¤ì¤Ð @c NULL ¤òÊÖ¤¹¡£
961
962     @latexonly \IPAlabel{mdatabase_define} @endlatexonly  */
963
964 /***
965     @seealso
966     mdatabase_load (),  mdatabase_define ()  */
967
968 MDatabase *
969 mdatabase_define (MSymbol tag0, MSymbol tag1, MSymbol tag2, MSymbol tag3,
970                   void *(*loader) (MSymbol *, void *),
971                   void *extra_info)
972 {
973   MDatabase *mdb;
974
975   mdb = mdatabase_find (tag0, tag1, tag2, tag3);
976   if (! mdb)
977     {
978       MDatabase template;
979
980       template.tag[0] = tag0, template.tag[1] = tag1;
981       template.tag[2] = tag2, template.tag[3] = tag3;
982       template.extra_info = NULL;
983       MLIST_APPEND1 (&mdb_list, mdbs, template, MERROR_DB);
984       mdb = mdb_list.mdbs + (mdb_list.used - 1);
985     }
986   mdb->loader = loader ? loader : load_database;
987   if (mdb->loader == load_database)
988     {
989       MDatabaseInfo *db_info = mdb->extra_info;
990
991       if (db_info)
992         free (db_info->filename);
993       else
994         db_info = mdb->extra_info = malloc (sizeof (MDatabaseInfo));
995       db_info->filename = strdup ((char *) extra_info);
996       db_info->time = 0;
997     }
998   else
999     mdb->extra_info = extra_info;
1000   return (&(mdb_list.mdbs[mdb_list.used - 1]));
1001 }
1002
1003 /*=*/
1004 /***en
1005     @brief Load a data from the database.
1006
1007     The mdatabase_load () function loads a data specified in $MDB and
1008     returns the contents.  The type of contents depends on the type of
1009     the data.
1010
1011     If the data is of the @e plist @e type, this function returns a
1012     pointer to @e plist.
1013
1014     If the database is of the @e chartable @e type, it returns a
1015     chartable.  The default value of the chartable is set according to
1016     the second tag of the data as below:
1017
1018     @li If the tag is #Msymbol, the default value is #Mnil.
1019     @li If the tag is #Minteger, the default value is -1.
1020     @li Otherwise, the default value is @c NULL.
1021
1022     If the data is of the @e charset @e type, it returns a plist of length 2
1023     (keys are both #Mt).  The value of the first element is an array
1024     of integers that maps code points to the corresponding character
1025     codes.  The value of the second element is a chartable of integers
1026     that does the reverse mapping.  The charset must be defined in
1027     advance.  */
1028
1029
1030 /***ja
1031     @brief ¥Ç¡¼¥¿¥Ù¡¼¥¹¤«¤é¥Ç¡¼¥¿¤ò¥í¡¼¥É¤¹¤ë.
1032
1033     ´Ø¿ô mdatabase_load () ¤Ï $MDB 
1034     ¤¬»Ø¤¹¥Ç¡¼¥¿¤ò¥í¡¼¥É¤·¡¢¤½¤ÎÃæ¿È¤òÊÖ¤¹¡£ÊÖ¤µ¤ì¤ë¤â¤Î¤Ï¥Ç¡¼¥¿¤Î¥¿¥¤¥×¤Ë¤è¤Ã¤Æ°Û¤Ê¤ë¡£
1035
1036     ¥Ç¡¼¥¿¤¬ @e plist¥¿¥¤¥× ¤Ê¤é¤Ð¡¢ @e plist ¤Ø¤Î¥Ý¥¤¥ó¥¿¤òÊÖ¤¹¡£
1037
1038     ¥Ç¡¼¥¿¤¬ @e chartable¥¿¥¤¥× ¤Ê¤é¤Ðʸ»ú¥Æ¡¼¥Ö¥ë¤òÊÖ¤¹¡£
1039     Ê¸»ú¥Æ¡¼¥Ö¥ë¤Î¥Ç¥Õ¥©¥ë¥ÈÃͤϡ¢¥Ç¡¼¥¿¤ÎÂè2¥¿¥°¤Ë¤è¤Ã¤Æ°Ê²¼¤Î¤è¤¦¤Ë·è¤Þ¤ë¡£
1040
1041     @li ¥¿¥°¤¬ #Msymbol ¤Ê¤é¡¢¥Ç¥Õ¥©¥ë¥ÈÃͤϠ#Mnil
1042     @li ¥¿¥°¤¬ #Minteger ¤Ê¤é¡¢¥Ç¥Õ¥©¥ë¥ÈÃͤϠ-1
1043     @li ¤½¤ì°Ê³°¤Ê¤é¡¢¥Ç¥Õ¥©¥ë¥ÈÃͤϠ@c NULL
1044
1045     ¥Ç¡¼¥¿¤¬ @e charset¥¿¥¤¥× ¤Ê¤é¤ÐŤµ 2 ¤Î plist ¤òÊÖ¤¹¡Ê¥­¡¼¤Ï¶¦¤Ë#Mt ¡Ë¡£
1046     ºÇ½é¤ÎÍ×ÁǤÎÃͤϥ³¡¼¥É¥Ý¥¤¥ó¥È¤òÂбþ¤¹¤ëʸ»ú¥³¡¼¥É¤Ë¥Þ¥Ã¥×¤¹¤ëÀ°¿ô¤ÎÇÛÎó¤Ç¤¢¤ë¡£
1047     £²ÈÖÌܤÎÍ×ÁǤÎÃͤϵդΥޥåפò¤¹¤ëʸ»ú¥Æ¡¼¥Ö¥ë¤Ç¤¢¤ë¡£
1048     ¤³¤Îʸ»ú¥»¥Ã¥È¤Ïͽ¤áÄêµÁ¤µ¤ì¤Æ¤¤¤Ê¤±¤ì¤Ð¤Ê¤é¤Ê¤¤¡£
1049
1050     @latexonly \IPAlabel{mdatabase_load} @endlatexonly
1051   */
1052
1053 /***
1054     @seealso
1055     mdatabase_load (),  mdatabase_define ()  */
1056
1057 void *
1058 mdatabase_load (MDatabase *mdb)
1059 {
1060   int mdebug_mask = MDEBUG_DATABASE;
1061   char buf[256];
1062
1063   MDEBUG_PRINT1 (" [DATABASE] loading <%s>.\n",
1064                  gen_database_name (buf, mdb->tag));
1065   return (*mdb->loader) (mdb->tag, mdb->extra_info);
1066 }
1067
1068 /*=*/
1069 /***en
1070     @brief Get tags of a data.
1071
1072     The mdatabase_tag () function returns an array of tags (symbols)
1073     that identify the data in $MDB.  The length of the array is
1074     four.  */
1075
1076 /***ja
1077     @brief ¥Ç¡¼¥¿¤Î¥¿¥°¤òÆÀ¤ë.
1078
1079     ´Ø¿ô mdatabase_tag () ¤Ï¡¢¥Ç¡¼¥¿ $MDB ¤Î¥¿¥°¡Ê¥·¥ó¥Ü¥ë¡Ë¤ÎÇÛÎó¤òÊÖ¤¹¡£ÇÛÎó¤ÎŤµ¤Ï
1080     4 ¤Ç¤¢¤ë¡£
1081
1082     @latexonly \IPAlabel{mdatabase_tag} @endlatexonly  */
1083
1084 MSymbol *
1085 mdatabase_tag (MDatabase *mdb)
1086 {
1087   return mdb->tag;
1088 }
1089
1090 /*** @} */
1091
1092 /*
1093   Local Variables:
1094   coding: euc-japan
1095   End:
1096 */