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