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