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