* Makefile: Add reference to ICSharpCode.SharpZipLib.dll.
[mono.git] / mcs / tools / monodoc / Monodoc / provider.cs
1 //
2 // Provider: shared code and interfaces for providers
3 //
4 // Author:
5 //   Miguel de Icaza (miguel@ximian.com)
6 //
7 // (C) 2002, Ximian, Inc.
8 //
9 // TODO:
10 //   Each node should have a provider link
11 //
12 //   Should encode numbers using a runlength encoding to save space
13 //
14 namespace Monodoc {
15 using System;
16 using System.IO;
17 using System.Text;
18 using System.Text.RegularExpressions;
19 using System.Collections;
20 using System.Diagnostics;
21 using System.Configuration;
22 using System.Text.RegularExpressions;
23 using System.Xml;
24 using System.Xml.XPath;
25 using ICSharpCode.SharpZipLib.Zip;
26
27 using Monodoc.Lucene.Net.Index;
28 using Monodoc.Lucene.Net.Analysis.Standard;
29
30 using Mono.Documentation;
31
32 /// <summary>
33 ///    This tree is populated by the documentation providers, or populated
34 ///    from a binary encoding of the tree.  The format of the tree is designed
35 ///    to minimize the need to load it in full.
36 /// </summary>
37 public class Tree : Node {
38
39 #region Loading the tree from a file
40
41         /// <summary>
42         ///   Our HelpSource container
43         /// </summary>
44         public readonly HelpSource HelpSource;
45         
46         internal FileStream InputStream;
47         internal BinaryReader InputReader;
48
49         /// <summary>
50         ///   Load from file constructor
51         /// </summary>
52         public Tree (HelpSource hs, string filename) : base (null, null)
53         {
54                 Encoding utf8 = new UTF8Encoding (false, true);
55
56                 if (!File.Exists (filename)){
57                         throw new FileNotFoundException ();
58                 }
59                 
60                 InputStream = File.OpenRead (filename);
61                 InputReader = new BinaryReader (InputStream, utf8);
62                 byte [] sig = InputReader.ReadBytes (4);
63                 
64                 if (!GoodSig (sig))
65                         throw new Exception ("Invalid file format");
66                 
67                 InputStream.Position = 4;
68                 position = InputReader.ReadInt32 ();
69
70                 LoadNode ();
71                 HelpSource = hs;
72         }
73
74         /// <summary>
75         ///    Tree creation and merged tree constructor
76         /// </summary>
77         public Tree (HelpSource hs, string caption, string url) : base (caption, url)
78         {
79                 HelpSource = hs;
80         }
81
82         public Tree (HelpSource hs, Node parent, string caption, string element) : base (parent, caption, element)
83         {
84                 HelpSource = hs;
85         }
86
87 #endregion
88
89         /// <summary>
90         ///    Saves the tree into the specified file using the help file format.
91         /// </summary>
92         public void Save (string file)
93         {
94                 Encoding utf8 = new UTF8Encoding (false, true);
95                 using (FileStream output = File.OpenWrite (file)){
96                         // Skip over the pointer to the first node.
97                         output.Position = 8;
98                         
99                         using (BinaryWriter writer = new BinaryWriter (output, utf8)){
100                                 // Recursively dump
101                                 Dump (output, writer);
102
103                                 output.Position = 0;
104                                 writer.Write (new byte [] { (byte) 'M', (byte) 'o', (byte) 'H', (byte) 'P' });
105                                 writer.Write (position);
106                         }
107                 }
108         }
109
110         static bool GoodSig (byte [] sig)
111         {
112                 if (sig.Length != 4)
113                         return false;
114                 if (sig [0] != (byte) 'M' ||
115                     sig [1] != (byte) 'o' ||
116                     sig [2] != (byte) 'H' ||
117                     sig [3] != (byte) 'P')
118                         return false;
119                 return true;
120         }
121
122 }
123
124 public class Node : IComparable {
125         string caption, element;
126         public bool Documented;
127         public readonly Tree tree;
128         Node parent;
129         protected ArrayList nodes;
130         protected internal int position;
131
132         static ArrayList empty = ArrayList.ReadOnly(new ArrayList(0));
133
134         /// <summary>
135         ///    Creates a node, called by the Tree.
136         /// </summary>
137         public Node (string caption, string element)
138         {
139                 this.tree = (Tree) this;
140                 this.caption = caption;
141                 this.element = element;
142                 parent = null;
143         }
144
145         public Node (Node parent, string caption, string element)
146         {
147                 this.parent = parent;
148                 this.tree = parent.tree;
149                 this.caption = caption;
150                 this.element = element;
151         }
152         
153         /// <summary>
154         ///    Creates a node from an on-disk representation
155         /// </summary>
156         Node (Node parent, int address)
157         {
158                 this.parent = parent;
159                 position = address;
160                 this.tree = parent.tree;
161                 if (address > 0)
162                         LoadNode ();
163         }
164
165         public void AddNode (Node n)
166         {
167                 Nodes.Add (n);
168                 n.parent = this;
169                 n.Documented = true;
170         }
171
172         public void DelNode (Node n)
173         {
174                 Nodes.Remove (n);
175         }
176
177         public ArrayList Nodes {
178                 get {
179                         if (position < 0)
180                                 LoadNode ();
181                         return nodes != null ? nodes : empty;
182                 }
183         }
184
185         public string Element {
186                 get {
187                         if (position < 0)
188                                 LoadNode ();
189                         return element;
190                 }
191
192                 set {
193                         element = value;
194                 }
195         }
196
197         public string Caption {
198                 get {
199                         if (position < 0)
200                                 LoadNode ();
201                         return caption;
202                 }
203         }
204         
205         public Node Parent {
206                 get {
207                         return parent;
208                 }
209         }
210                 
211         public void LoadNode ()
212         {
213                 if (position < 0)
214                         position = -position;
215
216                 tree.InputStream.Position = position;
217                 BinaryReader reader = tree.InputReader;
218                 int count = DecodeInt (reader);
219                 element = reader.ReadString ();
220                 caption = reader.ReadString ();
221                 if (count == 0)
222                         return;
223                 
224                 nodes = new ArrayList (count);
225                 for (int i = 0; i < count; i++){
226                         int child_address = DecodeInt (reader);
227                                                               
228                         Node t = new Node (this, -child_address);
229                         nodes.Add (t);
230                 }
231         }
232         
233         /// <summary>
234         ///   Creates a new node, in the locator entry point, and with
235         ///   a user visible caption of @caption
236         /// </summary>
237         public Node CreateNode (string c_caption, string c_element)
238         {
239                 if (nodes == null)
240                         nodes = new ArrayList ();
241
242                 Node t = new Node (this, c_caption, c_element);
243                 nodes.Add (t);
244                 return t;
245         }
246
247         /// <summary>
248         ///   Looks up or creates a new node, in the locator entry point, and with
249         ///   a user visible caption of @caption.  This is different from
250         ///   CreateNode in that it will look up an existing node for the given @locator.
251         /// </summary>
252         public Node LookupNode (string c_caption, string c_element)
253         {
254                 if (nodes == null)
255                         return CreateNode (c_caption, c_element);
256
257                 foreach (Node n in nodes){
258                         if (n.element == c_element)
259                                 return n;
260                 }
261                 return CreateNode (c_caption, c_element);
262         }
263
264         public void EnsureNodes ()
265         {
266                 if (nodes == null)
267                         nodes = new ArrayList ();
268         }
269         
270         public bool IsLeaf {
271                 get {
272                         return nodes == null;
273                 }
274         }
275
276         void EncodeInt (BinaryWriter writer, int value)
277         {
278                 do {
279                         int high = (value >> 7) & 0x01ffffff;
280                         byte b = (byte)(value & 0x7f);
281
282                         if (high != 0) {
283                                 b = (byte)(b | 0x80);
284                         }
285                         
286                         writer.Write(b);
287                         value = high;
288                 } while(value != 0);
289         }
290
291         int DecodeInt (BinaryReader reader)
292         {
293                 int ret = 0;
294                 int shift = 0;
295                 byte b;
296                 
297                         do {
298                                 b = reader.ReadByte();
299
300                                 ret = ret | ((b & 0x7f) << shift);
301                                 shift += 7;
302                         } while ((b & 0x80) == 0x80);
303                         
304                         return ret;
305         }
306
307         internal void Dump (FileStream output, BinaryWriter writer)
308         {
309                 if (nodes != null){
310                         foreach (Node child in nodes){
311                                 child.Dump (output, writer);
312                         }
313                 }
314                 position = (int) output.Position;
315                 EncodeInt (writer, nodes == null ? 0 : (int) nodes.Count);
316                 writer.Write (element);
317                 writer.Write (caption);
318
319                 if (nodes != null){
320                         foreach (Node child in nodes){
321                                 EncodeInt (writer, child.position);
322                         }
323                 }
324         }
325
326         static int indent;
327
328         static void Indent ()
329         {
330                 for (int i = 0; i < indent; i++)
331                         Console.Write ("   ");
332         }
333         
334         public static void PrintTree (Node node)
335         {
336                 Indent ();
337                 Console.WriteLine ("{0},{1}", node.Element, node.Caption);
338                 if (node.Nodes.Count == 0)
339                         return;
340
341                 indent++;
342                 foreach (Node n in node.Nodes)
343                         PrintTree (n);
344                 indent--;
345         }
346
347         public void Sort ()
348         {
349                 if (nodes != null)
350                         nodes.Sort ();
351         }
352
353         public string URL {
354                 get {
355                         if (position < 0)
356                                 LoadNode ();
357
358                         if (element.IndexOf (":") >= 0)
359                                 return element;
360
361                         if (parent != null){
362                                 string url = parent.URL;
363
364                                 if (url.EndsWith ("/"))
365                                         return url + element;
366                                 else
367                                         return parent.URL + "/" + element;
368                         } else
369                                 return element;
370                 }
371         }
372
373         int IComparable.CompareTo (object obj)
374         {
375                 Node other = obj as Node;
376                 if (other == null)
377                         return -1;
378
379                 if (position < 0)
380                         LoadNode ();
381                 if (other.position < 0)
382                         other.LoadNode ();
383
384                 Regex digits = new Regex (@"([\d]+)|([^\d]+)");
385                 MatchEvaluator eval = delegate (Match m) {
386                         return (m.Value.Length > 0 && char.IsDigit (m.Value [0])) 
387                                 ? m.Value.PadLeft (System.Math.Max (caption.Length, other.caption.Length)) 
388                                 : m.Value;
389                 };
390                 return digits.Replace (caption, eval).CompareTo (digits.Replace (other.caption, eval));
391         }
392 }
393
394 //
395 // The HelpSource class keeps track of the archived data, and its
396 // tree
397 //
398 public class HelpSource {
399         static int id;
400         public static bool use_css = false;
401         public static string css_code;
402         public static string CssCode {
403                 get {
404                         if (css_code != null)
405                                 return css_code;
406
407                         System.Reflection.Assembly assembly = System.Reflection.Assembly.GetCallingAssembly ();
408                         Stream str_css = assembly.GetManifestResourceStream ("base.css");
409                         StringBuilder sb = new StringBuilder ((new StreamReader (str_css)).ReadToEnd());
410                         sb.Replace ("@@FONT_FAMILY@@", SettingsHandler.Settings.preferred_font_family);
411                         sb.Replace ("@@FONT_SIZE@@", SettingsHandler.Settings.preferred_font_size.ToString());
412                         css_code = sb.ToString ();
413                         return css_code;
414                 }
415                 set { css_code = value; }
416         }
417
418         public virtual string InlineCss {
419                 get { return CssCode; }
420         }
421
422         public virtual string InlineJavaScript {
423                 get { return null; }
424         }
425
426         public static bool FullHtml = true;
427
428         // should only be enabled by ASP.NET webdoc
429         public static bool UseWebdocCache;
430
431         //
432         // The unique ID for this HelpSource.
433         //
434         int source_id;
435         DateTime zipFileWriteTime;
436         string name;
437         TraceLevel trace_level = TraceLevel.Warning;
438         bool nozip;
439         string base_dir;
440
441         public HelpSource (string base_filename, bool create)
442         {
443                 this.name = Path.GetFileName (base_filename);
444                 tree_filename = base_filename + ".tree";
445                 zip_filename = base_filename + ".zip";
446                 base_dir = XmlDocUtils.GetCacheDirectory (base_filename);
447                 if (!UseWebdocCache && !create && Directory.Exists (base_dir)) {
448                         nozip = true;
449                 }
450
451                 if (create)
452                         SetupForOutput ();
453                 else 
454                         Tree = new Tree (this, tree_filename);
455
456                 source_id = id++;
457                 try {
458                         FileInfo fi = new FileInfo (zip_filename);
459                         zipFileWriteTime = fi.LastWriteTime;
460                 } catch {
461                         zipFileWriteTime = DateTime.Now;
462                 }
463         }
464         
465         public HelpSource() {
466                 Tree = new Tree (this, "Blah", "Blah");
467                 source_id = id++;
468         }
469
470         public DateTime ZipFileWriteTime {
471                 get {
472                         return zipFileWriteTime;
473                 }
474         }
475         
476         public int SourceID {
477                 get {
478                         return source_id;
479                 }
480         }
481         
482         public string Name {
483                 get {
484                         return name;
485                 }
486         }
487
488         public TraceLevel TraceLevel {
489                 get { return trace_level; }
490                 set { trace_level = value; }
491         }
492         
493         ZipFile zip_file;
494         
495         /// <summary>
496         ///   Returns a stream from the packaged help source archive
497         /// </summary>
498         public virtual Stream GetHelpStream (string id)
499         {
500                 if (nozip) {
501                         string path = XmlDocUtils.GetCachedFileName (base_dir, id);
502                         if (File.Exists (path))
503                                 return File.OpenRead (path);
504                         return null;
505                 }
506
507                 if (zip_file == null)
508                         zip_file = new ZipFile (zip_filename);
509
510                 ZipEntry entry = zip_file.GetEntry (id);
511                 if (entry != null)
512                         return zip_file.GetInputStream (entry);
513                 return null;
514         }
515         
516         public string GetRealPath (string file)
517         {
518                 if (zip_file == null)
519                         zip_file = new ZipFile (zip_filename);
520
521                 ZipEntry entry = zip_file.GetEntry (file);
522                 if (entry != null && entry.ExtraData != null)
523                         return ConvertToString (entry.ExtraData);
524                 return null;
525         }
526         
527         public XmlReader GetHelpXml (string id)
528         {
529                 if (nozip) {
530                         Stream s = File.OpenRead (XmlDocUtils.GetCachedFileName (base_dir, id));
531                         string url = "monodoc:///" + SourceID + "@" + System.Web.HttpUtility.UrlEncode (id) + "@";
532                         return new XmlTextReader (url, s);
533                 }
534
535                 if (zip_file == null)
536                         zip_file = new ZipFile (zip_filename);
537
538                 ZipEntry entry = zip_file.GetEntry (id);
539                 if (entry != null) {
540                         Stream s = zip_file.GetInputStream (entry);
541                         string url = "monodoc:///" + SourceID + "@" + System.Web.HttpUtility.UrlEncode (id) + "@";
542                         return new XmlTextReader (url, s);
543                 }
544                 return null;
545         }
546         
547         public virtual XmlDocument GetHelpXmlWithChanges (string id)
548         {
549                 if (nozip) {
550                         Stream s = File.OpenRead (XmlDocUtils.GetCachedFileName (base_dir, id));
551                         string url = "monodoc:///" + SourceID + "@" + System.Web.HttpUtility.UrlEncode (id) + "@";
552                         XmlReader r = new XmlTextReader (url, s);
553                         XmlDocument ret = new XmlDocument ();
554                         ret.Load (r);
555                         return ret;
556                 }
557
558                 if (zip_file == null)
559                         zip_file = new ZipFile (zip_filename);
560
561                 ZipEntry entry = zip_file.GetEntry (id);
562                 if (entry != null) {
563                         Stream s = zip_file.GetInputStream (entry);
564                         string url = "monodoc:///" + SourceID + "@" + System.Web.HttpUtility.UrlEncode (id) + "@";
565                         XmlReader r = new XmlTextReader (url, s);
566                         XmlDocument ret = new XmlDocument ();
567                         ret.Load (r);
568                         
569                         if (entry.ExtraData != null)
570                                 EditingUtils.AccountForChanges (ret, Name, ConvertToString (entry.ExtraData));
571                         
572                         return ret;
573                 }
574                 return null;    
575         }
576         
577         /// <summary>
578         ///   Get a nice, unique expression for any XPath node that you get.
579         ///   This function is used by editing to get the expression to put
580         ///   on to the file. The idea is to create an expression that is resistant
581         ///   to changes in the structure of the XML.
582         /// </summary>
583         public virtual string GetNodeXPath (XPathNavigator n)
584         {
585                 return EditingUtils.GetXPath (n.Clone ());
586         }
587         
588         public string GetEditUri (XPathNavigator n)
589         {
590                 return EditingUtils.FormatEditUri (n.BaseURI, GetNodeXPath (n));
591         }
592         
593         static string ConvertToString (byte[] data)
594         {
595                 return Encoding.UTF8.GetString(data);
596         }
597         
598         static byte[] ConvertToArray (string str)
599         {
600                 return Encoding.UTF8.GetBytes(str);
601         }
602
603         /// <summary>
604         ///   The tree that is being populated
605         /// </summary>
606         public Tree Tree;
607         public RootTree RootTree;
608
609         // Base filename used by this HelpSource.
610         string tree_filename, zip_filename;
611
612         // Used for ziping. 
613         const int buffer_size = 65536;
614         ZipOutputStream zip_output;
615         byte [] buffer;
616         
617         HelpSource (string base_filename)
618         {
619         }
620                 
621         void SetupForOutput ()
622         {
623                 Tree = new Tree (this, "", "");
624
625                 FileStream stream = File.Create (zip_filename);
626                 
627                 zip_output = new ZipOutputStream (stream);
628                 zip_output.SetLevel (9);
629
630                 buffer = new byte [buffer_size];
631         }               
632
633         /// <summary>
634         ///   Saves the tree and the archive
635         /// </summary>
636         public void Save ()
637         {
638                 Tree.Save (tree_filename);
639                 zip_output.Finish ();
640                 zip_output.Close ();
641         }
642
643         int code;
644
645         string GetNewCode ()
646         {
647                 return String.Format ("{0}", code++);
648         }
649
650         /// <summary>
651         ///   Providers call this to store a file they will need, and the return value
652         ///   is the name that was assigned to it
653         /// </summary>
654         public string PackFile (string file)
655         {
656                 string entry_name = GetNewCode ();
657                 return PackFile (file, entry_name);
658         }
659
660         public string PackFile (string file, string entry_name)
661         {
662                 using (FileStream input = File.OpenRead (file)) {
663                         PackStream (input, entry_name, file);
664                 }
665
666                 return entry_name;
667         }
668         
669         public void PackStream (Stream s, string entry_name)
670         {
671                 PackStream (s, entry_name, null);
672         }
673         
674         void PackStream (Stream s, string entry_name, string realPath)
675         {
676                 ZipEntry entry = new ZipEntry (entry_name);
677                                 
678                 if (realPath != null)
679                         entry.ExtraData = ConvertToArray (realPath);
680                 
681                 zip_output.PutNextEntry (entry);
682                 int n;
683                         
684                 while ((n = s.Read (buffer, 0, buffer_size)) > 0){
685                         zip_output.Write (buffer, 0, n);
686                 }       
687         }
688         
689         public void PackXml (string fname, XmlDocument doc, string real_path)
690         { 
691                 ZipEntry entry = new ZipEntry (fname); 
692                 if (real_path != null) 
693                         entry.ExtraData = ConvertToArray(real_path);
694
695                 zip_output.PutNextEntry (entry);
696                 XmlTextWriter xmlWriter = new XmlTextWriter (zip_output, Encoding.UTF8);
697                 doc.WriteContentTo (xmlWriter);
698                 xmlWriter.Flush ();
699         }
700         
701         public virtual void RenderPreviewDocs (XmlNode newNode, XmlWriter writer)
702         {
703                 throw new NotImplementedException ();
704         }
705         
706         public virtual string GetText (string url, out Node n)
707         {
708                 n = null;
709                 return null;
710         }
711
712         protected string GetCachedText (string url)
713         {
714                 if (!nozip)
715                         return null;
716                 string file = XmlDocUtils.GetCachedFileName (base_dir, url);
717                 if (!File.Exists (file))
718                         return null;
719                 return File.OpenText (file).ReadToEnd ();
720         }
721
722         public virtual Stream GetImage (string url)
723         {
724                 return null;
725         }
726         
727         //
728         // Default method implementation does not satisfy the request
729         //
730         public virtual string RenderTypeLookup (string prefix, string ns, string type, string member, out Node n)
731         {
732                 n = null;
733                 return null;
734         }
735
736         public virtual string RenderNamespaceLookup (string nsurl, out Node n)
737         {
738                 n = null;
739                 return null;
740         }
741
742         //
743         // Populates the index.
744         //
745         public virtual void PopulateIndex (IndexMaker index_maker)
746         {
747         }
748         
749         //
750         // Build an html document
751         //
752         public static string BuildHtml (string css, string html_code)
753         {
754                 return BuildHtml (css, null, html_code);
755         }
756
757         internal static string BuildHtml (string css, string js, string html_code) {
758                 if (!FullHtml) {
759                         return html_code;
760                 }
761                 StringWriter output = new StringWriter ();
762                 output.Write ("<html><head>");
763                 output.Write ("<style type=\"text/css\">");
764                 output.Write (CssCode);
765                 output.Write (css);
766                 output.Write ("</style>");
767
768                 System.Reflection.Assembly assembly = System.Reflection.Assembly.GetCallingAssembly ();
769                 Stream str_js = assembly.GetManifestResourceStream ("helper.js");
770                 StringBuilder sb = new StringBuilder ((new StreamReader (str_js)).ReadToEnd());
771                 output.Write ("<script type=\"text/JavaScript\">\n");
772                 output.Write (sb.ToString ());
773                 output.Write ("</script>\n");
774                 
775                 if (js != null) {
776                         output.Write ("<script type=\"text/JavaScript\">\n");
777                         output.Write (js);
778                         output.Write ("\n</script>");
779                 }
780
781                 output.Write ("</head><body>");
782                 output.Write (html_code);
783                 output.Write ("</body></html>");
784                 return output.ToString ();
785         }
786
787         //
788         // Create different Documents for adding to Lucene search index
789         // The default action is do nothing. Subclasses should add the docs
790         // 
791         public virtual void PopulateSearchableIndex (IndexWriter writer) {
792                 return;
793         }
794
795         public void Message (TraceLevel level, string format, params object[] args)
796         {
797                 if ((int) level <= (int) trace_level)
798                         Console.WriteLine (format, args);
799         }
800
801         public void Error (string format, params object[] args)
802         {
803                 Console.Error.WriteLine (format, args);
804         }
805 }
806
807 public abstract class Provider {
808         //
809         // This code is used to "tag" all the different sources
810         //
811         static short serial;
812
813         public int code;
814         
815         public Provider ()
816         {
817                 code = serial++;
818         }
819
820         public abstract void PopulateTree (Tree tree);
821
822         //
823         // Called at shutdown time after the tree has been populated to perform
824         // any fixups or final tasks.
825         //
826         public abstract void CloseTree (HelpSource hs, Tree tree);
827 }
828
829 public class RootTree : Tree {
830         string basedir;
831         
832         public static ArrayList UncompiledHelpSources = new ArrayList();
833         
834         public const int MonodocVersion = 1;
835         
836         public static RootTree LoadTree ()
837         {
838                 string basedir;
839                 string myPath = System.Reflection.Assembly.GetExecutingAssembly ().Location;
840                 string cfgFile = myPath + ".config";
841                 if (!File.Exists (cfgFile)) {
842                         basedir = ".";
843                         return LoadTree (basedir);
844                 }
845                 
846                 XmlDocument d = new XmlDocument ();
847                 d.Load (cfgFile);
848                 basedir = d.SelectSingleNode ("config/path").Attributes ["docsPath"].Value;
849                 
850                 return LoadTree (basedir);
851         }
852         
853         //
854         // Loads the tree layout
855         //
856         public static RootTree LoadTree (string basedir)
857         {
858                 XmlDocument doc = new XmlDocument ();
859
860                 RootTree root = new RootTree ();
861                 root.basedir = basedir;
862                 
863                 //
864                 // Load the layout
865                 //
866                 string layout = Path.Combine (basedir, "monodoc.xml");
867                 doc.Load (layout);
868                 XmlNodeList nodes = doc.SelectNodes ("/node/node");
869
870                 root.name_to_node ["root"] = root;
871                 root.name_to_node ["libraries"] = root;
872                 root.Populate (root, nodes);
873
874                 Node third_party = root.LookupEntryPoint ("various");
875                 if (third_party == null) {
876                         Console.Error.WriteLine ("No 'various' doc node! Check monodoc.xml!");
877                         third_party = root;
878                 }
879
880                 //
881                 // Load the sources
882                 //
883                 string sources_dir = Path.Combine (basedir, "sources");
884                 
885                 string [] files = Directory.GetFiles (sources_dir);
886                 foreach (string file in files){
887                         if (!file.EndsWith (".source"))
888                                 continue;
889
890                         doc = new XmlDocument ();
891                         try {
892                                 doc.Load (file);
893                         } catch {
894                                 Console.Error.WriteLine ("Error: Could not load source file {0}", file);
895                                 continue;
896                         }
897
898                         XmlNodeList extra_nodes = doc.SelectNodes ("/monodoc/node");
899                         if (extra_nodes.Count > 0)
900                                 root.Populate (third_party, extra_nodes);
901
902                         XmlNodeList sources = doc.SelectNodes ("/monodoc/source");
903                         if (sources == null){
904                                 Console.Error.WriteLine ("Error: No <source> section found in the {0} file", file);
905                                 continue;
906                         }
907                         foreach (XmlNode source in sources){
908                                 XmlAttribute a = source.Attributes ["provider"];
909                                 if (a == null){
910                                         Console.Error.WriteLine ("Error: no provider in <source>");
911                                         continue;
912                                 }
913                                 string provider = a.InnerText;
914                                 a = source.Attributes ["basefile"];
915                                 if (a == null){
916                                         Console.Error.WriteLine ("Error: no basefile in <source>");
917                                         continue;
918                                 }
919                                 string basefile = a.InnerText;
920                                 a = source.Attributes ["path"];
921                                 if (a == null){
922                                         Console.Error.WriteLine ("Error: no path in <source>");
923                                         continue;
924                                 }
925                                 string path = a.InnerText;
926
927                                 string basefilepath = Path.Combine (sources_dir, basefile);
928                                 HelpSource hs = GetHelpSource (provider, basefilepath);
929                                 if (hs == null)
930                                         continue;
931                                 hs.RootTree = root;
932                                 root.help_sources.Add (hs);
933                                 root.name_to_hs [path] = hs;
934
935                                 Node parent = root.LookupEntryPoint (path);
936                                 if (parent == null){
937                                         Console.Error.WriteLine ("node `{0}' is not defined on the documentation map", path);
938                                         parent = third_party;
939                                 }
940
941                                 foreach (Node n in hs.Tree.Nodes){
942                                         parent.AddNode (n);
943                                 }
944                                 parent.Sort ();
945                         }
946                 }
947                 
948                 foreach (string path in UncompiledHelpSources) {
949                         EcmaUncompiledHelpSource hs = new EcmaUncompiledHelpSource(path);
950                         hs.RootTree = root;
951                         root.help_sources.Add (hs);
952                         string epath = "extra-help-source-" + hs.Name;
953                         Node hsn = root.CreateNode (hs.Name, "root:/" + epath);
954                         root.name_to_hs [epath] = hs;
955                         hsn.EnsureNodes ();
956                         foreach (Node n in hs.Tree.Nodes){
957                                 hsn.AddNode (n);
958                         }
959                 }
960                 
961                 // Clean the tree
962                 PurgeNode(root);
963
964                 root.Sort ();
965
966                 return root;
967         }
968         
969         // Delete nodes which does not have documentaiton (source)
970         static bool PurgeNode(Node node)
971         {
972                 bool purge = false;
973                 
974                 if (!node.Documented)
975                 {
976                         ArrayList del_child = new ArrayList();
977                         //Delete node unless any child has documentation
978                         bool purged_child = false;
979                         foreach (Node child in node.Nodes)
980                         {
981                                 purged_child = PurgeNode(child);
982                                 if (purged_child) 
983                                 {
984                                         del_child.Add(child);
985                                 }
986                         }
987                                 
988                         // delete the node if all its children are to be deleted
989                         purge = (node.Nodes.Count == del_child.Count); 
990                                 
991                         // delete children
992                         foreach (Node child in del_child)
993                         {
994                                 node.DelNode(child);
995                         }
996                 }
997                 
998                 return purge;
999         }
1000                                         
1001         
1002         static HelpSource GetHelpSource (string provider, string basefilepath)
1003         {
1004                 try {
1005                         switch (provider){
1006                         case "ecma":
1007                                 return new EcmaHelpSource (basefilepath, false);
1008                         case "ecma-uncompiled":
1009                                 return new EcmaUncompiledHelpSource (basefilepath);
1010                         case "monohb":
1011                                 return new MonoHBHelpSource(basefilepath, false);
1012                         case "xhtml":
1013                                 return new XhtmlHelpSource (basefilepath, false);
1014                         case "man":
1015                                 return new ManHelpSource (basefilepath, false);
1016                         case "simple":
1017                                 return new SimpleHelpSource (basefilepath, false);
1018                         case "error":
1019                                 return new ErrorHelpSource (basefilepath, false);
1020                         case "ecmaspec":
1021                                 return new EcmaSpecHelpSource (basefilepath, false);
1022                         case "addins":
1023                                 return new AddinsHelpSource (basefilepath, false);
1024                         default:
1025                                 Console.Error.WriteLine ("Error: Unknown provider specified: {0}", provider);
1026                                 break;
1027                         }
1028                         return null;
1029                 }
1030                 catch (FileNotFoundException) {
1031                         Console.Error.WriteLine ("Error: did not find one of the files in sources/"+basefilepath);
1032                         return null;
1033                 }
1034         }
1035                 
1036
1037         //
1038         // Maintains the name to node mapping
1039         //
1040         Hashtable name_to_node = new Hashtable ();
1041         Hashtable name_to_hs = new Hashtable ();
1042         
1043         void Populate (Node parent, XmlNodeList xml_node_list)
1044         {
1045                 foreach (XmlNode xml_node in xml_node_list){
1046                         XmlAttribute e = xml_node.Attributes ["parent"];
1047                         if (e != null && name_to_node.ContainsKey (e.InnerText)) {
1048                                 Node p = (Node) name_to_node [e.InnerText];
1049                                 xml_node.Attributes.Remove (e);
1050                                 Populate (p, xml_node.SelectNodes ("."));
1051                                 continue;
1052                         }
1053                         e = xml_node.Attributes ["label"];
1054                         if (e == null){
1055                                 Console.Error.WriteLine ("`label' attribute missing in <node>");
1056                                 continue;
1057                         }
1058                         string label = e.InnerText;
1059                         e = xml_node.Attributes ["name"];
1060                         if (e == null){
1061                                 Console.Error.WriteLine ("`name' attribute missing in <node>");
1062                                 continue;
1063                         }
1064                         string name = e.InnerText;
1065
1066                         Node n = parent.LookupNode (label, "root:/" + name);
1067                         n.EnsureNodes ();
1068                         name_to_node [name] = n;
1069                         XmlNodeList children = xml_node.SelectNodes ("./node");
1070                         if (children != null)
1071                                 Populate (n, children);
1072                 }
1073         }
1074
1075         public Node LookupEntryPoint (string name)
1076         {
1077                 return (Node) name_to_node [name];
1078         }
1079         
1080         ArrayList help_sources;
1081         DateTime lastHelpSourceTime;
1082         
1083         RootTree () : base (null, "Mono Documentation", "root:")
1084         {
1085                 nodes = new ArrayList ();
1086                 help_sources = new ArrayList ();
1087                 lastHelpSourceTime = DateTime.MinValue;
1088         }
1089
1090         public DateTime LastHelpSourceTime {
1091                 get {
1092                         return lastHelpSourceTime;
1093                 }
1094         }
1095         
1096         public static bool GetNamespaceAndType (string url, out string ns, out string type)
1097         {
1098                 int nsidx = -1;
1099                 int numLt = 0;
1100                 for (int i = 0; i < url.Length; ++i) {
1101                         char c = url [i];
1102                         switch (c) {
1103                         case '<':
1104                         case '{':
1105                                 ++numLt;
1106                                 break;
1107                         case '>':
1108                         case '}':
1109                                 --numLt;
1110                                 break;
1111                         case '.':
1112                                 if (numLt == 0)
1113                                         nsidx = i;
1114                                 break;
1115                         }
1116                 }
1117
1118                 if (nsidx == -1) {
1119                         Console.Error.WriteLine ("Did not find dot in: " + url);
1120                         ns = null;
1121                         type = null;
1122                         return false;
1123                 }
1124                 ns = url.Substring (0, nsidx);
1125                 type = url.Substring (nsidx + 1);
1126                 
1127                 //Console.Error.WriteLine ("GetNameSpaceAndType (ns={0}, type={1}", ns, type);
1128                 return true;
1129         }
1130
1131         public XmlDocument GetHelpXml (string url)
1132         {
1133                 string rest = url.Substring (2);
1134                 string ns, type;
1135
1136                 if (!GetNamespaceAndType (rest, out ns, out type))
1137                         return null;
1138
1139                 foreach (HelpSource hs in help_sources) {
1140                         EcmaHelpSource ehs = hs as EcmaHelpSource;
1141                         if (ehs == null)
1142                                 continue;
1143                         string id = ehs.GetIdFromUrl ("T:", ns, type);
1144                         if (id == null)
1145                                 continue;
1146                         XmlDocument doc = hs.GetHelpXmlWithChanges (id);
1147                         if (doc != null)
1148                                 return doc;
1149                 }
1150                 return null;
1151         }
1152         
1153         public string TypeLookup (string url, out Node match_node)
1154         {
1155                 string rest = Regex.Replace (url, @"^T:\s*", "");
1156                 string ns, type;
1157
1158                 if (!GetNamespaceAndType (rest, out ns, out type)){
1159                         match_node = null;
1160                         return null;
1161                 }
1162                 
1163                 foreach (HelpSource hs in help_sources){
1164                         string s = hs.RenderTypeLookup ("T:", ns, type, null, out match_node);
1165                         
1166                         if (s != null) {
1167                                 lastHelpSourceTime = hs.ZipFileWriteTime;
1168                                 return s;
1169                         }
1170                 }
1171                 match_node = null;
1172                 return null;
1173         }
1174
1175         public string MemberLookup (string prefix, string url, out Node match_node)
1176         {
1177                 string rest = Regex.Replace (url, @"^.:\s*", "");
1178                 
1179                 // Dots in the arg list (for methods) confuse this.
1180                 // Chop off the arg list for now and put it back later.
1181                 string arglist = "";
1182                 int argliststart = rest.IndexOf("(");
1183                 if (argliststart >= 0) {
1184                         arglist = rest.Substring(argliststart);
1185                         rest = rest.Substring(0, argliststart);
1186                 }
1187
1188                 string ns_type, member;
1189         
1190                 if (prefix != "C:") {
1191                         int member_idx = rest.LastIndexOf (".");
1192         
1193                         // The dot in .ctor (if it's a M: link) would confuse this.
1194                         if (rest.EndsWith("..ctor")) member_idx--;
1195         
1196                         ns_type = rest.Substring (0, member_idx);
1197                         member = rest.Substring (member_idx + 1);
1198                 } else {
1199                         // C: links don't have the .ctor member part as it would in a M: link
1200                         // Even though externally C: links are different from M: links,
1201                         // C: links get transformed into M:-style links (with .ctor) here.
1202                         ns_type = rest;
1203                         member = ".ctor";
1204                 }
1205  
1206
1207                 //Console.WriteLine ("NS_TYPE: {0}  MEMBER: {1}", ns_type, member);
1208
1209                 string ns, type;
1210                 if (!GetNamespaceAndType (ns_type, out ns, out type)){
1211                         match_node = null;
1212                         return null;
1213                 }
1214                 
1215                 foreach (HelpSource hs in help_sources){
1216                         string s = hs.RenderTypeLookup (prefix, ns, type, member + arglist, out match_node);
1217                         
1218                         if (s != null) {
1219                                 lastHelpSourceTime = hs.ZipFileWriteTime;
1220                                 return s;
1221                         }
1222                 }
1223                 match_node = null;
1224                 return null;
1225         }
1226
1227         public Stream GetImage (string url)
1228         {
1229                 if (url.StartsWith ("source-id:")){
1230                         string rest = url.Substring (10);
1231                         int p = rest.IndexOf (":");
1232                         string str_idx = rest.Substring (0, p);
1233                         int idx = 0;
1234
1235                         try {
1236                                 idx = Int32.Parse (str_idx);
1237                         } catch {
1238                                 Console.Error.WriteLine ("Failed to parse source-id url: {0} `{1}'", url, str_idx);
1239                                 return null;
1240                         }
1241
1242                         HelpSource hs = GetHelpSourceFromId (idx);
1243                         lastHelpSourceTime = hs.ZipFileWriteTime;
1244                         return hs.GetImage (rest.Substring (p + 1));
1245                 } else {
1246                         System.Reflection.Assembly assembly = System.Reflection.Assembly.GetAssembly (typeof(RootTree));                        
1247                         return assembly.GetManifestResourceStream (url);
1248                 }
1249                 lastHelpSourceTime = DateTime.MinValue;
1250                 return null;
1251         }
1252         
1253         public HelpSource GetHelpSourceFromId (int id)
1254         {
1255                 return (HelpSource) help_sources [id];
1256         }
1257         
1258         string home_cache;
1259         /// <summary>
1260         ///    Allows every HelpSource to try to provide the content for this
1261         ///    URL.
1262         /// </summary>
1263         public string RenderUrl (string url, out Node match_node)
1264         {
1265                 lastHelpSourceTime = DateTime.MinValue;
1266                 if (url == "root:") {
1267                         match_node = this;
1268
1269                         // look whether there are contribs
1270                         GlobalChangeset chgs = EditingUtils.changes;
1271                         StringBuilder con = new StringBuilder ();
1272                         
1273                         //add links to the contrib
1274                         int oldContrib = 0, contribs = 0;
1275                         con.Append ("<ul>");
1276                         foreach (DocSetChangeset dscs in chgs.DocSetChangesets) 
1277                                 foreach (FileChangeset fcs in dscs.FileChangesets) 
1278                                         foreach (Change c in fcs.Changes) {
1279                                                 if (c.NodeUrl == null) {
1280                                                         if (c.Serial == SettingsHandler.Settings.SerialNumber)
1281                                                                 oldContrib++;
1282                                                 } else if (c.Serial == SettingsHandler.Settings.SerialNumber) {
1283                                                         contribs++;
1284                                                         con.Append (String.Format ("<li><a href=\"{0}\">{0}</a></li>", c.NodeUrl));
1285                                                 }
1286                                         }
1287                         
1288                         string contrib = (oldContrib + contribs) == 1?"There is {0} contribution":"There are {0} contributions";
1289                         con.Insert (0, String.Format (contrib, oldContrib + contribs) + " pending upload <i>(Contributing--&gt; Upload)</i>", 1);
1290                         con.Append ("</ul>");
1291                         if (oldContrib == 1)
1292                                 con.Append ("<i>You have 1 contribution that is not listed below that will be sent the next time you upload contributions. Only contributions made from now on will be listed.</i>");
1293                         else if (oldContrib > 1)
1294                                 con.Append ("<i>You have " + oldContrib + "contributions that are not listed below and will be sent the next time you upload contributions. Only contributions made from now on will be listed.</i>");
1295
1296                         //start the rendering
1297                         if (!HelpSource.use_css) {
1298                                 StringBuilder sb = new StringBuilder ("<table bgcolor=\"#b0c4de\" width=\"100%\" cellpadding=\"5\"><tr><td><h3>Mono Documentation Library</h3></td></tr></table>");
1299                         
1300                                 foreach (Node n in Nodes)
1301                                         sb.AppendFormat ("<a href='{0}'>{1}</a><br/>", n.Element, n.Caption);
1302                         
1303                                 //contributions
1304                                 sb.Append ("<br><table bgcolor=\"#fff3f3\" width=\"100%\" cellpadding=\"5\"><tr><td>");
1305                                 sb.Append ("<h5>Contributions</h5><br>");
1306                                 if ((oldContrib + contribs) == 0) {
1307                                         sb.Append ("<p><b>You have not made any contributions yet.</b></p>");
1308                                         sb.Append ("<p>The Documentation of the libraries is not complete and your contributions would be greatly appreciated. The procedure is easy, browse to the part of the documentation you want to contribute to and click on the <font color=\"blue\">[Edit]</font> link to start writing the documentation.</p>");
1309                                         sb.Append ("<p>When you are happy with your changes, use the Contributing--&gt; Upload Contributions menu to send your contributions to our server.</p></div>");
1310                                 } else {
1311                                         sb.Append (con.ToString ());
1312                                 }
1313                                 sb.Append ("</td></tr></table>");
1314                                 return sb.ToString ();  
1315                         } else {
1316                                 if (home_cache == null) {
1317                                         System.Reflection.Assembly assembly = System.Reflection.Assembly.GetAssembly (typeof (HelpSource));
1318                                         Stream hp_stream = assembly.GetManifestResourceStream ("home.html");
1319                                         home_cache = (new StreamReader (hp_stream)).ReadToEnd ();
1320                                 }
1321                                 StringBuilder sb = new StringBuilder (home_cache);
1322                                 // adjust fonts
1323                                 sb.Replace ("@@FONT_FAMILY@@", SettingsHandler.Settings.preferred_font_family);
1324                                 sb.Replace ("@@FONT_SIZE@@", SettingsHandler.Settings.preferred_font_size.ToString());
1325                                 //contributions
1326                                 if ((oldContrib + contribs) == 0) {
1327                                         sb.Replace ("@@CONTRIB_DISP@@", "display: none;");
1328                                 } else {
1329                                         sb.Replace ("@@NO_CONTRIB_DISP@@", "display: none;");
1330                                         sb.Replace ("@@CONTRIBS@@", con.ToString ());
1331                                 }
1332                                         
1333                                 // load the url of nodes
1334                                 String add_str;
1335                                 StringBuilder urls = new StringBuilder ();
1336                                 foreach (Node n in Nodes) {
1337                                         add_str = String.Format ("<li><a href=\"{0}\">{1}</a></li>", n.Element, n.Caption);
1338                                         urls.Append (add_str);
1339                                 }
1340                                 sb.Replace ("@@API_DOCS@@", urls.ToString ());
1341                                                 
1342                                 return sb.ToString ();
1343                         }
1344                 } 
1345                 
1346                 if (url.StartsWith ("root:")) {
1347                         match_node = ((Node)name_to_node [url.Substring (6)]);
1348                         HelpSource hs = ((HelpSource)name_to_hs [url.Substring (6)]);
1349                         if (hs == null) 
1350                         {
1351                                 return GenerateNodeIndex(match_node);
1352                         }
1353                                 
1354                         Node dummy;
1355                         lastHelpSourceTime = hs.ZipFileWriteTime;
1356                         return hs.GetText ("root:", out dummy);
1357                 }
1358         
1359                 
1360                 if (url.StartsWith ("source-id:")){
1361                         string rest = url.Substring (10);
1362                         int p = rest.IndexOf (":");
1363                         string str_idx = rest.Substring (0, p);
1364                         int idx = 0;
1365
1366                         try {
1367                                 idx = Int32.Parse (str_idx);
1368                         } catch {
1369                                 Console.Error.WriteLine ("Failed to parse source-id url: {0} `{1}'", url, str_idx);
1370                                 match_node = null;
1371                                 return null;
1372                         }
1373                         HelpSource hs = (HelpSource) help_sources [idx];
1374                         // Console.WriteLine ("Attempting to get docs from: " + rest.Substring (p + 1));
1375                         lastHelpSourceTime = hs.ZipFileWriteTime;
1376                         return hs.GetText (rest.Substring (p + 1), out match_node);
1377                 }
1378
1379                 if (url.Length < 2){
1380                         match_node = null;
1381                         return null;
1382                 }
1383                 
1384                 string prefix = url.Substring (0, 2);
1385                 
1386                 switch (prefix.ToUpper ()){
1387                 case "N:":
1388                         foreach (HelpSource hs in help_sources){
1389                                 string s = hs.RenderNamespaceLookup (url, out match_node);
1390                                 if (s != null) {
1391                                         lastHelpSourceTime = hs.ZipFileWriteTime;
1392                                         return s;
1393                                 }
1394                         }
1395                         match_node = null;
1396                         return null;
1397
1398                 case "T:":
1399                         return TypeLookup (url, out match_node);
1400
1401                 case "M:":
1402                 case "F:":
1403                 case "P:":
1404                 case "E:":
1405                 case "C:":
1406                 case "O:":
1407                         return MemberLookup (prefix, url, out match_node);
1408                 
1409                 default:
1410                         foreach (HelpSource hs in help_sources){
1411                                 string s = hs.GetText (url, out match_node);
1412                                 
1413                                 if (s != null) {
1414                                         lastHelpSourceTime = hs.ZipFileWriteTime;
1415                                         return s;
1416                                 }
1417                         }
1418                         match_node = null;
1419                         return null;
1420                 }
1421         }
1422         
1423         public string GenerateNodeIndex (Node node)
1424         {
1425                 StringBuilder buf = new StringBuilder();
1426                 buf.AppendFormat("<H3>{0}</H3>", node.Caption);
1427                 buf.Append("<ul>");
1428                 foreach (Node child in node.Nodes)
1429                 {
1430                         buf.AppendFormat("<li><a href=\"{0}\">{1}</a>", child.URL, child.Caption);
1431                 }
1432                 buf.Append("</ul>");
1433                 return buf.ToString();
1434         }
1435         
1436         public IndexReader GetIndex ()
1437         {
1438                 //try to load from basedir
1439                 string index_file = Path.Combine (basedir, "monodoc.index");
1440                 if (File.Exists (index_file))
1441                         return IndexReader.Load (index_file);
1442                 //then, try to load from config dir
1443                 index_file = Path.Combine (SettingsHandler.Path, "monodoc.index");
1444                 return IndexReader.Load (index_file);
1445                 
1446         }
1447
1448         public static void MakeIndex ()
1449         {
1450                 RootTree root = LoadTree ();
1451                 if (root == null)
1452                         return;
1453
1454                 IndexMaker index_maker = new IndexMaker ();
1455                 
1456                 foreach (HelpSource hs in root.help_sources){
1457                         hs.PopulateIndex (index_maker);
1458                 }
1459
1460                 // if the user has no write permissions use config dir
1461                 string path = Path.Combine (root.basedir, "monodoc.index");
1462                 try {
1463                         index_maker.Save (path);
1464                 } catch (System.UnauthorizedAccessException) {
1465                         path = Path.Combine (SettingsHandler.Path, "monodoc.index");
1466                         try {
1467                                 index_maker.Save (path);
1468                         } catch (System.UnauthorizedAccessException) {
1469                                 Console.WriteLine ("Unable to write index file in {0}", Path.Combine (SettingsHandler.Path, "monodoc.index")); 
1470                                 return;
1471                         }
1472                 }
1473
1474                 if (IsUnix){
1475                         // No octal in C#, how lame is that
1476                         chmod (path, 0x1a4);
1477                 }
1478                 Console.WriteLine ("Documentation index updated");
1479         }
1480
1481         static bool IsUnix {
1482                 get {
1483                         int p = (int) Environment.OSVersion.Platform;
1484                         return ((p == 4) || (p == 128) || (p == 6));
1485                 }
1486         }
1487
1488         // Search Index
1489         public SearchableIndex GetSearchIndex ()
1490         {
1491                 //try to load from basedir
1492                 string index_file = Path.Combine (basedir, "search_index");
1493                 if (Directory.Exists (index_file))
1494                         return SearchableIndex.Load (index_file);
1495                 //then, try to load from config dir
1496                 index_file = Path.Combine (SettingsHandler.Path, "search_index");
1497                 return SearchableIndex.Load (index_file);
1498         }
1499
1500         public static void MakeSearchIndex ()
1501         {
1502                 // Loads the RootTree
1503                 Console.WriteLine ("Loading the monodoc tree...");
1504                 RootTree root = LoadTree ();
1505                 if (root == null)
1506                         return;
1507
1508                 string dir = Path.Combine (root.basedir, "search_index");
1509                 IndexWriter writer;
1510                 //try to create the dir to store the index
1511                 try {
1512                         if (!Directory.Exists (dir)) 
1513                                 Directory.CreateDirectory (dir);
1514
1515                         writer = new IndexWriter(Lucene.Net.Store.FSDirectory.GetDirectory(dir, true), new StandardAnalyzer(), true);
1516                 } catch (UnauthorizedAccessException) {
1517                         //try in the .config directory
1518                         try {
1519                                 dir = Path.Combine (SettingsHandler.Path, "search_index");
1520                                 if (!Directory.Exists (dir)) 
1521                                         Directory.CreateDirectory (dir);
1522
1523                                 writer = new IndexWriter(Lucene.Net.Store.FSDirectory.GetDirectory(dir, true), new StandardAnalyzer(), true);
1524                         } catch (UnauthorizedAccessException) {
1525                                 Console.WriteLine ("You don't have permissions to write on " + dir);
1526                                 return;
1527                         }
1528                 }
1529
1530                 //Collect all the documents
1531                 Console.WriteLine ("Collecting and adding documents...");
1532                 foreach (HelpSource hs in root.HelpSources) 
1533                         hs.PopulateSearchableIndex (writer);
1534         
1535                 //Optimize and close
1536                 Console.WriteLine ("Closing...");
1537                 writer.Optimize();
1538                 writer.Close();
1539         }
1540
1541
1542         public ICollection HelpSources { get { return new ArrayList(help_sources); } }
1543
1544         [System.Runtime.InteropServices.DllImport ("libc")]
1545         static extern int chmod (string filename, int mode);
1546 }
1547 }