[monodoc] Removed debug spew
[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 Mono.Lucene.Net.Index;
31 using Mono.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         public string BaseDir {
506                 get {
507                         return base_dir;
508                 }
509         }
510         
511         ZipFile zip_file;
512         
513         /// <summary>
514         ///   Returns a stream from the packaged help source archive
515         /// </summary>
516         public virtual Stream GetHelpStream (string id)
517         {
518                 if (nozip) {
519                         string path = XmlDocUtils.GetCachedFileName (base_dir, id);
520                         if (File.Exists (path))
521                                 return File.OpenRead (path);
522                         return null;
523                 }
524
525                 if (zip_file == null)
526                         zip_file = new ZipFile (zip_filename);
527
528                 ZipEntry entry = zip_file.GetEntry (id);
529                 if (entry != null)
530                         return zip_file.GetInputStream (entry);
531                 return null;
532         }
533         
534         public string GetRealPath (string file)
535         {
536                 if (zip_file == null)
537                         zip_file = new ZipFile (zip_filename);
538
539                 ZipEntry entry = zip_file.GetEntry (file);
540                 if (entry != null && entry.ExtraData != null)
541                         return ConvertToString (entry.ExtraData);
542                 return null;
543         }
544         
545         public XmlReader GetHelpXml (string id)
546         {
547                 if (nozip) {
548                         Stream s = File.OpenRead (XmlDocUtils.GetCachedFileName (base_dir, id));
549                         string url = "monodoc:///" + SourceID + "@" + System.Web.HttpUtility.UrlEncode (id) + "@";
550                         return new XmlTextReader (url, s);
551                 }
552
553                 if (zip_file == null)
554                         zip_file = new ZipFile (zip_filename);
555
556                 ZipEntry entry = zip_file.GetEntry (id);
557                 if (entry != null) {
558                         Stream s = zip_file.GetInputStream (entry);
559                         string url = "monodoc:///" + SourceID + "@" + System.Web.HttpUtility.UrlEncode (id) + "@";
560                         return new XmlTextReader (url, s);
561                 }
562                 return null;
563         }
564         
565         public virtual XmlDocument GetHelpXmlWithChanges (string id)
566         {
567                 if (nozip) {
568                         Stream s = File.OpenRead (XmlDocUtils.GetCachedFileName (base_dir, id));
569                         string url = "monodoc:///" + SourceID + "@" + System.Web.HttpUtility.UrlEncode (id) + "@";
570                         XmlReader r = new XmlTextReader (url, s);
571                         XmlDocument ret = new XmlDocument ();
572                         ret.Load (r);
573                         return ret;
574                 }
575
576                 if (zip_file == null)
577                         zip_file = new ZipFile (zip_filename);
578
579                 ZipEntry entry = zip_file.GetEntry (id);
580                 if (entry != null) {
581                         Stream s = zip_file.GetInputStream (entry);
582                         string url = "monodoc:///" + SourceID + "@" + System.Web.HttpUtility.UrlEncode (id) + "@";
583                         XmlReader r = new XmlTextReader (url, s);
584                         XmlDocument ret = new XmlDocument ();
585                         ret.Load (r);
586                         
587                         if (entry.ExtraData != null)
588                                 EditingUtils.AccountForChanges (ret, Name, ConvertToString (entry.ExtraData));
589                         
590                         return ret;
591                 }
592                 return null;    
593         }
594         
595         /// <summary>
596         ///   Get a nice, unique expression for any XPath node that you get.
597         ///   This function is used by editing to get the expression to put
598         ///   on to the file. The idea is to create an expression that is resistant
599         ///   to changes in the structure of the XML.
600         /// </summary>
601         public virtual string GetNodeXPath (XPathNavigator n)
602         {
603                 return EditingUtils.GetXPath (n.Clone ());
604         }
605         
606         public string GetEditUri (XPathNavigator n)
607         {
608                 return EditingUtils.FormatEditUri (n.BaseURI, GetNodeXPath (n));
609         }
610         
611         static string ConvertToString (byte[] data)
612         {
613                 return Encoding.UTF8.GetString(data);
614         }
615         
616         static byte[] ConvertToArray (string str)
617         {
618                 return Encoding.UTF8.GetBytes(str);
619         }
620
621         /// <summary>
622         ///   The tree that is being populated
623         /// </summary>
624         public Tree Tree;
625         public RootTree RootTree;
626
627         // Base filename used by this HelpSource.
628         string tree_filename, zip_filename;
629
630         // Used for ziping. 
631         const int buffer_size = 65536;
632         ZipOutputStream zip_output;
633         byte [] buffer;
634         
635         HelpSource (string base_filename)
636         {
637         }
638                 
639         void SetupForOutput ()
640         {
641                 Tree = new Tree (this, "", "");
642
643                 FileStream stream = File.Create (zip_filename);
644                 
645                 zip_output = new ZipOutputStream (stream);
646                 zip_output.SetLevel (9);
647
648                 buffer = new byte [buffer_size];
649         }               
650
651         /// <summary>
652         ///   Saves the tree and the archive
653         /// </summary>
654         public void Save ()
655         {
656                 Tree.Save (tree_filename);
657                 zip_output.Finish ();
658                 zip_output.Close ();
659         }
660
661         int code;
662
663         string GetNewCode ()
664         {
665                 return String.Format ("{0}", code++);
666         }
667
668         /// <summary>
669         ///   Providers call this to store a file they will need, and the return value
670         ///   is the name that was assigned to it
671         /// </summary>
672         public string PackFile (string file)
673         {
674                 string entry_name = GetNewCode ();
675                 return PackFile (file, entry_name);
676         }
677
678         public string PackFile (string file, string entry_name)
679         {
680                 using (FileStream input = File.OpenRead (file)) {
681                         PackStream (input, entry_name, file);
682                 }
683
684                 return entry_name;
685         }
686         
687         public void PackStream (Stream s, string entry_name)
688         {
689                 PackStream (s, entry_name, null);
690         }
691         
692         void PackStream (Stream s, string entry_name, string realPath)
693         {
694                 ZipEntry entry = new ZipEntry (entry_name);
695                                 
696                 if (realPath != null)
697                         entry.ExtraData = ConvertToArray (realPath);
698                 
699                 zip_output.PutNextEntry (entry);
700                 int n;
701                         
702                 while ((n = s.Read (buffer, 0, buffer_size)) > 0){
703                         zip_output.Write (buffer, 0, n);
704                 }       
705         }
706         
707         public void PackXml (string fname, XmlDocument doc, string real_path)
708         { 
709                 ZipEntry entry = new ZipEntry (fname); 
710                 if (real_path != null) 
711                         entry.ExtraData = ConvertToArray(real_path);
712
713                 zip_output.PutNextEntry (entry);
714                 XmlTextWriter xmlWriter = new XmlTextWriter (zip_output, Encoding.UTF8);
715                 doc.WriteContentTo (xmlWriter);
716                 xmlWriter.Flush ();
717         }
718         
719         public virtual void RenderPreviewDocs (XmlNode newNode, XmlWriter writer)
720         {
721                 throw new NotImplementedException ();
722         }
723
724         public virtual string GetPublicUrl (string id)
725         {
726                 return id;
727         }
728         
729         public virtual string GetText (string url, out Node n)
730         {
731                 n = null;
732                 return null;
733         }
734
735         protected string GetCachedText (string url)
736         {
737                 if (!nozip)
738                         return null;
739                 string file = XmlDocUtils.GetCachedFileName (base_dir, url);
740                 if (!File.Exists (file))
741                         return null;
742                 return File.OpenText (file).ReadToEnd ();
743         }
744
745         public virtual Stream GetImage (string url)
746         {
747                 return null;
748         }
749         
750         //
751         // Default method implementation does not satisfy the request
752         //
753         public virtual string RenderTypeLookup (string prefix, string ns, string type, string member, out Node n)
754         {
755                 n = null;
756                 return null;
757         }
758
759         public virtual string RenderNamespaceLookup (string nsurl, out Node n)
760         {
761                 n = null;
762                 return null;
763         }
764
765         //
766         // Populates the index.
767         //
768         public virtual void PopulateIndex (IndexMaker index_maker)
769         {
770         }
771         
772         //
773         // Build an html document
774         //
775         public static string BuildHtml (string css, string html_code)
776         {
777                 return BuildHtml (css, null, html_code);
778         }
779
780         internal static string BuildHtml (string css, string js, string html_code) {
781                 if (!FullHtml) {
782                         return html_code;
783                 }
784                 StringWriter output = new StringWriter ();
785                 output.Write ("<html><head>");
786                 output.Write ("<style type=\"text/css\">");
787                 output.Write (CssCode);
788                 output.Write (css);
789                 output.Write ("</style>");
790
791                 System.Reflection.Assembly assembly = System.Reflection.Assembly.GetCallingAssembly ();
792                 Stream str_js = assembly.GetManifestResourceStream ("helper.js");
793                 StringBuilder sb = new StringBuilder ((new StreamReader (str_js)).ReadToEnd());
794                 output.Write ("<script type=\"text/JavaScript\">\n");
795                 output.Write (sb.ToString ());
796                 output.Write ("</script>\n");
797                 
798                 if (js != null) {
799                         output.Write ("<script type=\"text/JavaScript\">\n");
800                         output.Write (js);
801                         output.Write ("\n</script>");
802                 }
803
804                 output.Write ("</head><body>");
805                 output.Write (html_code);
806                 output.Write ("</body></html>");
807                 return output.ToString ();
808         }
809
810         //
811         // Create different Documents for adding to Lucene search index
812         // The default action is do nothing. Subclasses should add the docs
813         // 
814         public virtual void PopulateSearchableIndex (IndexWriter writer) {
815                 return;
816         }
817
818         public void Message (TraceLevel level, string format, params object[] args)
819         {
820                 if ((int) level <= (int) trace_level)
821                         Console.WriteLine (format, args);
822         }
823
824         public void Error (string format, params object[] args)
825         {
826                 Console.Error.WriteLine (format, args);
827         }
828 }
829
830 public abstract class Provider {
831         //
832         // This code is used to "tag" all the different sources
833         //
834         static short serial;
835
836         public int code;
837         
838         public Provider ()
839         {
840                 code = serial++;
841         }
842
843         public abstract void PopulateTree (Tree tree);
844
845         //
846         // Called at shutdown time after the tree has been populated to perform
847         // any fixups or final tasks.
848         //
849         public abstract void CloseTree (HelpSource hs, Tree tree);
850 }
851
852 public class RootTree : Tree {
853         string basedir;
854         
855         public static ArrayList UncompiledHelpSources = new ArrayList();
856         
857         public const int MonodocVersion = 1;
858         
859         public static RootTree LoadTree ()
860         {
861                 return LoadTree (null);
862         }
863
864         const string MacMonoDocDir = "/Library/Frameworks/Mono.framework/Versions/Current/lib/monodoc";
865         
866         //
867         // Loads the tree layout
868         //
869         public static RootTree LoadTree (string basedir)
870         {
871                 if (basedir == null) {
872                         string myPath = System.Reflection.Assembly.GetExecutingAssembly ().Location;
873                         string cfgFile = myPath + ".config";
874                         if (!File.Exists (cfgFile)) {
875                                 basedir = ".";
876                         }
877                         else {
878                                 XmlDocument d = new XmlDocument ();
879                                 d.Load (cfgFile);
880                                 basedir = d.SelectSingleNode ("config/path").Attributes ["docsPath"].Value;
881                         }
882                         // Temporary workaround for developers distributing a monodoc.dll themselves on Mac
883                         if (Directory.Exists (MacMonoDocDir)){
884                                 Console.WriteLine ("MacDir exists");
885                                 if (!File.Exists (Path.Combine (basedir, "monodoc.xml"))){
886                                         basedir = MacMonoDocDir;
887                                 }
888                         }
889                 }
890                 Console.WriteLine ("Basedir={0}", basedir);
891
892                 //
893                 // Load the layout
894                 //
895                 XmlDocument doc = new XmlDocument ();
896                 string layout = Path.Combine (basedir, "monodoc.xml");
897                 doc.Load (layout);
898
899                 string osxExternalDir = "/Library/Frameworks/Mono.framework/External/monodoc";
900                 string[] osxExternalSources = Directory.Exists (osxExternalDir)
901                         ? Directory.GetFiles (osxExternalDir, "*.source")
902                         : new string[0];
903
904                 return LoadTree (basedir, doc, 
905                                 Directory.GetFiles (Path.Combine (basedir, "sources"), "*.source")
906                                 .Concat (osxExternalSources));
907         }
908
909
910         // Compatibility shim w/ Mono 2.6
911         public static RootTree LoadTree (string indexDir, XmlDocument docTree, IEnumerable sourceFiles)
912         {
913                 return LoadTree (indexDir, docTree, sourceFiles.Cast<string>());
914         }
915
916         public static RootTree LoadTree (string indexDir, XmlDocument docTree, IEnumerable<string> sourceFiles)
917         {
918                 if (docTree == null) {
919                         docTree = new XmlDocument ();
920                         using (var defTree = typeof(RootTree).Assembly.GetManifestResourceStream ("monodoc.xml"))
921                                 docTree.Load (defTree);
922                 }
923
924
925                 sourceFiles = sourceFiles ?? new string [0];
926
927                 //
928                 // Load the layout
929                 //
930
931                 RootTree root = new RootTree ();
932                 root.basedir = indexDir;
933
934                 XmlNodeList nodes = docTree.SelectNodes ("/node/node");
935
936                 root.name_to_node ["root"] = root;
937                 root.name_to_node ["libraries"] = root;
938                 root.Populate (root, nodes);
939
940                 Node third_party = root.LookupEntryPoint ("various");
941                 if (third_party == null) {
942                         Console.Error.WriteLine ("No 'various' doc node! Check monodoc.xml!");
943                         third_party = root;
944                 }
945
946                 //
947                 // Load the sources
948                 //
949                 foreach (var sourceFile in sourceFiles){
950                         root.AddSourceFile (sourceFile);
951                 }
952                 
953                 foreach (string path in UncompiledHelpSources) {
954                         EcmaUncompiledHelpSource hs = new EcmaUncompiledHelpSource(path);
955                         hs.RootTree = root;
956                         root.help_sources.Add (hs);
957                         string epath = "extra-help-source-" + hs.Name;
958                         Node hsn = root.CreateNode (hs.Name, "root:/" + epath);
959                         root.name_to_hs [epath] = hs;
960                         hsn.EnsureNodes ();
961                         foreach (Node n in hs.Tree.Nodes){
962                                 hsn.AddNode (n);
963                         }
964                 }
965                 
966                 // Clean the tree
967                 PurgeNode(root);
968
969                 root.Sort ();
970
971                 return root;
972         }
973
974         public void AddSource (string sources_dir)
975         {
976                 string [] files = Directory.GetFiles (sources_dir);
977
978                 foreach (string file in files){
979                         if (!file.EndsWith (".source"))
980                                 continue;
981                         AddSourceFile (file);
982                 }
983         }
984
985         Dictionary<string,string> loadedSourceFiles = new Dictionary<string,string> ();
986         
987         public void AddSourceFile (string sourceFile)
988         {
989                 if (loadedSourceFiles.ContainsKey (sourceFile))
990                         return;
991                 
992                 Node third_party = LookupEntryPoint ("various") ?? this;
993
994                 XmlDocument doc = new XmlDocument ();
995                 try {
996                         doc.Load (sourceFile);
997                 }
998                 catch {
999                         Console.Error.WriteLine ("Error: Could not load source file {0}", sourceFile);
1000                         return;
1001                 }
1002
1003                 XmlNodeList extra_nodes = doc.SelectNodes ("/monodoc/node");
1004                 if (extra_nodes.Count > 0)
1005                         Populate (third_party, extra_nodes);
1006
1007                 XmlNodeList sources = doc.SelectNodes ("/monodoc/source");
1008                 if (sources == null){
1009                         Console.Error.WriteLine ("Error: No <source> section found in the {0} file", sourceFile);
1010                         return;
1011                 }
1012                 loadedSourceFiles [sourceFile] = sourceFile;
1013                 foreach (XmlNode source in sources){
1014                         XmlAttribute a = source.Attributes ["provider"];
1015                         if (a == null){
1016                                 Console.Error.WriteLine ("Error: no provider in <source>");
1017                                 continue;
1018                         }
1019                         string provider = a.InnerText;
1020                         a = source.Attributes ["basefile"];
1021                         if (a == null){
1022                                 Console.Error.WriteLine ("Error: no basefile in <source>");
1023                                 continue;
1024                         }
1025                         string basefile = a.InnerText;
1026                         a = source.Attributes ["path"];
1027                         if (a == null){
1028                                 Console.Error.WriteLine ("Error: no path in <source>");
1029                                 continue;
1030                         }
1031                         string path = a.InnerText;
1032
1033                         string basefilepath = Path.Combine (Path.GetDirectoryName (sourceFile), basefile);
1034                         HelpSource hs = GetHelpSource (provider, basefilepath);
1035                         if (hs == null)
1036                                 continue;
1037                         hs.RootTree = this;
1038                         help_sources.Add (hs);
1039                         name_to_hs [path] = hs;
1040
1041                         Node parent = LookupEntryPoint (path);
1042                         if (parent == null){
1043                                 Console.Error.WriteLine ("node `{0}' is not defined on the documentation map", path);
1044                                 parent = third_party;
1045                         }
1046
1047                         foreach (Node n in hs.Tree.Nodes){
1048                                 parent.AddNode (n);
1049                         }
1050                         parent.Sort ();
1051                 }
1052         }
1053         
1054         // Delete nodes which does not have documentaiton (source)
1055         static bool PurgeNode(Node node)
1056         {
1057                 bool purge = false;
1058                 
1059                 if (!node.Documented)
1060                 {
1061                         ArrayList del_child = new ArrayList();
1062                         //Delete node unless any child has documentation
1063                         bool purged_child = false;
1064                         foreach (Node child in node.Nodes)
1065                         {
1066                                 purged_child = PurgeNode(child);
1067                                 if (purged_child) 
1068                                 {
1069                                         del_child.Add(child);
1070                                 }
1071                         }
1072                                 
1073                         // delete the node if all its children are to be deleted
1074                         purge = (node.Nodes.Count == del_child.Count); 
1075                                 
1076                         // delete children
1077                         foreach (Node child in del_child)
1078                         {
1079                                 node.DelNode(child);
1080                         }
1081                 }
1082                 
1083                 return purge;
1084         }
1085
1086         public static string[] GetSupportedFormats ()
1087         {
1088                 return new string[]{
1089                         "ecma", 
1090                         "ecmaspec", 
1091                         "error", 
1092                         "hb", 
1093                         "man", 
1094                         "simple", 
1095                         "xhtml"
1096                 };
1097         }
1098         
1099         public static HelpSource GetHelpSource (string provider, string basefilepath)
1100         {
1101                 try {
1102                         switch (provider){
1103                         case "ecma":
1104                                 return new EcmaHelpSource (basefilepath, false);
1105                         case "ecma-uncompiled":
1106                                 return new EcmaUncompiledHelpSource (basefilepath);
1107                         case "monohb":
1108                                 return new MonoHBHelpSource(basefilepath, false);
1109                         case "xhtml": case "hb":
1110                                 return new XhtmlHelpSource (basefilepath, false);
1111                         case "man":
1112                                 return new ManHelpSource (basefilepath, false);
1113                         case "simple":
1114                                 return new SimpleHelpSource (basefilepath, false);
1115                         case "error":
1116                                 return new ErrorHelpSource (basefilepath, false);
1117                         case "ecmaspec":
1118                                 return new EcmaSpecHelpSource (basefilepath, false);
1119                         case "addins":
1120                                 return new AddinsHelpSource (basefilepath, false);
1121                         default:
1122                                 Console.Error.WriteLine ("Error: Unknown provider specified: {0}", provider);
1123                                 break;
1124                         }
1125                         return null;
1126                 }
1127                 catch (FileNotFoundException) {
1128                         Console.Error.WriteLine ("Error: did not find one of the files in sources/"+basefilepath);
1129                         return null;
1130                 }
1131         }
1132
1133         public static Provider GetProvider (string provider, params string[] basefilepaths)
1134         {
1135                 switch (provider) {
1136                 case "addins":
1137                         return new AddinsProvider (basefilepaths [0]);
1138                 case "ecma": {
1139                         EcmaProvider p = new EcmaProvider ();
1140                         foreach (string d in basefilepaths)
1141                                 p.AddDirectory (d);
1142                         return p;
1143                 }
1144                 case "ecmaspec":
1145                         return new EcmaSpecProvider (basefilepaths [0]);
1146                 case "error":
1147                         return new ErrorProvider (basefilepaths [0]);
1148                 case "man":
1149                         return new ManProvider (basefilepaths);
1150                 case "simple":
1151                         return new SimpleProvider (basefilepaths [0]);
1152                 case "xhtml":
1153                 case "hb":
1154                         return new XhtmlProvider (basefilepaths [0]);
1155                 default:
1156                         throw new NotSupportedException (provider);
1157                 }
1158         }
1159
1160         //
1161         // Maintains the name to node mapping
1162         //
1163         Hashtable name_to_node = new Hashtable ();
1164         Hashtable name_to_hs = new Hashtable ();
1165         
1166         void Populate (Node parent, XmlNodeList xml_node_list)
1167         {
1168                 foreach (XmlNode xml_node in xml_node_list){
1169                         XmlAttribute e = xml_node.Attributes ["parent"];
1170                         if (e != null && name_to_node.ContainsKey (e.InnerText)) {
1171                                 Node p = (Node) name_to_node [e.InnerText];
1172                                 xml_node.Attributes.Remove (e);
1173                                 Populate (p, xml_node.SelectNodes ("."));
1174                                 continue;
1175                         }
1176                         e = xml_node.Attributes ["label"];
1177                         if (e == null){
1178                                 Console.Error.WriteLine ("`label' attribute missing in <node>");
1179                                 continue;
1180                         }
1181                         string label = e.InnerText;
1182                         e = xml_node.Attributes ["name"];
1183                         if (e == null){
1184                                 Console.Error.WriteLine ("`name' attribute missing in <node>");
1185                                 continue;
1186                         }
1187                         string name = e.InnerText;
1188
1189                         Node n = parent.LookupNode (label, "root:/" + name);
1190                         n.EnsureNodes ();
1191                         name_to_node [name] = n;
1192                         XmlNodeList children = xml_node.SelectNodes ("./node");
1193                         if (children != null)
1194                                 Populate (n, children);
1195                 }
1196         }
1197
1198         public Node LookupEntryPoint (string name)
1199         {
1200                 return (Node) name_to_node [name];
1201         }
1202         
1203         ArrayList help_sources;
1204         DateTime lastHelpSourceTime;
1205         
1206         RootTree () : base (null, "Mono Documentation", "root:")
1207         {
1208                 nodes = new ArrayList ();
1209                 help_sources = new ArrayList ();
1210                 lastHelpSourceTime = DateTime.MinValue;
1211         }
1212
1213         public DateTime LastHelpSourceTime {
1214                 get {
1215                         return lastHelpSourceTime;
1216                 }
1217         }
1218         
1219         public static bool GetNamespaceAndType (string url, out string ns, out string type)
1220         {
1221                 int nsidx = -1;
1222                 int numLt = 0;
1223                 for (int i = 0; i < url.Length; ++i) {
1224                         char c = url [i];
1225                         switch (c) {
1226                         case '<':
1227                         case '{':
1228                                 ++numLt;
1229                                 break;
1230                         case '>':
1231                         case '}':
1232                                 --numLt;
1233                                 break;
1234                         case '.':
1235                                 if (numLt == 0)
1236                                         nsidx = i;
1237                                 break;
1238                         }
1239                 }
1240
1241                 if (nsidx == -1) {
1242                         ns = null;
1243                         type = null;
1244                         return false;
1245                 }
1246                 ns = url.Substring (0, nsidx);
1247                 type = url.Substring (nsidx + 1);
1248                 
1249                 //Console.Error.WriteLine ("GetNameSpaceAndType (ns={0}, type={1}", ns, type);
1250                 return true;
1251         }
1252
1253         public XmlDocument GetHelpXml (string url)
1254         {
1255                 string rest = url.Substring (2);
1256                 string ns, type;
1257
1258                 if (!GetNamespaceAndType (rest, out ns, out type))
1259                         return null;
1260
1261                 foreach (HelpSource hs in help_sources) {
1262                         EcmaHelpSource ehs = hs as EcmaHelpSource;
1263                         if (ehs == null)
1264                                 continue;
1265                         string id = ehs.GetIdFromUrl ("T:", ns, type);
1266                         if (id == null)
1267                                 continue;
1268                         XmlDocument doc = hs.GetHelpXmlWithChanges (id);
1269                         if (doc != null)
1270                                 return doc;
1271                 }
1272                 return null;
1273         }
1274         
1275         public string TypeLookup (string url, out Node match_node)
1276         {
1277                 string rest = Regex.Replace (url, @"^T:\s*", "");
1278                 string ns, type;
1279
1280                 if (!GetNamespaceAndType (rest, out ns, out type)){
1281                         match_node = null;
1282                         return null;
1283                 }
1284                 
1285                 foreach (HelpSource hs in help_sources){
1286                         string s = hs.RenderTypeLookup ("T:", ns, type, null, out match_node);
1287                         
1288                         if (s != null) {
1289                                 lastHelpSourceTime = hs.ZipFileWriteTime;
1290                                 return s;
1291                         }
1292                 }
1293                 match_node = null;
1294                 return null;
1295         }
1296
1297         public string MemberLookup (string prefix, string url, out Node match_node)
1298         {
1299                 string rest = Regex.Replace (url, @"^.:\s*", "");
1300                 
1301                 // Dots in the arg list (for methods) confuse this.
1302                 // Chop off the arg list for now and put it back later.
1303                 string arglist = "";
1304                 int argliststart = rest.IndexOf("(");
1305                 if (argliststart >= 0) {
1306                         arglist = rest.Substring(argliststart);
1307                         rest = rest.Substring(0, argliststart);
1308                 }
1309
1310                 string ns_type, member;
1311         
1312                 if (prefix != "C:") {
1313                         int member_idx = rest.LastIndexOf (".");
1314         
1315                         // The dot in .ctor (if it's a M: link) would confuse this.
1316                         if (rest.EndsWith("..ctor")) member_idx--;
1317         
1318                         ns_type = rest.Substring (0, member_idx);
1319                         member = rest.Substring (member_idx + 1);
1320                 } else {
1321                         // C: links don't have the .ctor member part as it would in a M: link
1322                         // Even though externally C: links are different from M: links,
1323                         // C: links get transformed into M:-style links (with .ctor) here.
1324                         ns_type = rest;
1325                         member = ".ctor";
1326                 }
1327  
1328
1329                 //Console.WriteLine ("NS_TYPE: {0}  MEMBER: {1}", ns_type, member);
1330
1331                 string ns, type;
1332                 if (!GetNamespaceAndType (ns_type, out ns, out type)){
1333                         match_node = null;
1334                         return null;
1335                 }
1336                 
1337                 foreach (HelpSource hs in help_sources){
1338                         string s = hs.RenderTypeLookup (prefix, ns, type, member + arglist, out match_node);
1339                         
1340                         if (s != null) {
1341                                 lastHelpSourceTime = hs.ZipFileWriteTime;
1342                                 return s;
1343                         }
1344                 }
1345                 match_node = null;
1346                 return null;
1347         }
1348
1349         public Stream GetImage (string url)
1350         {
1351                 if (url.StartsWith ("source-id:")){
1352                         string rest = url.Substring (10);
1353                         int p = rest.IndexOf (":");
1354                         string str_idx = rest.Substring (0, p);
1355                         int idx = 0;
1356
1357                         try {
1358                                 idx = Int32.Parse (str_idx);
1359                         } catch {
1360                                 Console.Error.WriteLine ("Failed to parse source-id url: {0} `{1}'", url, str_idx);
1361                                 return null;
1362                         }
1363
1364                         HelpSource hs = GetHelpSourceFromId (idx);
1365                         lastHelpSourceTime = hs.ZipFileWriteTime;
1366                         return hs.GetImage (rest.Substring (p + 1));
1367                 } else {
1368                         System.Reflection.Assembly assembly = System.Reflection.Assembly.GetAssembly (typeof(RootTree));                        
1369                         return assembly.GetManifestResourceStream (url);
1370                 }
1371                 lastHelpSourceTime = DateTime.MinValue;
1372                 return null;
1373         }
1374         
1375         public HelpSource GetHelpSourceFromId (int id)
1376         {
1377                 return (HelpSource) help_sources [id];
1378         }
1379         
1380         //
1381         // Fetches the node title
1382         //
1383         public string GetTitle (string url)
1384         {
1385                 Node match_node;
1386
1387                 if (url == null || url.StartsWith ("root:"))
1388                         return "Mono Documentation";
1389                 
1390                 if (url.Length > 2 && url [1] == ':'){
1391                         switch (url [0]){
1392                         case 'N':
1393                                 return url.Substring (2) + " Namespace";
1394
1395                         case 'T':
1396                                 string s = TypeLookup (url, out match_node);
1397                                 if (match_node != null)
1398                                         return match_node.Caption;
1399                                 return url.Substring (2) + " type";
1400
1401                 case 'M':
1402                 case 'F':
1403                 case 'P':
1404                 case 'E':
1405                 case 'C':
1406                 case 'O':
1407                         MemberLookup (url.Substring (0,2), url, out match_node);
1408                         if (match_node != null)
1409                                 return match_node.Caption;
1410                         break;
1411                         }
1412                 }
1413                 
1414                 return "Mono Documentation";
1415         }
1416         
1417         string home_cache;
1418         /// <summary>
1419         ///    Allows every HelpSource to try to provide the content for this
1420         ///    URL.
1421         /// </summary>
1422         public string RenderUrl (string url, out Node match_node)
1423         {
1424                 lastHelpSourceTime = DateTime.MinValue;
1425                 if (url == "root:") {
1426                         match_node = this;
1427
1428                         // look whether there are contribs
1429                         GlobalChangeset chgs = EditingUtils.changes;
1430                         StringBuilder con = new StringBuilder ();
1431                         
1432                         //add links to the contrib
1433                         int oldContrib = 0, contribs = 0;
1434                         con.Append ("<ul>");
1435                         foreach (DocSetChangeset dscs in chgs.DocSetChangesets) 
1436                                 foreach (FileChangeset fcs in dscs.FileChangesets) 
1437                                         foreach (Change c in fcs.Changes) {
1438                                                 if (c.NodeUrl == null) {
1439                                                         if (c.Serial == SettingsHandler.Settings.SerialNumber)
1440                                                                 oldContrib++;
1441                                                 } else if (c.Serial == SettingsHandler.Settings.SerialNumber) {
1442                                                         contribs++;
1443                                                         con.Append (String.Format ("<li><a href=\"{0}\">{0}</a></li>", c.NodeUrl));
1444                                                 }
1445                                         }
1446                         
1447                         string contrib = (oldContrib + contribs) == 1?"There is {0} contribution":"There are {0} contributions";
1448                         con.Insert (0, String.Format (contrib, oldContrib + contribs) + " pending upload <i>(Contributing--&gt; Upload)</i>", 1);
1449                         con.Append ("</ul>");
1450                         if (oldContrib == 1)
1451                                 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>");
1452                         else if (oldContrib > 1)
1453                                 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>");
1454
1455                         //start the rendering
1456                         if (!HelpSource.use_css) {
1457                                 StringBuilder sb = new StringBuilder ("<table bgcolor=\"#b0c4de\" width=\"100%\" cellpadding=\"5\"><tr><td><h3>Mono Documentation Library</h3></td></tr></table>");
1458                         
1459                                 foreach (Node n in Nodes)
1460                                         sb.AppendFormat ("<a href='{0}'>{1}</a><br/>", n.Element, n.Caption);
1461                         
1462                                 //contributions
1463                                 sb.Append ("<br><table bgcolor=\"#fff3f3\" width=\"100%\" cellpadding=\"5\"><tr><td>");
1464                                 sb.Append ("<h5>Contributions</h5><br>");
1465                                 if ((oldContrib + contribs) == 0) {
1466                                         sb.Append ("<p><b>You have not made any contributions yet.</b></p>");
1467                                         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>");
1468                                         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>");
1469                                 } else {
1470                                         sb.Append (con.ToString ());
1471                                 }
1472                                 sb.Append ("</td></tr></table>");
1473                                 return sb.ToString ();  
1474                         } else {
1475                                 if (home_cache == null) {
1476                                         System.Reflection.Assembly assembly = System.Reflection.Assembly.GetAssembly (typeof (HelpSource));
1477                                         Stream hp_stream = assembly.GetManifestResourceStream ("home.html");
1478                                         home_cache = (new StreamReader (hp_stream)).ReadToEnd ();
1479                                 }
1480                                 StringBuilder sb = new StringBuilder (home_cache);
1481                                 // adjust fonts
1482                                 sb.Replace ("@@FONT_FAMILY@@", SettingsHandler.Settings.preferred_font_family);
1483                                 sb.Replace ("@@FONT_SIZE@@", SettingsHandler.Settings.preferred_font_size.ToString());
1484                                 //contributions
1485                                 var visible = SettingsHandler.Settings.EnableEditing ? "block;" : "none;";
1486                                 if ((oldContrib + contribs) == 0) {
1487                                         sb.Replace ("@@CONTRIB_DISP@@", "display: none;");
1488                                         sb.Replace ("@@NO_CONTRIB_DISP@@", "display: " + visible);
1489                                 } else {
1490                                         sb.Replace ("@@CONTRIB_DISP@@", "display: " + visible);
1491                                         sb.Replace ("@@NO_CONTRIB_DISP@@", "display: none;");
1492                                         sb.Replace ("@@CONTRIBS@@", con.ToString ());
1493                                 }
1494                                 sb.Replace ("@@EDITING_ENABLED@@", "display: " + visible);
1495                                         
1496                                 // load the url of nodes
1497                                 String add_str;
1498                                 StringBuilder urls = new StringBuilder ();
1499                                 foreach (Node n in Nodes) {
1500                                         add_str = String.Format ("<li><a href=\"{0}\">{1}</a></li>", n.Element, n.Caption);
1501                                         urls.Append (add_str);
1502                                 }
1503                                 sb.Replace ("@@API_DOCS@@", urls.ToString ());
1504                                                 
1505                                 return sb.ToString ();
1506                         }
1507                 } 
1508                 
1509                 if (url.StartsWith ("root:")) {
1510                         match_node = ((Node)name_to_node [url.Substring (6)]);
1511                         HelpSource hs = ((HelpSource)name_to_hs [url.Substring (6)]);
1512                         if (hs == null) 
1513                         {
1514                                 return GenerateNodeIndex(match_node);
1515                         }
1516                                 
1517                         Node dummy;
1518                         lastHelpSourceTime = hs.ZipFileWriteTime;
1519                         return hs.GetText ("root:", out dummy);
1520                 }
1521         
1522                 
1523                 if (url.StartsWith ("source-id:")){
1524                         string rest = url.Substring (10);
1525                         int p = rest.IndexOf (":");
1526                         string str_idx = rest.Substring (0, p);
1527                         int idx = 0;
1528
1529                         try {
1530                                 idx = Int32.Parse (str_idx);
1531                         } catch {
1532                                 Console.Error.WriteLine ("Failed to parse source-id url: {0} `{1}'", url, str_idx);
1533                                 match_node = null;
1534                                 return null;
1535                         }
1536                         HelpSource hs = (HelpSource) help_sources [idx];
1537                         // Console.WriteLine ("Attempting to get docs from: " + rest.Substring (p + 1));
1538                         lastHelpSourceTime = hs.ZipFileWriteTime;
1539                         return hs.GetText (rest.Substring (p + 1), out match_node);
1540                 }
1541
1542                 if (url.Length < 2){
1543                         match_node = null;
1544                         return null;
1545                 }
1546                 
1547                 string prefix = url.Substring (0, 2);
1548                 
1549                 switch (prefix.ToUpper ()){
1550                 case "N:":
1551                         foreach (HelpSource hs in help_sources){
1552                                 string s = hs.RenderNamespaceLookup (url, out match_node);
1553                                 if (s != null) {
1554                                         lastHelpSourceTime = hs.ZipFileWriteTime;
1555                                         return s;
1556                                 }
1557                         }
1558                         match_node = null;
1559                         return null;
1560
1561                 case "T:":
1562                         return TypeLookup (url, out match_node);
1563
1564                 case "M:":
1565                 case "F:":
1566                 case "P:":
1567                 case "E:":
1568                 case "C:":
1569                 case "O:":
1570                         return MemberLookup (prefix, url, out match_node);
1571                 
1572                 default:
1573                         foreach (HelpSource hs in help_sources){
1574                                 string s = hs.GetText (url, out match_node);
1575                                 
1576                                 if (s != null) {
1577                                         lastHelpSourceTime = hs.ZipFileWriteTime;
1578                                         return s;
1579                                 }
1580                         }
1581                         match_node = null;
1582                         return null;
1583                 }
1584         }
1585         
1586         public string GenerateNodeIndex (Node node)
1587         {
1588                 StringBuilder buf = new StringBuilder();
1589                 buf.AppendFormat("<H3>{0}</H3>", node.Caption);
1590                 buf.Append("<ul>");
1591                 foreach (Node child in node.Nodes)
1592                 {
1593                         buf.AppendFormat("<li><a href=\"{0}\">{1}</a>", child.URL, child.Caption);
1594                 }
1595                 buf.Append("</ul>");
1596                 return buf.ToString();
1597         }
1598         
1599         public IndexReader GetIndex ()
1600         {
1601                 //try to load from basedir
1602                 string index_file = Path.Combine (basedir, "monodoc.index");
1603                 if (File.Exists (index_file))
1604                         return IndexReader.Load (index_file);
1605                 //then, try to load from config dir
1606                 index_file = Path.Combine (SettingsHandler.Path, "monodoc.index");
1607                 return IndexReader.Load (index_file);
1608                 
1609         }
1610
1611         public static void MakeIndex ()
1612         {
1613                 MakeIndex (LoadTree ());
1614         }
1615
1616         public static void MakeIndex (RootTree root)
1617         {
1618                 if (root == null)
1619                         return;
1620
1621                 IndexMaker index_maker = new IndexMaker ();
1622                 
1623                 foreach (HelpSource hs in root.help_sources){
1624                         hs.PopulateIndex (index_maker);
1625                 }
1626
1627                 // if the user has no write permissions use config dir
1628                 string path = Path.Combine (root.basedir, "monodoc.index");
1629                 try {
1630                         index_maker.Save (path);
1631                 } catch (System.UnauthorizedAccessException) {
1632                         path = Path.Combine (SettingsHandler.Path, "monodoc.index");
1633                         try {
1634                                 index_maker.Save (path);
1635                         } catch (System.UnauthorizedAccessException) {
1636                                 Console.WriteLine ("Unable to write index file in {0}", Path.Combine (SettingsHandler.Path, "monodoc.index")); 
1637                                 return;
1638                         }
1639                 }
1640
1641                 if (IsUnix){
1642                         // No octal in C#, how lame is that
1643                         chmod (path, 0x1a4);
1644                 }
1645                 Console.WriteLine ("Documentation index updated");
1646         }
1647
1648         static bool IsUnix {
1649                 get {
1650                         int p = (int) Environment.OSVersion.Platform;
1651                         return ((p == 4) || (p == 128) || (p == 6));
1652                 }
1653         }
1654
1655         // Search Index
1656         public SearchableIndex GetSearchIndex ()
1657         {
1658                 //try to load from basedir
1659                 string index_file = Path.Combine (basedir, "search_index");
1660                 if (Directory.Exists (index_file))
1661                         return SearchableIndex.Load (index_file);
1662                 //then, try to load from config dir
1663                 index_file = Path.Combine (SettingsHandler.Path, "search_index");
1664                 return SearchableIndex.Load (index_file);
1665         }
1666
1667         public static void MakeSearchIndex ()
1668         {
1669                 MakeSearchIndex (LoadTree ());
1670         }
1671
1672         public static void MakeSearchIndex (RootTree root)
1673         {
1674                 // Loads the RootTree
1675                 Console.WriteLine ("Loading the monodoc tree...");
1676
1677                 if (root == null)
1678                         return;
1679
1680                 string dir = Path.Combine (root.basedir, "search_index");
1681                 IndexWriter writer;
1682                 //try to create the dir to store the index
1683                 try {
1684                         if (!Directory.Exists (dir)) 
1685                                 Directory.CreateDirectory (dir);
1686
1687                         writer = new IndexWriter(Mono.Lucene.Net.Store.FSDirectory.GetDirectory(dir, true), new StandardAnalyzer(), true);
1688                 } catch (UnauthorizedAccessException) {
1689                         //try in the .config directory
1690                         try {
1691                                 dir = Path.Combine (SettingsHandler.Path, "search_index");
1692                                 if (!Directory.Exists (dir)) 
1693                                         Directory.CreateDirectory (dir);
1694
1695                                 writer = new IndexWriter(Mono.Lucene.Net.Store.FSDirectory.GetDirectory(dir, true), new StandardAnalyzer(), true);
1696                         } catch (UnauthorizedAccessException) {
1697                                 Console.WriteLine ("You don't have permissions to write on " + dir);
1698                                 return;
1699                         }
1700                 }
1701
1702                 //Collect all the documents
1703                 Console.WriteLine ("Collecting and adding documents...");
1704                 foreach (HelpSource hs in root.HelpSources) 
1705                         hs.PopulateSearchableIndex (writer);
1706         
1707                 //Optimize and close
1708                 Console.WriteLine ("Closing...");
1709                 writer.Optimize();
1710                 writer.Close();
1711         }
1712
1713
1714         public ICollection HelpSources { get { return new ArrayList(help_sources); } }
1715
1716         [System.Runtime.InteropServices.DllImport ("libc")]
1717         static extern int chmod (string filename, int mode);
1718 }
1719 }