[monodoc/osx] Look for docs in additional directories.
[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                 sourceFiles = sourceFiles ?? new string [0];
910
911                 //
912                 // Load the layout
913                 //
914
915                 RootTree root = new RootTree ();
916                 root.basedir = indexDir;
917
918                 XmlNodeList nodes = docTree.SelectNodes ("/node/node");
919
920                 root.name_to_node ["root"] = root;
921                 root.name_to_node ["libraries"] = root;
922                 root.Populate (root, nodes);
923
924                 Node third_party = root.LookupEntryPoint ("various");
925                 if (third_party == null) {
926                         Console.Error.WriteLine ("No 'various' doc node! Check monodoc.xml!");
927                         third_party = root;
928                 }
929
930                 //
931                 // Load the sources
932                 //
933                 foreach (var sourceFile in sourceFiles)
934                         root.AddSourceFile (sourceFile);
935                 
936                 foreach (string path in UncompiledHelpSources) {
937                         EcmaUncompiledHelpSource hs = new EcmaUncompiledHelpSource(path);
938                         hs.RootTree = root;
939                         root.help_sources.Add (hs);
940                         string epath = "extra-help-source-" + hs.Name;
941                         Node hsn = root.CreateNode (hs.Name, "root:/" + epath);
942                         root.name_to_hs [epath] = hs;
943                         hsn.EnsureNodes ();
944                         foreach (Node n in hs.Tree.Nodes){
945                                 hsn.AddNode (n);
946                         }
947                 }
948                 
949                 // Clean the tree
950                 PurgeNode(root);
951
952                 root.Sort ();
953
954                 return root;
955         }
956
957         public void AddSource (string sources_dir)
958         {
959                 string [] files = Directory.GetFiles (sources_dir);
960
961                 foreach (string file in files){
962                         if (!file.EndsWith (".source"))
963                                 continue;
964                         AddSourceFile (file);
965                 }
966         }
967
968         public void AddSourceFile (string sourceFile)
969         {
970                 Node third_party = LookupEntryPoint ("various") ?? this;
971
972                 XmlDocument doc = new XmlDocument ();
973                 try {
974                         doc.Load (sourceFile);
975                 }
976                 catch {
977                         Console.Error.WriteLine ("Error: Could not load source file {0}", sourceFile);
978                         return;
979                 }
980
981                 XmlNodeList extra_nodes = doc.SelectNodes ("/monodoc/node");
982                 if (extra_nodes.Count > 0)
983                         Populate (third_party, extra_nodes);
984
985                 XmlNodeList sources = doc.SelectNodes ("/monodoc/source");
986                 if (sources == null){
987                         Console.Error.WriteLine ("Error: No <source> section found in the {0} file", sourceFile);
988                         return;
989                 }
990                 foreach (XmlNode source in sources){
991                         XmlAttribute a = source.Attributes ["provider"];
992                         if (a == null){
993                                 Console.Error.WriteLine ("Error: no provider in <source>");
994                                 continue;
995                         }
996                         string provider = a.InnerText;
997                         a = source.Attributes ["basefile"];
998                         if (a == null){
999                                 Console.Error.WriteLine ("Error: no basefile in <source>");
1000                                 continue;
1001                         }
1002                         string basefile = a.InnerText;
1003                         a = source.Attributes ["path"];
1004                         if (a == null){
1005                                 Console.Error.WriteLine ("Error: no path in <source>");
1006                                 continue;
1007                         }
1008                         string path = a.InnerText;
1009
1010                         string basefilepath = Path.Combine (Path.GetDirectoryName (sourceFile), basefile);
1011                         HelpSource hs = GetHelpSource (provider, basefilepath);
1012                         if (hs == null)
1013                                 continue;
1014                         hs.RootTree = this;
1015                         help_sources.Add (hs);
1016                         name_to_hs [path] = hs;
1017
1018                         Node parent = LookupEntryPoint (path);
1019                         if (parent == null){
1020                                 Console.Error.WriteLine ("node `{0}' is not defined on the documentation map", path);
1021                                 parent = third_party;
1022                         }
1023
1024                         foreach (Node n in hs.Tree.Nodes){
1025                                 parent.AddNode (n);
1026                         }
1027                         parent.Sort ();
1028                 }
1029         }
1030         
1031         // Delete nodes which does not have documentaiton (source)
1032         static bool PurgeNode(Node node)
1033         {
1034                 bool purge = false;
1035                 
1036                 if (!node.Documented)
1037                 {
1038                         ArrayList del_child = new ArrayList();
1039                         //Delete node unless any child has documentation
1040                         bool purged_child = false;
1041                         foreach (Node child in node.Nodes)
1042                         {
1043                                 purged_child = PurgeNode(child);
1044                                 if (purged_child) 
1045                                 {
1046                                         del_child.Add(child);
1047                                 }
1048                         }
1049                                 
1050                         // delete the node if all its children are to be deleted
1051                         purge = (node.Nodes.Count == del_child.Count); 
1052                                 
1053                         // delete children
1054                         foreach (Node child in del_child)
1055                         {
1056                                 node.DelNode(child);
1057                         }
1058                 }
1059                 
1060                 return purge;
1061         }
1062
1063         public static string[] GetSupportedFormats ()
1064         {
1065                 return new string[]{
1066                         "ecma", 
1067                         "ecmaspec", 
1068                         "error", 
1069                         "hb", 
1070                         "man", 
1071                         "simple", 
1072                         "xhtml"
1073                 };
1074         }
1075         
1076         public static HelpSource GetHelpSource (string provider, string basefilepath)
1077         {
1078                 try {
1079                         switch (provider){
1080                         case "ecma":
1081                                 return new EcmaHelpSource (basefilepath, false);
1082                         case "ecma-uncompiled":
1083                                 return new EcmaUncompiledHelpSource (basefilepath);
1084                         case "monohb":
1085                                 return new MonoHBHelpSource(basefilepath, false);
1086                         case "xhtml": case "hb":
1087                                 return new XhtmlHelpSource (basefilepath, false);
1088                         case "man":
1089                                 return new ManHelpSource (basefilepath, false);
1090                         case "simple":
1091                                 return new SimpleHelpSource (basefilepath, false);
1092                         case "error":
1093                                 return new ErrorHelpSource (basefilepath, false);
1094                         case "ecmaspec":
1095                                 return new EcmaSpecHelpSource (basefilepath, false);
1096                         case "addins":
1097                                 return new AddinsHelpSource (basefilepath, false);
1098                         default:
1099                                 Console.Error.WriteLine ("Error: Unknown provider specified: {0}", provider);
1100                                 break;
1101                         }
1102                         return null;
1103                 }
1104                 catch (FileNotFoundException) {
1105                         Console.Error.WriteLine ("Error: did not find one of the files in sources/"+basefilepath);
1106                         return null;
1107                 }
1108         }
1109
1110         public static Provider GetProvider (string provider, params string[] basefilepaths)
1111         {
1112                 switch (provider) {
1113                 case "addins":
1114                         return new AddinsProvider (basefilepaths [0]);
1115                 case "ecma": {
1116                         EcmaProvider p = new EcmaProvider ();
1117                         foreach (string d in basefilepaths)
1118                                 p.AddDirectory (d);
1119                         return p;
1120                 }
1121                 case "ecmaspec":
1122                         return new EcmaSpecProvider (basefilepaths [0]);
1123                 case "error":
1124                         return new ErrorProvider (basefilepaths [0]);
1125                 case "man":
1126                         return new ManProvider (basefilepaths);
1127                 case "simple":
1128                         return new SimpleProvider (basefilepaths [0]);
1129                 case "xhtml":
1130                 case "hb":
1131                         return new XhtmlProvider (basefilepaths [0]);
1132                 default:
1133                         throw new NotSupportedException (provider);
1134                 }
1135         }
1136
1137         //
1138         // Maintains the name to node mapping
1139         //
1140         Hashtable name_to_node = new Hashtable ();
1141         Hashtable name_to_hs = new Hashtable ();
1142         
1143         void Populate (Node parent, XmlNodeList xml_node_list)
1144         {
1145                 foreach (XmlNode xml_node in xml_node_list){
1146                         XmlAttribute e = xml_node.Attributes ["parent"];
1147                         if (e != null && name_to_node.ContainsKey (e.InnerText)) {
1148                                 Node p = (Node) name_to_node [e.InnerText];
1149                                 xml_node.Attributes.Remove (e);
1150                                 Populate (p, xml_node.SelectNodes ("."));
1151                                 continue;
1152                         }
1153                         e = xml_node.Attributes ["label"];
1154                         if (e == null){
1155                                 Console.Error.WriteLine ("`label' attribute missing in <node>");
1156                                 continue;
1157                         }
1158                         string label = e.InnerText;
1159                         e = xml_node.Attributes ["name"];
1160                         if (e == null){
1161                                 Console.Error.WriteLine ("`name' attribute missing in <node>");
1162                                 continue;
1163                         }
1164                         string name = e.InnerText;
1165
1166                         Node n = parent.LookupNode (label, "root:/" + name);
1167                         n.EnsureNodes ();
1168                         name_to_node [name] = n;
1169                         XmlNodeList children = xml_node.SelectNodes ("./node");
1170                         if (children != null)
1171                                 Populate (n, children);
1172                 }
1173         }
1174
1175         public Node LookupEntryPoint (string name)
1176         {
1177                 return (Node) name_to_node [name];
1178         }
1179         
1180         ArrayList help_sources;
1181         DateTime lastHelpSourceTime;
1182         
1183         RootTree () : base (null, "Mono Documentation", "root:")
1184         {
1185                 nodes = new ArrayList ();
1186                 help_sources = new ArrayList ();
1187                 lastHelpSourceTime = DateTime.MinValue;
1188         }
1189
1190         public DateTime LastHelpSourceTime {
1191                 get {
1192                         return lastHelpSourceTime;
1193                 }
1194         }
1195         
1196         public static bool GetNamespaceAndType (string url, out string ns, out string type)
1197         {
1198                 int nsidx = -1;
1199                 int numLt = 0;
1200                 for (int i = 0; i < url.Length; ++i) {
1201                         char c = url [i];
1202                         switch (c) {
1203                         case '<':
1204                         case '{':
1205                                 ++numLt;
1206                                 break;
1207                         case '>':
1208                         case '}':
1209                                 --numLt;
1210                                 break;
1211                         case '.':
1212                                 if (numLt == 0)
1213                                         nsidx = i;
1214                                 break;
1215                         }
1216                 }
1217
1218                 if (nsidx == -1) {
1219                         Console.Error.WriteLine ("Did not find dot in: " + url);
1220                         ns = null;
1221                         type = null;
1222                         return false;
1223                 }
1224                 ns = url.Substring (0, nsidx);
1225                 type = url.Substring (nsidx + 1);
1226                 
1227                 //Console.Error.WriteLine ("GetNameSpaceAndType (ns={0}, type={1}", ns, type);
1228                 return true;
1229         }
1230
1231         public XmlDocument GetHelpXml (string url)
1232         {
1233                 string rest = url.Substring (2);
1234                 string ns, type;
1235
1236                 if (!GetNamespaceAndType (rest, out ns, out type))
1237                         return null;
1238
1239                 foreach (HelpSource hs in help_sources) {
1240                         EcmaHelpSource ehs = hs as EcmaHelpSource;
1241                         if (ehs == null)
1242                                 continue;
1243                         string id = ehs.GetIdFromUrl ("T:", ns, type);
1244                         if (id == null)
1245                                 continue;
1246                         XmlDocument doc = hs.GetHelpXmlWithChanges (id);
1247                         if (doc != null)
1248                                 return doc;
1249                 }
1250                 return null;
1251         }
1252         
1253         public string TypeLookup (string url, out Node match_node)
1254         {
1255                 string rest = Regex.Replace (url, @"^T:\s*", "");
1256                 string ns, type;
1257
1258                 if (!GetNamespaceAndType (rest, out ns, out type)){
1259                         match_node = null;
1260                         return null;
1261                 }
1262                 
1263                 foreach (HelpSource hs in help_sources){
1264                         string s = hs.RenderTypeLookup ("T:", ns, type, null, out match_node);
1265                         
1266                         if (s != null) {
1267                                 lastHelpSourceTime = hs.ZipFileWriteTime;
1268                                 return s;
1269                         }
1270                 }
1271                 match_node = null;
1272                 return null;
1273         }
1274
1275         public string MemberLookup (string prefix, string url, out Node match_node)
1276         {
1277                 string rest = Regex.Replace (url, @"^.:\s*", "");
1278                 
1279                 // Dots in the arg list (for methods) confuse this.
1280                 // Chop off the arg list for now and put it back later.
1281                 string arglist = "";
1282                 int argliststart = rest.IndexOf("(");
1283                 if (argliststart >= 0) {
1284                         arglist = rest.Substring(argliststart);
1285                         rest = rest.Substring(0, argliststart);
1286                 }
1287
1288                 string ns_type, member;
1289         
1290                 if (prefix != "C:") {
1291                         int member_idx = rest.LastIndexOf (".");
1292         
1293                         // The dot in .ctor (if it's a M: link) would confuse this.
1294                         if (rest.EndsWith("..ctor")) member_idx--;
1295         
1296                         ns_type = rest.Substring (0, member_idx);
1297                         member = rest.Substring (member_idx + 1);
1298                 } else {
1299                         // C: links don't have the .ctor member part as it would in a M: link
1300                         // Even though externally C: links are different from M: links,
1301                         // C: links get transformed into M:-style links (with .ctor) here.
1302                         ns_type = rest;
1303                         member = ".ctor";
1304                 }
1305  
1306
1307                 //Console.WriteLine ("NS_TYPE: {0}  MEMBER: {1}", ns_type, member);
1308
1309                 string ns, type;
1310                 if (!GetNamespaceAndType (ns_type, out ns, out type)){
1311                         match_node = null;
1312                         return null;
1313                 }
1314                 
1315                 foreach (HelpSource hs in help_sources){
1316                         string s = hs.RenderTypeLookup (prefix, ns, type, member + arglist, out match_node);
1317                         
1318                         if (s != null) {
1319                                 lastHelpSourceTime = hs.ZipFileWriteTime;
1320                                 return s;
1321                         }
1322                 }
1323                 match_node = null;
1324                 return null;
1325         }
1326
1327         public Stream GetImage (string url)
1328         {
1329                 if (url.StartsWith ("source-id:")){
1330                         string rest = url.Substring (10);
1331                         int p = rest.IndexOf (":");
1332                         string str_idx = rest.Substring (0, p);
1333                         int idx = 0;
1334
1335                         try {
1336                                 idx = Int32.Parse (str_idx);
1337                         } catch {
1338                                 Console.Error.WriteLine ("Failed to parse source-id url: {0} `{1}'", url, str_idx);
1339                                 return null;
1340                         }
1341
1342                         HelpSource hs = GetHelpSourceFromId (idx);
1343                         lastHelpSourceTime = hs.ZipFileWriteTime;
1344                         return hs.GetImage (rest.Substring (p + 1));
1345                 } else {
1346                         System.Reflection.Assembly assembly = System.Reflection.Assembly.GetAssembly (typeof(RootTree));                        
1347                         return assembly.GetManifestResourceStream (url);
1348                 }
1349                 lastHelpSourceTime = DateTime.MinValue;
1350                 return null;
1351         }
1352         
1353         public HelpSource GetHelpSourceFromId (int id)
1354         {
1355                 return (HelpSource) help_sources [id];
1356         }
1357         
1358         //
1359         // Fetches the node title
1360         //
1361         public string GetTitle (string url)
1362         {
1363                 Node match_node;
1364
1365                 if (url == null || url.StartsWith ("root:"))
1366                         return "Mono Documentation";
1367                 
1368                 if (url.Length > 2 && url [1] == ':'){
1369                         switch (url [0]){
1370                         case 'N':
1371                                 return url.Substring (2) + " Namespace";
1372
1373                         case 'T':
1374                                 string s = TypeLookup (url, out match_node);
1375                                 if (match_node != null)
1376                                         return match_node.Caption;
1377                                 return url.Substring (2) + " type";
1378
1379                 case 'M':
1380                 case 'F':
1381                 case 'P':
1382                 case 'E':
1383                 case 'C':
1384                 case 'O':
1385                         MemberLookup (url.Substring (0,2), url, out match_node);
1386                         if (match_node != null)
1387                                 return match_node.Caption;
1388                         break;
1389                         }
1390                 }
1391                 
1392                 return "Mono Documentation";
1393         }
1394         
1395         string home_cache;
1396         /// <summary>
1397         ///    Allows every HelpSource to try to provide the content for this
1398         ///    URL.
1399         /// </summary>
1400         public string RenderUrl (string url, out Node match_node)
1401         {
1402                 lastHelpSourceTime = DateTime.MinValue;
1403                 if (url == "root:") {
1404                         match_node = this;
1405
1406                         // look whether there are contribs
1407                         GlobalChangeset chgs = EditingUtils.changes;
1408                         StringBuilder con = new StringBuilder ();
1409                         
1410                         //add links to the contrib
1411                         int oldContrib = 0, contribs = 0;
1412                         con.Append ("<ul>");
1413                         foreach (DocSetChangeset dscs in chgs.DocSetChangesets) 
1414                                 foreach (FileChangeset fcs in dscs.FileChangesets) 
1415                                         foreach (Change c in fcs.Changes) {
1416                                                 if (c.NodeUrl == null) {
1417                                                         if (c.Serial == SettingsHandler.Settings.SerialNumber)
1418                                                                 oldContrib++;
1419                                                 } else if (c.Serial == SettingsHandler.Settings.SerialNumber) {
1420                                                         contribs++;
1421                                                         con.Append (String.Format ("<li><a href=\"{0}\">{0}</a></li>", c.NodeUrl));
1422                                                 }
1423                                         }
1424                         
1425                         string contrib = (oldContrib + contribs) == 1?"There is {0} contribution":"There are {0} contributions";
1426                         con.Insert (0, String.Format (contrib, oldContrib + contribs) + " pending upload <i>(Contributing--&gt; Upload)</i>", 1);
1427                         con.Append ("</ul>");
1428                         if (oldContrib == 1)
1429                                 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>");
1430                         else if (oldContrib > 1)
1431                                 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>");
1432
1433                         //start the rendering
1434                         if (!HelpSource.use_css) {
1435                                 StringBuilder sb = new StringBuilder ("<table bgcolor=\"#b0c4de\" width=\"100%\" cellpadding=\"5\"><tr><td><h3>Mono Documentation Library</h3></td></tr></table>");
1436                         
1437                                 foreach (Node n in Nodes)
1438                                         sb.AppendFormat ("<a href='{0}'>{1}</a><br/>", n.Element, n.Caption);
1439                         
1440                                 //contributions
1441                                 sb.Append ("<br><table bgcolor=\"#fff3f3\" width=\"100%\" cellpadding=\"5\"><tr><td>");
1442                                 sb.Append ("<h5>Contributions</h5><br>");
1443                                 if ((oldContrib + contribs) == 0) {
1444                                         sb.Append ("<p><b>You have not made any contributions yet.</b></p>");
1445                                         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>");
1446                                         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>");
1447                                 } else {
1448                                         sb.Append (con.ToString ());
1449                                 }
1450                                 sb.Append ("</td></tr></table>");
1451                                 return sb.ToString ();  
1452                         } else {
1453                                 if (home_cache == null) {
1454                                         System.Reflection.Assembly assembly = System.Reflection.Assembly.GetAssembly (typeof (HelpSource));
1455                                         Stream hp_stream = assembly.GetManifestResourceStream ("home.html");
1456                                         home_cache = (new StreamReader (hp_stream)).ReadToEnd ();
1457                                 }
1458                                 StringBuilder sb = new StringBuilder (home_cache);
1459                                 // adjust fonts
1460                                 sb.Replace ("@@FONT_FAMILY@@", SettingsHandler.Settings.preferred_font_family);
1461                                 sb.Replace ("@@FONT_SIZE@@", SettingsHandler.Settings.preferred_font_size.ToString());
1462                                 //contributions
1463                                 var visible = SettingsHandler.Settings.EnableEditing ? "block;" : "none;";
1464                                 if ((oldContrib + contribs) == 0) {
1465                                         sb.Replace ("@@CONTRIB_DISP@@", "display: none;");
1466                                         sb.Replace ("@@NO_CONTRIB_DISP@@", "display: " + visible);
1467                                 } else {
1468                                         sb.Replace ("@@CONTRIB_DISP@@", "display: " + visible);
1469                                         sb.Replace ("@@NO_CONTRIB_DISP@@", "display: none;");
1470                                         sb.Replace ("@@CONTRIBS@@", con.ToString ());
1471                                 }
1472                                 sb.Replace ("@@EDITING_ENABLED@@", "display: " + visible);
1473                                         
1474                                 // load the url of nodes
1475                                 String add_str;
1476                                 StringBuilder urls = new StringBuilder ();
1477                                 foreach (Node n in Nodes) {
1478                                         add_str = String.Format ("<li><a href=\"{0}\">{1}</a></li>", n.Element, n.Caption);
1479                                         urls.Append (add_str);
1480                                 }
1481                                 sb.Replace ("@@API_DOCS@@", urls.ToString ());
1482                                                 
1483                                 return sb.ToString ();
1484                         }
1485                 } 
1486                 
1487                 if (url.StartsWith ("root:")) {
1488                         match_node = ((Node)name_to_node [url.Substring (6)]);
1489                         HelpSource hs = ((HelpSource)name_to_hs [url.Substring (6)]);
1490                         if (hs == null) 
1491                         {
1492                                 return GenerateNodeIndex(match_node);
1493                         }
1494                                 
1495                         Node dummy;
1496                         lastHelpSourceTime = hs.ZipFileWriteTime;
1497                         return hs.GetText ("root:", out dummy);
1498                 }
1499         
1500                 
1501                 if (url.StartsWith ("source-id:")){
1502                         string rest = url.Substring (10);
1503                         int p = rest.IndexOf (":");
1504                         string str_idx = rest.Substring (0, p);
1505                         int idx = 0;
1506
1507                         try {
1508                                 idx = Int32.Parse (str_idx);
1509                         } catch {
1510                                 Console.Error.WriteLine ("Failed to parse source-id url: {0} `{1}'", url, str_idx);
1511                                 match_node = null;
1512                                 return null;
1513                         }
1514                         HelpSource hs = (HelpSource) help_sources [idx];
1515                         // Console.WriteLine ("Attempting to get docs from: " + rest.Substring (p + 1));
1516                         lastHelpSourceTime = hs.ZipFileWriteTime;
1517                         return hs.GetText (rest.Substring (p + 1), out match_node);
1518                 }
1519
1520                 if (url.Length < 2){
1521                         match_node = null;
1522                         return null;
1523                 }
1524                 
1525                 string prefix = url.Substring (0, 2);
1526                 
1527                 switch (prefix.ToUpper ()){
1528                 case "N:":
1529                         foreach (HelpSource hs in help_sources){
1530                                 string s = hs.RenderNamespaceLookup (url, out match_node);
1531                                 if (s != null) {
1532                                         lastHelpSourceTime = hs.ZipFileWriteTime;
1533                                         return s;
1534                                 }
1535                         }
1536                         match_node = null;
1537                         return null;
1538
1539                 case "T:":
1540                         return TypeLookup (url, out match_node);
1541
1542                 case "M:":
1543                 case "F:":
1544                 case "P:":
1545                 case "E:":
1546                 case "C:":
1547                 case "O:":
1548                         return MemberLookup (prefix, url, out match_node);
1549                 
1550                 default:
1551                         foreach (HelpSource hs in help_sources){
1552                                 string s = hs.GetText (url, out match_node);
1553                                 
1554                                 if (s != null) {
1555                                         lastHelpSourceTime = hs.ZipFileWriteTime;
1556                                         return s;
1557                                 }
1558                         }
1559                         match_node = null;
1560                         return null;
1561                 }
1562         }
1563         
1564         public string GenerateNodeIndex (Node node)
1565         {
1566                 StringBuilder buf = new StringBuilder();
1567                 buf.AppendFormat("<H3>{0}</H3>", node.Caption);
1568                 buf.Append("<ul>");
1569                 foreach (Node child in node.Nodes)
1570                 {
1571                         buf.AppendFormat("<li><a href=\"{0}\">{1}</a>", child.URL, child.Caption);
1572                 }
1573                 buf.Append("</ul>");
1574                 return buf.ToString();
1575         }
1576         
1577         public IndexReader GetIndex ()
1578         {
1579                 //try to load from basedir
1580                 string index_file = Path.Combine (basedir, "monodoc.index");
1581                 if (File.Exists (index_file))
1582                         return IndexReader.Load (index_file);
1583                 //then, try to load from config dir
1584                 index_file = Path.Combine (SettingsHandler.Path, "monodoc.index");
1585                 return IndexReader.Load (index_file);
1586                 
1587         }
1588
1589         public static void MakeIndex ()
1590         {
1591                 RootTree root = LoadTree ();
1592                 if (root == null)
1593                         return;
1594
1595                 IndexMaker index_maker = new IndexMaker ();
1596                 
1597                 foreach (HelpSource hs in root.help_sources){
1598                         hs.PopulateIndex (index_maker);
1599                 }
1600
1601                 // if the user has no write permissions use config dir
1602                 string path = Path.Combine (root.basedir, "monodoc.index");
1603                 try {
1604                         index_maker.Save (path);
1605                 } catch (System.UnauthorizedAccessException) {
1606                         path = Path.Combine (SettingsHandler.Path, "monodoc.index");
1607                         try {
1608                                 index_maker.Save (path);
1609                         } catch (System.UnauthorizedAccessException) {
1610                                 Console.WriteLine ("Unable to write index file in {0}", Path.Combine (SettingsHandler.Path, "monodoc.index")); 
1611                                 return;
1612                         }
1613                 }
1614
1615                 if (IsUnix){
1616                         // No octal in C#, how lame is that
1617                         chmod (path, 0x1a4);
1618                 }
1619                 Console.WriteLine ("Documentation index updated");
1620         }
1621
1622         static bool IsUnix {
1623                 get {
1624                         int p = (int) Environment.OSVersion.Platform;
1625                         return ((p == 4) || (p == 128) || (p == 6));
1626                 }
1627         }
1628
1629         // Search Index
1630         public SearchableIndex GetSearchIndex ()
1631         {
1632                 //try to load from basedir
1633                 string index_file = Path.Combine (basedir, "search_index");
1634                 if (Directory.Exists (index_file))
1635                         return SearchableIndex.Load (index_file);
1636                 //then, try to load from config dir
1637                 index_file = Path.Combine (SettingsHandler.Path, "search_index");
1638                 return SearchableIndex.Load (index_file);
1639         }
1640
1641         public static void MakeSearchIndex ()
1642         {
1643                 // Loads the RootTree
1644                 Console.WriteLine ("Loading the monodoc tree...");
1645                 RootTree root = LoadTree ();
1646                 if (root == null)
1647                         return;
1648
1649                 string dir = Path.Combine (root.basedir, "search_index");
1650                 IndexWriter writer;
1651                 //try to create the dir to store the index
1652                 try {
1653                         if (!Directory.Exists (dir)) 
1654                                 Directory.CreateDirectory (dir);
1655
1656                         writer = new IndexWriter(Lucene.Net.Store.FSDirectory.GetDirectory(dir, true), new StandardAnalyzer(), true);
1657                 } catch (UnauthorizedAccessException) {
1658                         //try in the .config directory
1659                         try {
1660                                 dir = Path.Combine (SettingsHandler.Path, "search_index");
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                                 Console.WriteLine ("You don't have permissions to write on " + dir);
1667                                 return;
1668                         }
1669                 }
1670
1671                 //Collect all the documents
1672                 Console.WriteLine ("Collecting and adding documents...");
1673                 foreach (HelpSource hs in root.HelpSources) 
1674                         hs.PopulateSearchableIndex (writer);
1675         
1676                 //Optimize and close
1677                 Console.WriteLine ("Closing...");
1678                 writer.Optimize();
1679                 writer.Close();
1680         }
1681
1682
1683         public ICollection HelpSources { get { return new ArrayList(help_sources); } }
1684
1685         [System.Runtime.InteropServices.DllImport ("libc")]
1686         static extern int chmod (string filename, int mode);
1687 }
1688 }