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