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