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