*** empty log message ***
[m17n/m17n-lib-cs.git] / MDatabase.cs
1 using System;
2 using System.Collections;
3 using System.Collections.Generic;
4 using System.IO;
5 using System.Xml;
6
7 using M17N;
8 using M17N.Core;
9
10 namespace M17N.Core
11 {
12   internal class MGlob
13   {
14     static readonly char sep = Path.DirectorySeparatorChar;
15
16     public static void FileList (ref List<FileInfo> files,
17                                  string dir, string pattern)
18     {
19       int len = pattern.Length;
20
21       if (Path.IsPathRooted (pattern))
22         {
23           int headsep = 0;
24           int i;
25
26           for (i = 1; i < len && (pattern[i] != '*' && pattern[i] != '?'); i++)
27             if (pattern[i] == sep)
28               headsep = i;
29           if (i == len)
30             {
31               if (File.Exists (pattern))
32                 files.Add (new FileInfo (pattern));
33               return;
34             }
35           dir = pattern.Substring (0, headsep);
36           pattern = pattern.Substring (headsep + 1);
37         }
38       else
39         {
40           if (dir == null)
41             dir = Directory.GetCurrentDirectory ();
42         }
43       if (Directory.Exists (dir))
44         list (ref files, new DirectoryInfo (dir), pattern);
45     }
46
47     private static void list (ref List<FileInfo> files,
48                               DirectoryInfo dirinfo, string pattern)
49     {
50       int len = pattern.Length;
51       int i;
52     
53       M17n.DebugPrint ("Listing {0} in {1} ...", pattern, dirinfo);
54       for (i = 0; i < len && pattern[i] != sep; i++);
55       try {
56         if (i == len)
57           {
58             FileInfo[] listing = dirinfo.GetFiles (pattern);
59             i = listing.Length;
60             foreach (FileInfo elt in listing)
61               files.Add (elt);
62           }
63         else
64           {
65             string tail = pattern.Substring (i + 1);
66             pattern = pattern.Substring (0, i);
67             DirectoryInfo[] listing = dirinfo.GetDirectories (pattern);
68
69             i = listing.Length;
70             foreach (DirectoryInfo elt in listing)
71               list (ref files, elt, tail);
72           }
73       } catch {
74       }
75       M17n.DebugPrint (" found {0} files\n", i);
76     }
77   }
78
79   internal class MDatabaseDir
80   {
81     private const string ListFileName = "mdb.dir";
82
83     public string Dirname;
84     public DirectoryInfo DirInfo;
85     public DateTime DirChangeTime;
86     public FileInfo ListInfo;
87     public DateTime ListChangeTime;
88
89     public MDatabaseDir (string dirname)
90     {
91       Dirname = dirname;
92       DirChangeTime = ListChangeTime = DateTime.Now;
93     }
94
95     public void Refresh ()
96     {
97       if (DirInfo != null)
98         {
99           if (Dirname != null && Directory.Exists (Dirname))
100             {
101               DirInfo.Refresh ();
102               if (DirChangeTime < DirInfo.LastWriteTime)
103                 DirChangeTime = DirInfo.LastWriteTime;
104             }
105           else
106             {
107               DirInfo = null;
108               DirChangeTime = DateTime.Now;
109             }
110         }
111       else
112         {
113           if (Dirname != null && Directory.Exists (Dirname))
114             {
115               DirInfo = new DirectoryInfo (Dirname);
116               DirChangeTime = DateTime.Now;
117             }
118         }
119       if (DirInfo == null)
120         {
121           if (ListInfo != null)
122             {
123               ListInfo = null;
124               ListChangeTime = DateTime.Now;
125             }
126         }
127       else
128         {
129           if (ListInfo != null)
130             {
131               ListInfo.Refresh ();
132               if (ListChangeTime < ListInfo.LastWriteTime)
133                 ListChangeTime = ListInfo.LastWriteTime;
134             }
135           else
136             {
137               try {
138                 ListInfo = DirInfo.GetFiles (ListFileName)[0];
139                 ListChangeTime = DateTime.Now;
140               } catch {
141               }
142             }
143         }
144     }
145   }
146
147   public partial class MDatabase : IComparable<MDatabase>
148   {
149     /// Identifier of a MDatabase.
150     public struct Tag : IEquatable<Tag>
151     {
152       private MSymbol[] Tags;
153
154       public Tag (MSymbol tag0)
155         {
156           Tags = new MSymbol[4];
157           Tags[0] = tag0; Tags[1] = Tags[2] = Tags[3] = MSymbol.nil;
158         }
159
160       public Tag (MSymbol tag0, MSymbol tag1)
161         {
162           Tags = new MSymbol[4];
163           Tags[0] = tag0; Tags[1] = tag1; Tags[2] = Tags[3] = MSymbol.nil;
164         }
165
166       public Tag (MSymbol tag0, MSymbol tag1, MSymbol tag2)
167         {
168           Tags = new MSymbol[4];
169           Tags[0] = tag0; Tags[1] = tag1; Tags[2] = tag2; Tags[3] = MSymbol.nil;
170         }
171
172       public Tag (MSymbol tag0, MSymbol tag1, MSymbol tag2, MSymbol tag3)
173         {
174           Tags = new MSymbol[4];
175           Tags[0] = tag0; Tags[1] = tag1; Tags[2] = tag2; Tags[3] = tag3;
176         }
177
178       public Tag (ref MPlist plist)
179         {
180           Tags = new MSymbol[4];
181
182           for (int i = 0; i < 4; i++)
183             {
184               if (plist.IsSymbol)
185                 {
186                   Tags[i] = plist.Symbol;
187                   plist = plist.Next;
188                 }
189               else
190                 Tags[i] = MSymbol.nil;
191             }
192         }
193
194       public bool Equals (Tag tag)
195       {
196         for (int i = 0; i < 4; i++)
197           if (tag[i] != Tags[i])
198             return false;
199         return true;
200       }
201
202       public override int GetHashCode ()
203       {
204         return (Tags[0].GetHashCode () ^ Tags[1].GetHashCode ()
205                 ^ Tags[2].GetHashCode () ^ Tags[3].GetHashCode ());
206       }
207
208       public override string ToString ()
209       {
210         return ("<"
211                 + Tags[0] + "," + Tags[1] + "," + Tags[2] + "," + Tags[3]
212                 + ">");
213       }
214
215       public MSymbol this[int i]
216       {
217         set { Tags[i] = value; }
218         get { return Tags[i]; }
219       }
220
221       public bool Match (Tag tag)
222       {
223         for (int i = 0; i < 4; i++)
224           {
225             if (tag[i] == Mwildcard || Tags[i] == Mwildcard)
226               return true;
227             if (tag[i] != Tags[i])
228               return false;
229           }
230         return true;
231       }
232
233       public bool HasWildcard {
234         get {
235           for (int i = 0; i < 4; i++)
236             if (Tags[i] == Mwildcard)
237               return true;
238           return false;
239         }
240       }
241     }
242
243     public delegate object Loader (Tag tag, object extra_info);
244
245     internal class MDatabaseInfo
246     {
247       // These come from the declartion plist of the database.
248       internal string Description;
249       internal string Filename;
250       internal FileInfo Validater;
251       internal int Version;
252       internal MSymbol Format;
253       internal MSymbol Schema;
254       internal string SchemaFile;
255       internal MPlist Props;
256
257       public MDatabaseInfo ()
258       {
259         Format = Schema = MSymbol.nil;
260       }
261
262       private static int parse_version (MPlist plist)
263       {
264         string[] str;
265         int major, minor, release;
266
267         if (! plist.IsMText)
268           return 0xFFFFFF;
269         str = plist.Text.ToString ().Split ('.');
270         if (str.Length != 3)
271           return 0xFFFFFF;
272         try { major = int.Parse (str[0]); } catch { return 0xFFFFFF; }
273         try { minor = int.Parse (str[1]); } catch { return 0xFFFFFF; }
274         try { release = int.Parse (str[2]); } catch { return 0xFFFFFF; }
275         return ((major << 16) | (minor << 8) | release);
276       }
277
278       public MDatabaseInfo (MPlist plist)
279       {
280         Format = MSymbol.nil;
281         if (plist.IsMText)
282           {
283             Format = MSymbol.plist;
284             Filename = plist.Text.ToString ();
285             plist = plist.Next;
286           }
287         else if (plist.IsPlist)
288           {
289             MPlist p = plist.Plist;
290
291             if (p.IsMText)
292               {
293                 Filename = p.Text.ToString ();
294                 p = p.Next;
295               }
296             if (p.IsSymbol)
297               {
298                 Format = p.Symbol;
299                 p = p.Next;
300               }
301             if (p.IsSymbol)
302               {
303                 Schema = p.Symbol;
304                 p = p.Next;
305               }
306             if (p.IsMText)
307               SchemaFile = p.Text.ToString ();
308             plist = plist.Next;
309           }
310
311         Version = 0;
312         Props = new MPlist ();
313         foreach (MPlist pl in plist)
314           {
315             if (pl.IsPlist)
316               {
317                 MPlist p = pl.Plist;
318               
319                 if (p.IsSymbol && p.Symbol == Mversion)
320                   Version = parse_version (p.Next);
321                 else
322                   Props.Put (pl.Key, pl.Val);
323               }
324             else if (pl.IsSymbol)
325               {
326                 MPlist p = new MPlist ();
327                 p.Add (MSymbol.symbol, pl.Symbol);
328                 p.Add (MSymbol.symbol, MSymbol.t);
329                 Props.Put (MSymbol.plist, p);
330               }
331           }
332       }
333
334       public void Merge (MDatabaseInfo src)
335       {
336         if (Validater == null)
337           Validater = src.Validater;
338         if (Version == 0)
339           Version = src.Version;
340         if (Format == MSymbol.nil)
341           Format = src.Format;
342         if (Schema == MSymbol.nil)
343           Schema = src.Schema;
344         if (SchemaFile == null)
345           SchemaFile = src.SchemaFile;
346         foreach (MPlist p in src.Props)
347           if (Props.Assq (p.Plist.Symbol) == null)
348             Props.Push (p.Key, p.Val);
349       }
350
351       public override string ToString ()
352       {
353         string str = ("#<Info " + Format + " \"" + Filename + "\"");
354         if (Schema != MSymbol.nil)
355           str += " " + Schema;
356         return str + ">";
357       }
358     }
359
360     // Dictionaries for normal databases.
361     private static Dictionary<MDatabase.Tag, List<MDatabase>> ndict
362       = new Dictionary<MDatabase.Tag, List<MDatabase>> ();
363
364     // Dictionaries for databases of DBType WILDCARD
365     private static Dictionary<MDatabase.Tag, List<MDatabase>> wdict
366       = new Dictionary<MDatabase.Tag, List<MDatabase>> ();
367
368     private static MDatabaseDir[] DBDirs = new MDatabaseDir[4];
369
370     private static DateTime LastUpdateTime = new DateTime (0);
371
372     private static readonly MSymbol Mversion = "version";
373     private static readonly MSymbol Mwildcard = "*";
374     private static readonly MSymbol Mchar_table = "char-table";
375     private static readonly MSymbol Mcharset = "charset";
376     private static readonly MSymbol Mxml = "xml";
377
378     private static TimeSpan CheckInterval = new TimeSpan (50000000);
379
380     /// Type of database
381     private enum MDBType
382       {
383         /// The database was defined automatically from one of mdb.dir
384         /// files with no wildcard tag.
385         AUTO,
386         /// The database was defined automatically from one of mdb.dir
387         /// files with a wildcard tag to define multiple databases
388         /// of the same kind.
389         MULTIPLE,
390         /// The database was defined explicitely by MDatabase.Define
391         /// to use the normal loader.
392         EXPLICIT,
393         /// The database was defined explicitely by MDatabase.Define
394         /// to use a special loader.
395         UNKNOWN,
396         /// The database is for defining multiple databases of the
397         /// same kind with a wildcard tag.
398         WILDCARD,
399       };
400
401     /// Status of database
402     private enum MDBStatus
403       {
404         // The database file has not yet been decided, or is not yet
405         // expanded if DBType is WILDCARD.
406         NOT_READY,
407         // The database file was decided, or has been expanded if
408         // DBType is WILDCARD.
409         READY,
410         // The database is disabled.  It means that the database file
411         // is not readable, the version is not supported by the
412         // current system, or the validation was failed.
413         DISABLED,
414         // The database is deleted by the modificaiton of mdb.dir, or
415         // is overwritten by a new explicit definition.
416         INVALID,
417       };
418
419     public Tag tag;
420     public NameTable name_table = new NameTable ();
421     private Loader loader;
422     private object ExtraInfo;
423     // Directory of the database file.
424     // -1:unknown, 0:absolute, 1:DBDirs[1], 2:DBDirs[2], 3:DBDirs[3]
425     private int DirIndex;
426     // Directory of the mdb.dir defining the database file.
427     // 0: EXPLICIT or UNKNOWN, 1:DBDirs[1], 2:DBDirs[2], 3:DBDirs[3]
428     private int ListIndex;
429     private MDBType DBType;
430     private MDBStatus DBStatus;
431     internal MDatabaseInfo Info;
432     // File in which the database contents is stored.  This is null
433     // when DBStatus is NOT_READY.
434     internal FileInfo FileInfo;
435     // When the database file is loaded last.
436     internal DateTime LastLoaded = DateTime.Now;
437
438     public enum LoadStatus
439     {
440       None,
441       InvalidLoadMethod,
442       NotAvailable,
443       NotReadable,
444       InvalidContents,
445     };
446
447     public LoadStatus LastLoadStatus = LoadStatus.None;
448
449     static MDatabase ()
450     {
451       string share_dir = (Environment.GetFolderPath
452                           (Environment.SpecialFolder.CommonApplicationData));
453       string usr_dir = (Environment.GetFolderPath
454                         (Environment.SpecialFolder.ApplicationData));
455
456       try {
457         string dir = Environment.GetEnvironmentVariable ("M17NDIR");
458         DBDirs[1] = new MDatabaseDir (dir);
459       } catch {
460         try {
461           DBDirs[1] = new MDatabaseDir (Path.Combine (usr_dir, ".m17n.d"));
462         } catch (ArgumentException) {
463           DBDirs[1] = new MDatabaseDir (Path.Combine (usr_dir, "_m17n_d"));
464         }
465       }
466       DBDirs[2] = new MDatabaseDir (null);
467       DBDirs[3] = new MDatabaseDir (Path.Combine (share_dir, "m17n"));
468       update_all (true);
469     }
470
471     public static string ApplicationDir
472     {
473       get { return (DBDirs[1].Dirname); }
474       set { DBDirs[2] = new MDatabaseDir (value); update_all (true); }
475     }
476
477     // Update all listing and directories.  Return true iff some are
478     // really updated.
479     private static bool update_all (bool force)
480     {
481       if (! force && DateTime.Now - LastUpdateTime < CheckInterval)
482         return false;
483
484       bool updated = false;
485       for (int i = 1; i < 4; i++)
486         if (DBDirs[i].Dirname != null)
487           {
488             DBDirs[i].Refresh ();
489             if (LastUpdateTime < DBDirs[i].ListChangeTime)
490               {
491                 update_list (i);
492                 updated = true;
493               }
494             if (LastUpdateTime < DBDirs[i].DirChangeTime)
495               {
496                 update_dir (i);
497                 updated = true;
498               }
499           }
500       if (updated)
501         LastUpdateTime = DateTime.Now;
502       return updated;
503     }
504
505     public static void Dump ()
506     {
507       update_all (false);
508       Console.WriteLine ("[DBDirs]");
509       for (int i = 1; i < 4; i++)
510         if (DBDirs[i].Dirname != null)
511           {
512             if (DBDirs[i].DirInfo != null)
513               {
514                 Console.Write ("{0}:{1}", i, DBDirs[i].DirInfo.FullName);
515                 if (DBDirs[i].ListInfo != null)
516                   Console.WriteLine (" {0}", DBDirs[i].ListInfo);
517                 else
518                   Console.WriteLine (" .. no mdb.dir");
519               }
520             else
521               Console.WriteLine ("{0}:{1} .. not exist", i, DBDirs[i].Dirname);
522           }
523
524       Console.WriteLine ("[WDICT]");
525       foreach (KeyValuePair<Tag, List<MDatabase>> kv in wdict)
526         foreach (MDatabase mdb in kv.Value)
527           Console.WriteLine (mdb);
528
529       Console.WriteLine ("[NDICT]");
530       foreach (KeyValuePair<Tag, List<MDatabase>> kv in ndict)
531         foreach (MDatabase mdb in kv.Value)
532           Console.WriteLine (mdb);
533     }
534
535     private static void register (MDatabase mdb)
536     {
537       Dictionary<MDatabase.Tag, List<MDatabase>> dict
538         = mdb.DBType == MDBType.WILDCARD ? wdict : ndict;
539       List<MDatabase> mdbs;
540
541       if (dict.TryGetValue (mdb.tag, out mdbs))
542         {
543           for (int i = 0; i < mdbs.Count; i++)
544             if (mdbs[i].ListIndex == mdb.ListIndex)
545               {
546                 mdbs[i].DBStatus = MDBStatus.INVALID;
547                 mdbs[i] = mdb;
548                 return;
549               }
550           mdbs.Add (mdb);
551         }
552       else
553         {
554           mdbs = new List<MDatabase> (1);
555           mdbs.Add (mdb);
556           dict.Add (mdb.tag, mdbs);
557         }
558     }
559
560     private static void register (int list_idx, int dir_idx,
561                                   Tag tag, MDatabaseInfo info)
562     {
563       Dictionary<MDatabase.Tag, List<MDatabase>> dict
564         = tag.HasWildcard ? wdict : ndict;
565       List<MDatabase> mdbs;
566       MDatabase mdb;
567
568       if (dict.TryGetValue (tag, out mdbs))
569         for (int i = 0; i < mdbs.Count; i++)
570           {
571             mdb = mdbs[i];
572             if (mdb.ListIndex == list_idx && mdb.DirIndex >= dir_idx)
573               {
574                 mdb.DirIndex = dir_idx;
575                 if (dict == wdict)
576                   {
577                     mdb.DBType = MDBType.WILDCARD;
578                     mdb.DBStatus = MDBStatus.NOT_READY;
579                   }
580                 else if (dir_idx == -1)
581                   {
582                     mdb.DBType = MDBType.AUTO;
583                     mdb.DBStatus = MDBStatus.NOT_READY;
584                   }
585                 else
586                   {
587                     mdb.DBType = MDBType.MULTIPLE;
588                     mdb.DBStatus = MDBStatus.READY;
589                   }
590                 mdb.Info = info;
591                 if (mdb.DBStatus == MDBStatus.INVALID)
592                   M17n.DebugPrint ("registering: {0}\n", mdb);
593                 else
594                   M17n.DebugPrint ("updating: {0}\n", mdb);
595                 return;
596               }
597           }
598       else
599         {
600           mdbs = new List<MDatabase> (1);
601           dict.Add (tag, mdbs);
602         }
603       mdb = new MDatabase (list_idx, dir_idx, tag, info);
604       M17n.DebugPrint ("registering: {0}\n", mdb);
605       mdbs.Add (mdb);
606     }
607
608     public MDatabase (Tag tag, Loader loader, object extra_info)
609     {
610       this.tag = tag;
611       this.loader = loader;
612       DBType = MDBType.UNKNOWN;
613       DBStatus = MDBStatus.READY;
614       DirIndex = 0;
615       ListIndex = 0;
616       ExtraInfo = extra_info;
617       register (this);
618     }
619
620     public MDatabase (Tag tag, string filename)
621     {
622       this.tag = tag;
623       DBType = MDBType.EXPLICIT;
624       if (Path.IsPathRooted (filename))
625         {
626           DirIndex = 0;
627           if (File.Exists (filename))
628             DBStatus = MDBStatus.READY;
629           else
630             DBStatus = MDBStatus.INVALID;
631         }
632       else
633         {
634           DirIndex = -1;
635           DBStatus = MDBStatus.NOT_READY;
636         }
637       ListIndex = 0;
638       Info = new MDatabaseInfo ();
639       Info.Filename = filename;
640       register (this);
641     }
642
643     private MDatabase (int list_idx, int dir_idx, Tag tag, MDatabaseInfo info)
644     {
645       this.tag = tag;
646       this.Info = info;
647       DBType = this.tag.HasWildcard ? MDBType.WILDCARD : MDBType.AUTO;
648       if (tag[0] == Mchar_table || tag[0] == Mcharset)
649         Info.Format = tag[0];
650       ListIndex = list_idx;
651       DirIndex = dir_idx;
652       if (Path.IsPathRooted (Info.Filename))
653         DBStatus = MDBStatus.READY;
654       else
655         DBStatus = MDBStatus.NOT_READY;
656       if (Info.Format == Mchar_table)
657         MCharProp.Define (tag[2], this);
658     }
659
660     public override String ToString () {
661       string str = ("#<MDataBase (" + ListIndex + "," + DirIndex + ") "
662                     + tag + " " + DBType + " " + DBStatus);
663
664       if (DBType != MDBType.EXPLICIT && DBType != MDBType.UNKNOWN)
665         str += " " + Info;
666       return str + ">";
667     }
668
669     // Update (or disable) databases defined by "mdb.dir" in
670     // DBDirs[list_idx].
671     private static void update_list (int list_idx)
672     {
673       M17n.DebugPrint ("Updating list: {0}\n", list_idx);
674       // At first disable all target databases.
675       foreach (KeyValuePair<Tag, List<MDatabase>> kv in wdict)
676         foreach (MDatabase mdb in kv.Value)
677           if (mdb.ListIndex == list_idx)
678             {
679               M17n.DebugPrint ("deleting: {0}\n", mdb);
680               mdb.DBStatus = MDBStatus.INVALID;
681               break;
682             }
683       foreach (KeyValuePair<Tag, List<MDatabase>> kv in ndict)
684         foreach (MDatabase mdb in kv.Value)
685           if (mdb.ListIndex == list_idx)
686             {
687               M17n.DebugPrint ("deleting: {0}\n", mdb);
688               mdb.DBStatus = MDBStatus.INVALID;
689               break;
690             }
691
692       FileInfo dblist = DBDirs[list_idx].ListInfo;
693       if (dblist == null)
694         return;
695
696       MPlist plist = null;
697       using (FileStream stream = File.OpenRead (dblist.FullName))
698         {
699           plist = new MPlist (stream);
700         }
701       if (plist == null)
702         return;
703       foreach (MPlist pl in plist)
704         if (pl.IsPlist)
705           {
706             try
707               {
708                 MPlist p = pl.Plist;
709                 Tag tag = new Tag (ref p);
710                 MDatabaseInfo info = new MDatabaseInfo (p);
711                 int dir_idx = Path.IsPathRooted (info.Filename) ? 0 : -1;
712                 register (list_idx, dir_idx, tag, info);
713               }
714             catch (Exception e)
715               {
716                 Console.WriteLine (e.Message + ": " + pl.Plist);
717               }
718           }
719     }
720
721     // Update (or disable) databases in DBDirs[dir_idx].
722     private static void update_dir (int dir_idx)
723     {
724       M17n.DebugPrint ("Updating dir: {0}\n", dir_idx);
725       // Reset all databases in DBDirs[dir_idx].
726       foreach (KeyValuePair<Tag, List<MDatabase>> kv in ndict)
727         foreach (MDatabase mdb in kv.Value)
728           if (mdb.DirIndex >= dir_idx)
729             {
730               M17n.DebugPrint ("disabling: {0}\n", mdb);
731               mdb.DBStatus = MDBStatus.NOT_READY;
732               mdb.DirIndex = -1;
733             }
734       // Re-expand all WILDCARD databases in DBDirs[dir_idx].
735       if (DBDirs[dir_idx].DirInfo != null)
736         foreach (KeyValuePair<Tag, List<MDatabase>> kv in wdict)
737           foreach (MDatabase mdb in kv.Value)
738             if (mdb.DBStatus == MDBStatus.READY)
739               {
740                 M17n.DebugPrint ("re-expanding: {0}\n", mdb);
741                 register_files (DBDirs[dir_idx].Dirname, dir_idx, mdb);
742               }
743     }
744
745     private static bool parse_plist_header (FileInfo fileinfo, MDatabase mdb,
746                                             out Tag tag, out MDatabaseInfo info)
747     {
748       MPlist plist = null;
749
750       tag = new Tag (MSymbol.nil);
751       info = null;
752       using (FileStream stream = fileinfo.OpenRead ())
753         {
754           try { plist = new MPlist (stream, 1); } catch { }
755         }
756       if (plist == null || ! plist.IsPlist)
757         return false;
758       plist = plist.Plist;
759       tag = new Tag (ref plist);
760       if (tag.HasWildcard || ! tag.Match (mdb.tag))
761         return false;
762       info = new MDatabaseInfo (plist);
763       return true;
764     }
765
766     private static bool parse_xml_header (FileInfo fileinfo, MDatabase mdb,
767                                           out Tag tag, out MDatabaseInfo info)
768     {
769       tag = new Tag (MSymbol.nil);
770       info = null;
771       using (FileStream stream = fileinfo.OpenRead ())
772         {
773           try {
774             MPlist plist = new MPlist ();
775             XmlTextReader reader = new XmlTextReader (stream);
776             
777             reader.WhitespaceHandling = WhitespaceHandling.None;
778             do {
779               reader.Read ();
780             } while (reader.NodeType != XmlNodeType.Element);
781             plist.Add (MSymbol.symbol, (MSymbol) reader.Name);
782             reader.Read ();
783             if (reader.NodeType == XmlNodeType.Element && reader.Name == "tags")
784               {
785                 reader.Read ();
786                 while (reader.NodeType == XmlNodeType.Element)
787                   {
788                     reader.Read ();
789                     plist.Add (MSymbol.symbol, (MSymbol) reader.Value);
790                     reader.Read ();
791                     reader.Read ();
792                   }
793                 tag = new Tag (ref plist);
794                 if (tag.HasWildcard || ! tag.Match (mdb.tag))
795                   return false;
796                 info = new MDatabaseInfo (plist);
797                 return true;
798               }
799           } catch (Exception e) {
800             Console.WriteLine ("error {0}", e);
801           }
802         }
803       return false;
804     }
805
806     private static void register_files (string dir, int dir_idx, MDatabase mdb)
807     {
808       int list_idx = mdb.ListIndex;
809       List<FileInfo> files = new List<FileInfo> ();
810       MGlob.FileList (ref files, dir, mdb.Info.Filename);
811       foreach (FileInfo fileinfo in files)
812         {
813           Tag tag;
814           MDatabaseInfo info;
815
816           if (mdb.Info.Format == MSymbol.plist
817               ? parse_plist_header (fileinfo, mdb, out tag, out info)
818               : parse_xml_header (fileinfo, mdb, out tag, out info))
819             {
820               info.Merge (mdb.Info);
821               if (Path.IsPathRooted (mdb.Info.Filename))
822                 info.Filename = fileinfo.FullName;
823               else
824                 info.Filename = fileinfo.Name;
825               register (list_idx, dir_idx, tag, info);
826             }
827         }
828     }
829
830     private void expand_wildcard ()
831     {
832       M17n.DebugPrint ("expanding: {0}\n", this);
833
834       if (DirIndex == 0)
835         register_files (null, DirIndex, this);
836       else
837         for (int i = 1; i < 4; i++)
838           if (DBDirs[i].DirInfo != null)
839             register_files (DBDirs[i].DirInfo.FullName, i, this);
840       DBStatus = MDBStatus.READY;
841     }
842
843     private static void maybe_expand_wildcard (Tag tag)
844     {
845       foreach (KeyValuePair<Tag, List<MDatabase>> kv in wdict)
846         {
847           M17n.DebugPrint ("expand check: {0}\n", kv.Key);
848           if (kv.Key.Match (tag))
849             {
850               foreach (MDatabase mdb in kv.Value)
851                 {
852                   if (mdb.DBStatus == MDBStatus.NOT_READY)
853                     mdb.expand_wildcard ();
854                 }
855             }
856         }
857     }
858
859     // Update the status.  Return true iff the database file is
860     // readable but changed.
861
862     private bool update_status ()
863     {
864       if (DBType == MDBType.UNKNOWN)
865         return true;
866       update_all (false);
867       if (DBStatus == MDBStatus.INVALID)
868         return false;
869       if (DBStatus != MDBStatus.NOT_READY)
870         {
871           try {
872             FileInfo.Refresh ();
873           } catch {
874             DBStatus = MDBStatus.INVALID;
875             return false;
876           }
877           if (LastLoaded >= FileInfo.LastWriteTime)
878             return false;
879           DBStatus = MDBStatus.READY;
880           return true;
881         }         
882       for (int i = 1; i < 4; i++)
883         if (DBDirs[i] != null && DBDirs[i].Dirname != null)
884           {
885             string filename = Path.Combine (DBDirs[i].Dirname, Info.Filename);
886             if (File.Exists (filename))
887               {
888                 FileInfo = new FileInfo (filename);
889                 DirIndex = i;
890                 DBStatus = MDBStatus.READY;
891                 return true;
892               }
893           }
894       return false;
895     }
896
897     public static MDatabase Find (Tag tag)
898     {
899       List<MDatabase> mdbs;
900       MDatabase mdb = null;
901
902       if (tag.HasWildcard)
903         throw new ArgumentException ("Wildcard not allowed: " + tag);
904
905       if (ndict.TryGetValue (tag, out mdbs))
906         {
907           mdbs.Sort ();
908           for (int i = 0; i < mdbs.Count; i++)
909             {
910               mdb = mdbs[i];
911               if (mdb.ListIndex == 0)
912                 return mdb;
913               if (mdb.DBStatus == MDBStatus.READY)
914                 break;
915             }
916         }
917       if (! update_all (false) && mdb != null)
918         return mdb;
919       maybe_expand_wildcard (tag);
920       if (! ndict.TryGetValue (tag, out mdbs))
921         return null;
922       for (int i = 0; i < mdbs.Count; i++)
923         {
924           mdb = mdbs[i];
925           mdb.update_status ();
926           if (mdb.DBStatus == MDBStatus.READY)
927             return mdb;
928         }
929       return null;
930     }
931
932     public static List<MDatabase> List (Tag tag)
933     {
934       List<MDatabase> list = new List<MDatabase> ();
935
936       update_all (false);
937       maybe_expand_wildcard (tag);
938
939       if (tag.HasWildcard)
940         {
941           foreach (KeyValuePair<Tag, List<MDatabase>> kv in ndict)
942             if (kv.Key.Match (tag))
943               foreach (MDatabase mdb in kv.Value)
944                 {
945                   mdb.update_status ();
946                   if (mdb.DBStatus == MDBStatus.READY)
947                     {
948                       list.Add (mdb);
949                       break;
950                     }
951                 }
952         }
953       else
954         {
955           List<MDatabase> mdbs;
956           if (ndict.TryGetValue (tag, out mdbs))
957             foreach (MDatabase mdb in mdbs)
958               {
959                 mdb.update_status ();
960                 if (mdb.DBStatus == MDBStatus.READY)
961                   {
962                     list.Add (mdb);
963                     break;
964                   }
965               }
966         }
967       return list;
968     }
969
970     private FileStream get_stream ()
971     {
972       if (loader != null
973           || (Info.Format != MSymbol.plist && Info.Format != Mxml))
974         {
975           LastLoadStatus = LoadStatus.InvalidLoadMethod;
976           return null;
977         }
978       if (DBStatus != MDBStatus.READY)
979         {
980           LastLoadStatus = LoadStatus.NotAvailable;
981           return null;
982         }
983
984       FileStream stream = null;
985       try {
986         stream = FileInfo.OpenRead ();
987       } catch {
988         LastLoadStatus = LoadStatus.NotReadable;          
989       }
990       return stream;
991     }
992
993     public object Load ()
994     {
995       if (loader != null)
996         return loader (tag, ExtraInfo);
997       if (Info.Format == Mxml)
998         {
999           XmlDocument doc = new XmlDocument (name_table);
1000           try {
1001             XmlTextReader reader
1002               = new XmlTextReader (FileInfo.FullName, name_table);
1003             doc.Load (reader);
1004             LastLoaded = DateTime.Now;      
1005           } catch (Exception e) {
1006             Console.WriteLine (e);
1007             LastLoadStatus = LoadStatus.InvalidContents;
1008           }
1009           return doc;
1010         }
1011
1012       FileStream stream = get_stream ();
1013       if (stream == null)
1014         return null;
1015       MPlist plist = null;
1016       try {
1017         plist = new MPlist (stream);
1018         LastLoaded = DateTime.Now;
1019       } catch {
1020         LastLoadStatus = LoadStatus.InvalidContents;
1021       } finally {
1022         stream.Dispose ();
1023       }
1024       return plist;
1025     }
1026
1027     public object Load (MSymbol key, MSymbol stop)
1028     {
1029       FileStream stream = get_stream ();
1030
1031       if (stream == null)
1032         return null;
1033       if (Info.Format == Mxml)
1034         {
1035           XmlDocument doc = new XmlDocument (name_table);
1036           XmlTextReader reader = new XmlTextReader (stream, name_table);
1037
1038           reader.WhitespaceHandling = WhitespaceHandling.None;
1039           try {
1040             reader.Read ();
1041             while (reader.NodeType != XmlNodeType.Element)
1042               reader.Read ();
1043             doc.LoadXml ("<" + reader.Name + "></" + reader.Name + ">");
1044             reader.Read ();
1045             XmlNode node = doc.DocumentElement;
1046             while (reader.NodeType == XmlNodeType.Element
1047                    ? reader.Name != stop.Name
1048                    : reader.NodeType != XmlNodeType.EndElement)
1049               if (reader.NodeType == XmlNodeType.Element
1050                   && reader.Name == key.Name)
1051                 node = doc.DocumentElement.InsertAfter (doc.ReadNode (reader),
1052                                                         node);
1053           } finally {
1054             reader.Close ();
1055             stream.Dispose ();
1056           }
1057           return doc;
1058         }
1059
1060       MPlist plist = null;
1061       try {
1062         plist = new MPlist (stream, key, stop);
1063         LastLoaded = DateTime.Now;
1064       } catch {
1065         LastLoadStatus = LoadStatus.InvalidContents;
1066       } finally {
1067         stream.Dispose ();
1068       }
1069       return plist;
1070     }
1071
1072     public object Load (MSymbol stop)
1073     {
1074       FileStream stream = get_stream ();
1075
1076       if (stream == null)
1077         return null;
1078       if (Info.Format == Mxml)
1079         {
1080           XmlDocument doc = new XmlDocument (name_table);
1081           XmlTextReader reader = new XmlTextReader (stream, name_table);
1082
1083           reader.WhitespaceHandling = WhitespaceHandling.None;
1084           try {
1085             reader.Read ();
1086             while (reader.NodeType != XmlNodeType.Element)
1087               reader.Read ();
1088             doc.LoadXml ("<" + reader.Name + "></" + reader.Name + ">");
1089             reader.Read ();
1090             XmlNode node = null;
1091             while (reader.NodeType == XmlNodeType.Element
1092                    ? reader.Name != stop.Name
1093                    : reader.NodeType != XmlNodeType.EndElement)
1094               if (reader.NodeType == XmlNodeType.Element)
1095                   node = doc.DocumentElement.InsertAfter (doc.ReadNode (reader),
1096                                                           node);
1097           } catch (Exception e) {
1098             Console.WriteLine (e);
1099           } finally {
1100             reader.Close ();
1101             stream.Dispose ();
1102           }
1103           return doc;
1104         }
1105
1106       MPlist plist = null;
1107       try {
1108         plist = new MPlist (stream, stop);
1109         LastLoaded = DateTime.Now;
1110       } catch (Exception e) {
1111         Console.WriteLine (e);
1112         LastLoadStatus = LoadStatus.InvalidContents;
1113       } finally {
1114         stream.Dispose ();
1115       }
1116       return plist;
1117     }
1118
1119     public XmlNode Load (string id, params string[] nodes)
1120     {
1121       FileStream stream = get_stream ();
1122       if (stream == null)
1123         return null;
1124       if (Info.Format != Mxml)
1125         throw new Exception ("Not an XML format");
1126
1127       XmlDocument doc = new XmlDocument (name_table);
1128       XmlTextReader reader = new XmlTextReader (stream, name_table);
1129       int len = nodes.Length;
1130
1131       reader.WhitespaceHandling = WhitespaceHandling.None;
1132       do {
1133         reader.Read ();
1134       } while (reader.NodeType != XmlNodeType.Element);
1135
1136       if (reader.Name != nodes[0])
1137         return null;
1138
1139       string ns = reader.GetAttribute ("xmlns");
1140       XmlNode top = doc.CreateNode (XmlNodeType.Element, nodes[0], ns);
1141       XmlNode node = top;
1142
1143       try {
1144         int i;
1145
1146         for (i = 1; i + 1 < len; i++)
1147           {
1148             if (! reader.ReadToDescendant (nodes[i]))
1149               return null;
1150             node = node.InsertAfter (doc.CreateNode (XmlNodeType.Element,
1151                                                      nodes[i], ns), null);
1152           }
1153         if (! reader.ReadToDescendant (nodes[i]))
1154           return null;
1155         XmlNode ref_node = null;
1156         while (reader.NodeType != XmlNodeType.EndElement)
1157           {
1158             if (reader.NodeType == XmlNodeType.Element)
1159               {
1160                 if (reader.Name == nodes[i]
1161                     && (id == null || id == reader.GetAttribute ("id")))
1162                   ref_node = node.InsertAfter (doc.ReadNode (reader), ref_node);
1163                 else
1164                   reader.Skip ();
1165               }
1166             else
1167               reader.Read ();
1168           }
1169               
1170       } catch (Exception e) {
1171         Console.WriteLine (e);
1172       } finally {
1173         reader.Close ();
1174         stream.Dispose ();
1175       }
1176       return top;
1177     }
1178
1179     /// <summary>Return a list of currently available database
1180     /// directory names</summary>.
1181     public static string[] DirectoryList ()
1182     {
1183       List<string> dirs = new List<string> ();
1184
1185       for (int i = 1; i < 4; i++)
1186         if (DBDirs[i].Dirname != null)
1187           dirs.Add (DBDirs[i].Dirname);
1188       return dirs.ToArray ();
1189     }
1190
1191     public MSymbol Format { get { return Info.Format; } }
1192
1193     public static bool Changed (DateTime time)
1194     {
1195       update_all (false);
1196       return (time < LastUpdateTime);
1197     }
1198
1199     public bool NeedReload ()
1200     {
1201       return update_status ();
1202     }  
1203
1204     // For IComparable<MDatabase>
1205     public int CompareTo (MDatabase other)
1206     {
1207       return (ListIndex == other.ListIndex
1208               ? DirIndex - other.DirIndex
1209               : ListIndex - other.ListIndex);
1210     }
1211   }
1212 }