*** 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 MPlist 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     private Loader loader;
421     private object ExtraInfo;
422     // Directory of the database file.
423     // -1:unknown, 0:absolute, 1:DBDirs[1], 2:DBDirs[2], 3:DBDirs[3]
424     private int DirIndex;
425     // Directory of the mdb.dir defining the database file.
426     // 0: EXPLICIT or UNKNOWN, 1:DBDirs[1], 2:DBDirs[2], 3:DBDirs[3]
427     private int ListIndex;
428     private MDBType DBType;
429     private MDBStatus DBStatus;
430     internal MDatabaseInfo Info;
431     // File in which the database contents is stored.  This is null
432     // when DBStatus is NOT_READY.
433     internal FileInfo FileInfo;
434     // When the database file is loaded last.
435     internal DateTime LastLoaded = DateTime.Now;
436
437     public enum LoadStatus
438     {
439       None,
440       InvalidLoadMethod,
441       NotAvailable,
442       NotReadable,
443       InvalidContents,
444     };
445
446     public LoadStatus LastLoadStatus = LoadStatus.None;
447
448     static MDatabase ()
449     {
450       string share_dir = (Environment.GetFolderPath
451                           (Environment.SpecialFolder.CommonApplicationData));
452       string usr_dir = (Environment.GetFolderPath
453                         (Environment.SpecialFolder.ApplicationData));
454
455       try {
456         string dir = Environment.GetEnvironmentVariable ("M17NDIR");
457         DBDirs[1] = new MDatabaseDir (dir);
458       } catch {
459         try {
460           DBDirs[1] = new MDatabaseDir (Path.Combine (usr_dir, ".m17n.d"));
461         } catch (ArgumentException) {
462           DBDirs[1] = new MDatabaseDir (Path.Combine (usr_dir, "_m17n_d"));
463         }
464       }
465       DBDirs[2] = new MDatabaseDir (null);
466       DBDirs[3] = new MDatabaseDir (Path.Combine (share_dir, "m17n"));
467       update_all (true);
468     }
469
470     public static string ApplicationDir
471     {
472       get { return (DBDirs[1].Dirname); }
473       set { DBDirs[2] = new MDatabaseDir (value); update_all (true); }
474     }
475
476     // Update all listing and directories.  Return true iff some are
477     // really updated.
478     private static bool update_all (bool force)
479     {
480       if (! force && DateTime.Now - LastUpdateTime < CheckInterval)
481         return false;
482
483       bool updated = false;
484       for (int i = 1; i < 4; i++)
485         if (DBDirs[i].Dirname != null)
486           {
487             DBDirs[i].Refresh ();
488             if (LastUpdateTime < DBDirs[i].ListChangeTime)
489               {
490                 update_list (i);
491                 updated = true;
492               }
493             if (LastUpdateTime < DBDirs[i].DirChangeTime)
494               {
495                 update_dir (i);
496                 updated = true;
497               }
498           }
499       if (updated)
500         LastUpdateTime = DateTime.Now;
501       return updated;
502     }
503
504     public static void Dump ()
505     {
506       update_all (false);
507       Console.WriteLine ("[DBDirs]");
508       for (int i = 1; i < 4; i++)
509         if (DBDirs[i].Dirname != null)
510           {
511             if (DBDirs[i].DirInfo != null)
512               {
513                 Console.Write ("{0}:{1}", i, DBDirs[i].DirInfo.FullName);
514                 if (DBDirs[i].ListInfo != null)
515                   Console.WriteLine (" {0}", DBDirs[i].ListInfo);
516                 else
517                   Console.WriteLine (" .. no mdb.dir");
518               }
519             else
520               Console.WriteLine ("{0}:{1} .. not exist", i, DBDirs[i].Dirname);
521           }
522
523       Console.WriteLine ("[WDICT]");
524       foreach (KeyValuePair<Tag, List<MDatabase>> kv in wdict)
525         foreach (MDatabase mdb in kv.Value)
526           Console.WriteLine (mdb);
527
528       Console.WriteLine ("[NDICT]");
529       foreach (KeyValuePair<Tag, List<MDatabase>> kv in ndict)
530         foreach (MDatabase mdb in kv.Value)
531           Console.WriteLine (mdb);
532     }
533
534     private static void register (MDatabase mdb)
535     {
536       Dictionary<MDatabase.Tag, List<MDatabase>> dict
537         = mdb.DBType == MDBType.WILDCARD ? wdict : ndict;
538       List<MDatabase> mdbs;
539
540       if (dict.TryGetValue (mdb.tag, out mdbs))
541         {
542           for (int i = 0; i < mdbs.Count; i++)
543             if (mdbs[i].ListIndex == mdb.ListIndex)
544               {
545                 mdbs[i].DBStatus = MDBStatus.INVALID;
546                 mdbs[i] = mdb;
547                 return;
548               }
549           mdbs.Add (mdb);
550         }
551       else
552         {
553           mdbs = new List<MDatabase> (1);
554           mdbs.Add (mdb);
555           dict.Add (mdb.tag, mdbs);
556         }
557     }
558
559     private static void register (int list_idx, int dir_idx,
560                                   Tag tag, MDatabaseInfo info)
561     {
562       Dictionary<MDatabase.Tag, List<MDatabase>> dict
563         = tag.HasWildcard ? wdict : ndict;
564       List<MDatabase> mdbs;
565       MDatabase mdb;
566
567       if (dict.TryGetValue (tag, out mdbs))
568         for (int i = 0; i < mdbs.Count; i++)
569           {
570             mdb = mdbs[i];
571             if (mdb.ListIndex == list_idx && mdb.DirIndex >= dir_idx)
572               {
573                 mdb.DirIndex = dir_idx;
574                 if (dict == wdict)
575                   {
576                     mdb.DBType = MDBType.WILDCARD;
577                     mdb.DBStatus = MDBStatus.NOT_READY;
578                   }
579                 else if (dir_idx == -1)
580                   {
581                     mdb.DBType = MDBType.AUTO;
582                     mdb.DBStatus = MDBStatus.NOT_READY;
583                   }
584                 else
585                   {
586                     mdb.DBType = MDBType.MULTIPLE;
587                     mdb.DBStatus = MDBStatus.READY;
588                   }
589                 mdb.Info = info;
590                 if (mdb.DBStatus == MDBStatus.INVALID)
591                   M17n.DebugPrint ("registering: {0}\n", mdb);
592                 else
593                   M17n.DebugPrint ("updating: {0}\n", mdb);
594                 return;
595               }
596           }
597       else
598         {
599           mdbs = new List<MDatabase> (1);
600           dict.Add (tag, mdbs);
601         }
602       mdb = new MDatabase (list_idx, dir_idx, tag, info);
603       M17n.DebugPrint ("registering: {0}\n", mdb);
604       mdbs.Add (mdb);
605     }
606
607     public MDatabase (Tag tag, Loader loader, object extra_info)
608     {
609       this.tag = tag;
610       this.loader = loader;
611       DBType = MDBType.UNKNOWN;
612       DBStatus = MDBStatus.READY;
613       DirIndex = 0;
614       ListIndex = 0;
615       ExtraInfo = extra_info;
616       register (this);
617     }
618
619     public MDatabase (Tag tag, string filename)
620     {
621       this.tag = tag;
622       DBType = MDBType.EXPLICIT;
623       if (Path.IsPathRooted (filename))
624         {
625           DirIndex = 0;
626           if (File.Exists (filename))
627             DBStatus = MDBStatus.READY;
628           else
629             DBStatus = MDBStatus.INVALID;
630         }
631       else
632         {
633           DirIndex = -1;
634           DBStatus = MDBStatus.NOT_READY;
635         }
636       ListIndex = 0;
637       Info = new MDatabaseInfo ();
638       Info.Filename = filename;
639       register (this);
640     }
641
642     private MDatabase (int list_idx, int dir_idx, Tag tag, MDatabaseInfo info)
643     {
644       this.tag = tag;
645       this.Info = info;
646       DBType = this.tag.HasWildcard ? MDBType.WILDCARD : MDBType.AUTO;
647       if (tag[0] == Mchar_table || tag[0] == Mcharset)
648         Info.Format = tag[0];
649       ListIndex = list_idx;
650       DirIndex = dir_idx;
651       if (Path.IsPathRooted (Info.Filename))
652         DBStatus = MDBStatus.READY;
653       else
654         DBStatus = MDBStatus.NOT_READY;
655       if (Info.Format == Mchar_table)
656         MCharProp.Define (tag[2], this);
657     }
658
659     public override String ToString () {
660       string str = ("#<MDataBase (" + ListIndex + "," + DirIndex + ") "
661                     + tag + " " + DBType + " " + DBStatus);
662
663       if (DBType != MDBType.EXPLICIT && DBType != MDBType.UNKNOWN)
664         str += " " + Info;
665       return str + ">";
666     }
667
668     // Update (or disable) databases defined by "mdb.dir" in
669     // DBDirs[list_idx].
670     private static void update_list (int list_idx)
671     {
672       M17n.DebugPrint ("Updating list: {0}\n", list_idx);
673       // At first disable all target databases.
674       foreach (KeyValuePair<Tag, List<MDatabase>> kv in wdict)
675         foreach (MDatabase mdb in kv.Value)
676           if (mdb.ListIndex == list_idx)
677             {
678               M17n.DebugPrint ("deleting: {0}\n", mdb);
679               mdb.DBStatus = MDBStatus.INVALID;
680               break;
681             }
682       foreach (KeyValuePair<Tag, List<MDatabase>> kv in ndict)
683         foreach (MDatabase mdb in kv.Value)
684           if (mdb.ListIndex == list_idx)
685             {
686               M17n.DebugPrint ("deleting: {0}\n", mdb);
687               mdb.DBStatus = MDBStatus.INVALID;
688               break;
689             }
690
691       FileInfo dblist = DBDirs[list_idx].ListInfo;
692       if (dblist == null)
693         return;
694
695       MPlist plist = null;
696       using (FileStream stream = File.OpenRead (dblist.FullName))
697         {
698           plist = new MPlist (stream);
699         }
700       if (plist == null)
701         return;
702       foreach (MPlist pl in plist)
703         if (pl.IsPlist)
704           {
705             try
706               {
707                 MPlist p = pl.Plist;
708                 Tag tag = new Tag (ref p);
709                 MDatabaseInfo info = new MDatabaseInfo (p);
710                 int dir_idx = Path.IsPathRooted (info.Filename) ? 0 : -1;
711                 register (list_idx, dir_idx, tag, info);
712               }
713             catch (Exception e)
714               {
715                 Console.WriteLine (e.Message + ": " + pl.Plist);
716               }
717           }
718     }
719
720     // Update (or disable) databases in DBDirs[dir_idx].
721     private static void update_dir (int dir_idx)
722     {
723       M17n.DebugPrint ("Updating dir: {0}\n", dir_idx);
724       // Reset all databases in DBDirs[dir_idx].
725       foreach (KeyValuePair<Tag, List<MDatabase>> kv in ndict)
726         foreach (MDatabase mdb in kv.Value)
727           if (mdb.DirIndex >= dir_idx)
728             {
729               M17n.DebugPrint ("disabling: {0}\n", mdb);
730               mdb.DBStatus = MDBStatus.NOT_READY;
731               mdb.DirIndex = -1;
732             }
733       // Re-expand all WILDCARD databases in DBDirs[dir_idx].
734       if (DBDirs[dir_idx].DirInfo != null)
735         foreach (KeyValuePair<Tag, List<MDatabase>> kv in wdict)
736           foreach (MDatabase mdb in kv.Value)
737             if (mdb.DBStatus == MDBStatus.READY)
738               {
739                 M17n.DebugPrint ("re-expanding: {0}\n", mdb);
740                 register_files (DBDirs[dir_idx].Dirname, dir_idx, mdb);
741               }
742     }
743
744     private static bool parse_plist_header (FileInfo fileinfo, MDatabase mdb,
745                                             out Tag tag, out MDatabaseInfo info)
746     {
747       MPlist plist = null;
748
749       tag = new Tag (MSymbol.nil);
750       info = null;
751       using (FileStream stream = fileinfo.OpenRead ())
752         {
753           try { plist = new MPlist (stream, 1); } catch { }
754         }
755       if (plist == null || ! plist.IsPlist)
756         return false;
757       plist = plist.Plist;
758       tag = new Tag (ref plist);
759       if (tag.HasWildcard || ! tag.Match (mdb.tag))
760         return false;
761       info = new MDatabaseInfo (plist);
762       return true;
763     }
764
765     private static bool parse_xml_header (FileInfo fileinfo, MDatabase mdb,
766                                           out Tag tag, out MDatabaseInfo info)
767     {
768       tag = new Tag (MSymbol.nil);
769       info = null;
770       using (FileStream stream = fileinfo.OpenRead ())
771         {
772           try {
773             MPlist plist = new MPlist ();
774             XmlTextReader reader = new XmlTextReader (stream);
775             
776             reader.WhitespaceHandling = WhitespaceHandling.None;
777             do {
778               reader.Read ();
779             } while (reader.NodeType != XmlNodeType.Element);
780             plist.Add (MSymbol.symbol, (MSymbol) reader.Name);
781             reader.Read ();
782             if (reader.NodeType == XmlNodeType.Element && reader.Name == "tags")
783               {
784                 reader.Read ();
785                 while (reader.NodeType == XmlNodeType.Element)
786                   {
787                     reader.Read ();
788                     plist.Add (MSymbol.symbol, (MSymbol) reader.Value);
789                     reader.Read ();
790                     reader.Read ();
791                   }
792                 tag = new Tag (ref plist);
793                 if (tag.HasWildcard || ! tag.Match (mdb.tag))
794                   return false;
795                 info = new MDatabaseInfo (plist);
796                 return true;
797               }
798           } catch (Exception e) {
799             Console.WriteLine ("error {0}", e);
800           }
801         }
802       return false;
803     }
804
805     private static void register_files (string dir, int dir_idx, MDatabase mdb)
806     {
807       int list_idx = mdb.ListIndex;
808       List<FileInfo> files = new List<FileInfo> ();
809       MGlob.FileList (ref files, dir, mdb.Info.Filename);
810       foreach (FileInfo fileinfo in files)
811         {
812           Tag tag;
813           MDatabaseInfo info;
814
815           if (mdb.Info.Format == MSymbol.plist
816               ? parse_plist_header (fileinfo, mdb, out tag, out info)
817               : parse_xml_header (fileinfo, mdb, out tag, out info))
818             {
819               info.Merge (mdb.Info);
820               if (Path.IsPathRooted (mdb.Info.Filename))
821                 info.Filename = fileinfo.FullName;
822               else
823                 info.Filename = fileinfo.Name;
824               register (list_idx, dir_idx, tag, info);
825             }
826         }
827     }
828
829     private void expand_wildcard ()
830     {
831       M17n.DebugPrint ("expanding: {0}\n", this);
832
833       if (DirIndex == 0)
834         register_files (null, DirIndex, this);
835       else
836         for (int i = 1; i < 4; i++)
837           if (DBDirs[i].DirInfo != null)
838             register_files (DBDirs[i].DirInfo.FullName, i, this);
839       DBStatus = MDBStatus.READY;
840     }
841
842     private static void maybe_expand_wildcard (Tag tag)
843     {
844       foreach (KeyValuePair<Tag, List<MDatabase>> kv in wdict)
845         {
846           M17n.DebugPrint ("expand check: {0}\n", kv.Key);
847           if (kv.Key.Match (tag))
848             {
849               foreach (MDatabase mdb in kv.Value)
850                 {
851                   if (mdb.DBStatus == MDBStatus.NOT_READY)
852                     mdb.expand_wildcard ();
853                 }
854             }
855         }
856     }
857
858     // Update the status.  Return true iff the database file is
859     // readable but changed.
860
861     private bool update_status ()
862     {
863       if (DBType == MDBType.UNKNOWN)
864         return true;
865       update_all (false);
866       if (DBStatus == MDBStatus.INVALID)
867         return false;
868       if (DBStatus != MDBStatus.NOT_READY)
869         {
870           try {
871             FileInfo.Refresh ();
872           } catch {
873             DBStatus = MDBStatus.INVALID;
874             return false;
875           }
876           if (LastLoaded >= FileInfo.LastWriteTime)
877             return false;
878           DBStatus = MDBStatus.READY;
879           return true;
880         }         
881       for (int i = 1; i < 4; i++)
882         if (DBDirs[i] != null && DBDirs[i].Dirname != null)
883           {
884             string filename = Path.Combine (DBDirs[i].Dirname, Info.Filename);
885             if (File.Exists (filename))
886               {
887                 FileInfo = new FileInfo (filename);
888                 DirIndex = i;
889                 DBStatus = MDBStatus.READY;
890                 return true;
891               }
892           }
893       return false;
894     }
895
896     public static MDatabase Find (Tag tag)
897     {
898       List<MDatabase> mdbs;
899       MDatabase mdb = null;
900
901       if (tag.HasWildcard)
902         throw new ArgumentException ("Wildcard not allowed: " + tag);
903
904       if (ndict.TryGetValue (tag, out mdbs))
905         {
906           mdbs.Sort ();
907           for (int i = 0; i < mdbs.Count; i++)
908             {
909               mdb = mdbs[i];
910               if (mdb.ListIndex == 0)
911                 return mdb;
912               if (mdb.DBStatus == MDBStatus.READY)
913                 break;
914             }
915         }
916       if (! update_all (false) && mdb != null)
917         return mdb;
918       maybe_expand_wildcard (tag);
919       if (! ndict.TryGetValue (tag, out mdbs))
920         return null;
921       for (int i = 0; i < mdbs.Count; i++)
922         {
923           mdb = mdbs[i];
924           mdb.update_status ();
925           if (mdb.DBStatus == MDBStatus.READY)
926             return mdb;
927         }
928       return null;
929     }
930
931     public static List<MDatabase> List (Tag tag)
932     {
933       List<MDatabase> list = new List<MDatabase> ();
934
935       update_all (false);
936       maybe_expand_wildcard (tag);
937
938       if (tag.HasWildcard)
939         {
940           foreach (KeyValuePair<Tag, List<MDatabase>> kv in ndict)
941             if (kv.Key.Match (tag))
942               foreach (MDatabase mdb in kv.Value)
943                 {
944                   mdb.update_status ();
945                   if (mdb.DBStatus == MDBStatus.READY)
946                     {
947                       list.Add (mdb);
948                       break;
949                     }
950                 }
951         }
952       else
953         {
954           List<MDatabase> mdbs;
955           if (ndict.TryGetValue (tag, out mdbs))
956             foreach (MDatabase mdb in mdbs)
957               {
958                 mdb.update_status ();
959                 if (mdb.DBStatus == MDBStatus.READY)
960                   {
961                     list.Add (mdb);
962                     break;
963                   }
964               }
965         }
966       return list;
967     }
968
969     private FileStream get_stream ()
970     {
971       if (DBStatus != MDBStatus.READY)
972         {
973           LastLoadStatus = LoadStatus.NotAvailable;
974           return null;
975         }
976
977       FileStream stream = null;
978       try {
979         stream = FileInfo.OpenRead ();
980       } catch {
981         LastLoadStatus = LoadStatus.NotReadable;          
982       }
983       return stream;
984     }
985
986     public MPlist Load ()
987     {
988       if (loader != null)
989         return loader (tag, ExtraInfo);
990       if (Info.Format != MSymbol.plist)
991         throw new Exception ("Not a plist database");
992
993       FileStream stream = get_stream ();
994       if (stream == null)
995         return null;
996       MPlist plist = null;
997       try {
998         plist = new MPlist (stream);
999         LastLoaded = DateTime.Now;
1000       } catch {
1001         LastLoadStatus = LoadStatus.InvalidContents;
1002       } finally {
1003         stream.Dispose ();
1004       }
1005       return plist;
1006     }
1007
1008     public MPlist Load (MSymbol key, MSymbol stop)
1009     {
1010       if (loader != null)
1011         throw new Exception ("Partial load is impossible");
1012       if (Info.Format != MSymbol.plist)
1013         throw new Exception ("Not a plist database");
1014
1015       FileStream stream = get_stream ();
1016       if (stream == null)
1017         return null;
1018       try {
1019         MPlist plist = new MPlist (stream, key, stop);
1020         LastLoaded = DateTime.Now;
1021         return plist;
1022       } catch {
1023         LastLoadStatus = LoadStatus.InvalidContents;
1024         return null;
1025       } finally {
1026         stream.Dispose ();
1027       }
1028     }
1029
1030     public MPlist Load (MSymbol stop)
1031     {
1032       if (loader != null)
1033         throw new Exception ("Partial load is impossible");
1034       if (Info.Format != MSymbol.plist)
1035         throw new Exception ("Not a plist database");
1036
1037       FileStream stream = get_stream ();
1038       if (stream == null)
1039         return null;
1040       try {
1041         MPlist plist = new MPlist (stream, stop);
1042         LastLoaded = DateTime.Now;
1043         return plist;
1044       } catch (Exception e) {
1045         Console.WriteLine (e);
1046         LastLoadStatus = LoadStatus.InvalidContents;
1047         return null;
1048       } finally {
1049         stream.Dispose ();
1050       }
1051     }
1052
1053     private XmlTextReader get_reader (XmlDocument doc)
1054     {
1055       XmlTextReader reader;
1056
1057       try {
1058         if (doc.NameTable != null)
1059           reader = new XmlTextReader (FileInfo.FullName, doc.NameTable);
1060         else
1061           reader = new XmlTextReader (FileInfo.FullName);
1062         reader.WhitespaceHandling = WhitespaceHandling.None;
1063       } catch {
1064         LastLoadStatus = LoadStatus.NotReadable;
1065         reader = null;
1066       }
1067       return reader;
1068     }
1069
1070     public bool Load (XmlDocument doc)
1071     {
1072       if (Info.Format != Mxml)
1073         throw new Exception ("Not an XML database");
1074       XmlTextReader reader = get_reader (doc);
1075       if (reader == null)
1076         return false;
1077       try {
1078         doc.Load (reader);
1079         LastLoaded = DateTime.Now;          
1080         return true;
1081       } catch (Exception e) {
1082         Console.WriteLine (e);
1083         LastLoadStatus = LoadStatus.InvalidContents;
1084         return false;
1085       } finally {
1086         reader.Close ();
1087       }
1088     }
1089
1090     public bool Load (XmlDocument doc, MSymbol key, MSymbol stop)
1091     {
1092       if (Info.Format != Mxml)
1093         throw new Exception ("Not an XML database");
1094       XmlTextReader reader = get_reader (doc);
1095       if (reader == null)
1096         return false;
1097       try {
1098         reader.Read ();
1099         while (reader.NodeType != XmlNodeType.Element)
1100           reader.Read ();
1101         doc.LoadXml ("<" + reader.Name + "></" + reader.Name + ">");
1102         reader.Read ();
1103         XmlNode node = doc.DocumentElement;
1104         while (reader.NodeType == XmlNodeType.Element
1105                ? reader.Name != stop.Name
1106                : reader.NodeType != XmlNodeType.EndElement)
1107           if (reader.NodeType == XmlNodeType.Element
1108               && reader.Name == key.Name)
1109             node = doc.DocumentElement.InsertAfter (doc.ReadNode (reader),
1110                                                     node);
1111         return true;
1112       } catch (Exception e) {
1113         Console.WriteLine (e);
1114         return false;
1115       } finally {
1116         reader.Close ();
1117       }
1118     }
1119
1120     public bool Load (XmlDocument doc, MSymbol stop)
1121     {
1122       if (Info.Format != Mxml)
1123         throw new Exception ("Not an XML database");
1124       XmlTextReader reader = get_reader (doc);
1125       if (reader == null)
1126         return false;
1127       try {
1128         reader.Read ();
1129         while (reader.NodeType != XmlNodeType.Element)
1130           reader.Read ();
1131         doc.LoadXml ("<" + reader.Name + "></" + reader.Name + ">");
1132         reader.Read ();
1133         XmlNode node = doc.DocumentElement;
1134         while (reader.NodeType == XmlNodeType.Element
1135                ? reader.Name != stop.Name
1136                : reader.NodeType != XmlNodeType.EndElement)
1137           node = doc.DocumentElement.InsertAfter (doc.ReadNode (reader), node);
1138         return true;
1139       } catch (Exception e) {
1140         Console.WriteLine (e);
1141         return false;
1142       } finally {
1143         reader.Close ();
1144       }
1145     }
1146
1147     public XmlNode Load (XmlDocument doc, string id, params string[] nodes)
1148     {
1149       if (Info.Format != Mxml)
1150         throw new Exception ("Not an XML database");
1151       XmlTextReader reader = get_reader (doc);
1152       if (reader == null)
1153         return null;
1154       try {
1155         int len = nodes.Length;
1156         do {
1157           reader.Read ();
1158         } while (reader.NodeType != XmlNodeType.Element);
1159         if (reader.Name != nodes[0])
1160           return null;
1161
1162         string ns = reader.GetAttribute ("xmlns");
1163         XmlNode top = doc.CreateNode (XmlNodeType.Element, nodes[0], ns);
1164         XmlNode node = top;
1165         int i;
1166
1167         for (i = 1; i + 1 < len; i++)
1168           {
1169             if (! reader.ReadToDescendant (nodes[i]))
1170               return null;
1171             node = node.InsertAfter (doc.CreateNode (XmlNodeType.Element,
1172                                                      nodes[i], ns), null);
1173           }
1174         if (! reader.ReadToDescendant (nodes[i]))
1175           return null;
1176         XmlNode ref_node = null;
1177         while (reader.NodeType != XmlNodeType.EndElement)
1178           {
1179             if (reader.NodeType == XmlNodeType.Element)
1180               {
1181                 if (reader.Name == nodes[i]
1182                     && (id == null || id == reader.GetAttribute ("id")))
1183                   ref_node = node.InsertAfter (doc.ReadNode (reader), ref_node);
1184                 else
1185                   reader.Skip ();
1186               }
1187             else
1188               reader.Read ();
1189           }
1190         return top;
1191       } catch (Exception e) {
1192         Console.WriteLine (e);
1193         return null;
1194       } finally {
1195         reader.Close ();
1196       }
1197     }
1198
1199     /// <summary>Return a list of currently available database
1200     /// directory names</summary>.
1201     public static string[] DirectoryList ()
1202     {
1203       List<string> dirs = new List<string> ();
1204
1205       for (int i = 1; i < 4; i++)
1206         if (DBDirs[i].Dirname != null)
1207           dirs.Add (DBDirs[i].Dirname);
1208       return dirs.ToArray ();
1209     }
1210
1211     public MSymbol Format { get { return Info.Format; } }
1212
1213     public static bool Changed (DateTime time)
1214     {
1215       update_all (false);
1216       return (time < LastUpdateTime);
1217     }
1218
1219     public bool NeedReload ()
1220     {
1221       return update_status ();
1222     }  
1223
1224     // For IComparable<MDatabase>
1225     public int CompareTo (MDatabase other)
1226     {
1227       return (ListIndex == other.ListIndex
1228               ? DirIndex - other.DirIndex
1229               : ListIndex - other.ListIndex);
1230     }
1231   }
1232 }