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