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