2 // Provider: shared code and interfaces for providers
5 // Miguel de Icaza (miguel@ximian.com)
7 // (C) 2002, Ximian, Inc.
8 // Copyright 2003-2011 Novell
9 // Copyright 2011 Xamarin Inc
12 // Each node should have a provider link
14 // Should encode numbers using a runlength encoding to save space
18 using System.Collections.Generic;
22 using System.Text.RegularExpressions;
23 using System.Collections;
24 using System.Diagnostics;
25 using System.Configuration;
26 using System.Reflection;
28 using System.Xml.XPath;
29 using ICSharpCode.SharpZipLib.Zip;
31 using Mono.Lucene.Net.Index;
32 using Mono.Lucene.Net.Analysis.Standard;
34 using Mono.Documentation;
37 /// This tree is populated by the documentation providers, or populated
38 /// from a binary encoding of the tree. The format of the tree is designed
39 /// to minimize the need to load it in full.
41 public class Tree : Node {
43 #region Loading the tree from a file
46 /// Our HelpSource container
48 public readonly HelpSource HelpSource;
50 internal FileStream InputStream;
51 internal BinaryReader InputReader;
54 /// Load from file constructor
56 public Tree (HelpSource hs, string filename) : base (null, null)
58 Encoding utf8 = new UTF8Encoding (false, true);
60 if (!File.Exists (filename)){
61 throw new FileNotFoundException ();
64 InputStream = File.OpenRead (filename);
65 InputReader = new BinaryReader (InputStream, utf8);
66 byte [] sig = InputReader.ReadBytes (4);
69 throw new Exception ("Invalid file format");
71 InputStream.Position = 4;
72 position = InputReader.ReadInt32 ();
79 /// Tree creation and merged tree constructor
81 public Tree (HelpSource hs, string caption, string url) : base (caption, url)
86 public Tree (HelpSource hs, Node parent, string caption, string element) : base (parent, caption, element)
94 /// Saves the tree into the specified file using the help file format.
96 public void Save (string file)
98 Encoding utf8 = new UTF8Encoding (false, true);
99 using (FileStream output = File.OpenWrite (file)){
100 // Skip over the pointer to the first node.
103 using (BinaryWriter writer = new BinaryWriter (output, utf8)){
105 Dump (output, writer);
108 writer.Write (new byte [] { (byte) 'M', (byte) 'o', (byte) 'H', (byte) 'P' });
109 writer.Write (position);
114 static bool GoodSig (byte [] sig)
118 if (sig [0] != (byte) 'M' ||
119 sig [1] != (byte) 'o' ||
120 sig [2] != (byte) 'H' ||
121 sig [3] != (byte) 'P')
128 public class Node : IComparable {
129 string caption, element;
130 public bool Documented;
131 public readonly Tree tree;
133 protected ArrayList nodes;
134 protected internal int position;
136 static ArrayList empty = ArrayList.ReadOnly(new ArrayList(0));
139 /// Creates a node, called by the Tree.
141 public Node (string caption, string element)
143 this.tree = (Tree) this;
144 this.caption = caption;
145 this.element = element;
149 public Node (Node parent, string caption, string element)
151 this.parent = parent;
152 this.tree = parent.tree;
153 this.caption = caption;
154 this.element = element;
158 /// Creates a node from an on-disk representation
160 Node (Node parent, int address)
162 this.parent = parent;
164 this.tree = parent.tree;
169 public void AddNode (Node n)
176 public void DelNode (Node n)
181 public ArrayList Nodes {
185 return nodes != null ? nodes : empty;
189 public string Element {
201 public string Caption {
215 public void LoadNode ()
218 position = -position;
220 tree.InputStream.Position = position;
221 BinaryReader reader = tree.InputReader;
222 int count = DecodeInt (reader);
223 element = reader.ReadString ();
224 caption = reader.ReadString ();
228 nodes = new ArrayList (count);
229 for (int i = 0; i < count; i++){
230 int child_address = DecodeInt (reader);
232 Node t = new Node (this, -child_address);
238 /// Creates a new node, in the locator entry point, and with
239 /// a user visible caption of @caption
241 public Node CreateNode (string c_caption, string c_element)
244 nodes = new ArrayList ();
246 Node t = new Node (this, c_caption, c_element);
252 /// Looks up or creates a new node, in the locator entry point, and with
253 /// a user visible caption of @caption. This is different from
254 /// CreateNode in that it will look up an existing node for the given @locator.
256 public Node LookupNode (string c_caption, string c_element)
259 return CreateNode (c_caption, c_element);
261 foreach (Node n in nodes){
262 if (n.element == c_element)
265 return CreateNode (c_caption, c_element);
268 public void EnsureNodes ()
271 nodes = new ArrayList ();
276 return nodes == null;
280 void EncodeInt (BinaryWriter writer, int value)
283 int high = (value >> 7) & 0x01ffffff;
284 byte b = (byte)(value & 0x7f);
287 b = (byte)(b | 0x80);
295 int DecodeInt (BinaryReader reader)
302 b = reader.ReadByte();
304 ret = ret | ((b & 0x7f) << shift);
306 } while ((b & 0x80) == 0x80);
311 internal void Dump (FileStream output, BinaryWriter writer)
314 foreach (Node child in nodes){
315 child.Dump (output, writer);
318 position = (int) output.Position;
319 EncodeInt (writer, nodes == null ? 0 : (int) nodes.Count);
320 writer.Write (element);
321 writer.Write (caption);
324 foreach (Node child in nodes){
325 EncodeInt (writer, child.position);
332 static void Indent ()
334 for (int i = 0; i < indent; i++)
338 public static void PrintTree (Node node)
341 Console.WriteLine ("{0},{1}\t[PublicUrl: {2}]", node.Element, node.Caption, node.PublicUrl);
342 if (node.Nodes.Count == 0)
346 foreach (Node n in node.Nodes)
357 [Obsolete("Use PublicUrl")]
363 if (element.IndexOf (":") >= 0)
367 string url = parent.URL;
369 if (url.EndsWith ("/"))
370 return url + element;
372 return parent.URL + "/" + element;
378 public string PublicUrl {
380 return tree.HelpSource != null
381 ? tree.HelpSource.GetPublicUrl (URL)
386 int IComparable.CompareTo (object obj)
388 Node other = obj as Node;
394 if (other.position < 0)
398 var cap2 = other.caption;
400 /* Some node (notably from ecmaspec) have number prepended to them
401 * which we need to sort better by padding them to the same number
404 if (char.IsDigit (cap1[0]) && char.IsDigit (cap2[0])) {
405 int c1 = cap1.TakeWhile (char.IsDigit).Count ();
406 int c2 = cap2.TakeWhile (char.IsDigit).Count ();
409 cap1 = cap1.PadLeft (cap1.Length + Math.Max (0, c2 - c1), '0');
410 cap2 = cap2.PadLeft (cap2.Length + Math.Max (0, c1 - c2), '0');
414 return string.Compare (cap1, cap2, StringComparison.OrdinalIgnoreCase);
419 // The HelpSource class keeps track of the archived data, and its
422 public class HelpSource {
424 public static bool use_css = false;
425 public static string css_code;
426 public static string CssCode {
428 if (css_code != null)
431 System.Reflection.Assembly assembly = System.Reflection.Assembly.GetCallingAssembly ();
432 Stream str_css = assembly.GetManifestResourceStream ("base.css");
433 StringBuilder sb = new StringBuilder ((new StreamReader (str_css)).ReadToEnd());
434 sb.Replace ("@@FONT_FAMILY@@", SettingsHandler.Settings.preferred_font_family);
435 sb.Replace ("@@FONT_SIZE@@", SettingsHandler.Settings.preferred_font_size.ToString());
436 css_code = sb.ToString ();
439 set { css_code = value; }
442 public virtual string InlineCss {
443 get { return CssCode; }
446 public virtual string InlineJavaScript {
450 public static bool FullHtml = true;
452 // should only be enabled by ASP.NET webdoc
453 public static bool UseWebdocCache;
456 // The unique ID for this HelpSource.
459 DateTime zipFileWriteTime;
462 TraceLevel trace_level = TraceLevel.Warning;
463 protected bool nozip;
464 protected string base_dir;
466 public HelpSource (string base_filename, bool create)
468 this.name = Path.GetFileName (base_filename);
469 this.basepath = Path.GetDirectoryName (base_filename);
470 tree_filename = base_filename + ".tree";
471 zip_filename = base_filename + ".zip";
472 base_dir = XmlDocUtils.GetCacheDirectory (base_filename);
473 if (UseWebdocCache && !create && Directory.Exists (base_dir)) {
480 Tree = new Tree (this, tree_filename);
484 FileInfo fi = new FileInfo (zip_filename);
485 zipFileWriteTime = fi.LastWriteTime;
487 zipFileWriteTime = DateTime.Now;
491 public HelpSource() {
492 Tree = new Tree (this, "Blah", "Blah");
496 public DateTime ZipFileWriteTime {
498 return zipFileWriteTime;
502 public int SourceID {
514 /* This gives the full path of the source/ directory */
515 public string BaseFilePath {
521 public TraceLevel TraceLevel {
522 get { return trace_level; }
523 set { trace_level = value; }
526 public string BaseDir {
535 /// Returns a stream from the packaged help source archive
537 public virtual Stream GetHelpStream (string id)
540 string path = XmlDocUtils.GetCachedFileName (base_dir, id);
541 if (File.Exists (path))
542 return File.OpenRead (path);
546 if (zip_file == null)
547 zip_file = new ZipFile (zip_filename);
549 ZipEntry entry = zip_file.GetEntry (id);
551 return zip_file.GetInputStream (entry);
555 public string GetRealPath (string file)
557 if (zip_file == null)
558 zip_file = new ZipFile (zip_filename);
560 ZipEntry entry = zip_file.GetEntry (file);
561 if (entry != null && entry.ExtraData != null)
562 return ConvertToString (entry.ExtraData);
566 public XmlReader GetHelpXml (string id)
569 Stream s = File.OpenRead (XmlDocUtils.GetCachedFileName (base_dir, id));
570 string url = "monodoc:///" + SourceID + "@" + Uri.EscapeUriString (id) + "@";
571 return new XmlTextReader (url, s);
574 if (zip_file == null)
575 zip_file = new ZipFile (zip_filename);
577 ZipEntry entry = zip_file.GetEntry (id);
579 Stream s = zip_file.GetInputStream (entry);
580 string url = "monodoc:///" + SourceID + "@" + Uri.EscapeUriString (id) + "@";
581 return new XmlTextReader (url, s);
586 public virtual XmlDocument GetHelpXmlWithChanges (string id)
589 Stream s = File.OpenRead (XmlDocUtils.GetCachedFileName (base_dir, id));
590 string url = "monodoc:///" + SourceID + "@" + Uri.EscapeUriString (id) + "@";
591 XmlReader r = new XmlTextReader (url, s);
592 XmlDocument ret = new XmlDocument ();
597 if (zip_file == null)
598 zip_file = new ZipFile (zip_filename);
600 ZipEntry entry = zip_file.GetEntry (id);
602 Stream s = zip_file.GetInputStream (entry);
603 string url = "monodoc:///" + SourceID + "@" + Uri.EscapeUriString (id) + "@";
604 XmlReader r = new XmlTextReader (url, s);
605 XmlDocument ret = new XmlDocument ();
608 if (entry.ExtraData != null)
609 EditingUtils.AccountForChanges (ret, Name, ConvertToString (entry.ExtraData));
617 /// Get a nice, unique expression for any XPath node that you get.
618 /// This function is used by editing to get the expression to put
619 /// on to the file. The idea is to create an expression that is resistant
620 /// to changes in the structure of the XML.
622 public virtual string GetNodeXPath (XPathNavigator n)
624 return EditingUtils.GetXPath (n.Clone ());
627 public string GetEditUri (XPathNavigator n)
629 return EditingUtils.FormatEditUri (n.BaseURI, GetNodeXPath (n));
632 static string ConvertToString (byte[] data)
634 return Encoding.UTF8.GetString(data);
637 static byte[] ConvertToArray (string str)
639 return Encoding.UTF8.GetBytes(str);
643 /// The tree that is being populated
646 public RootTree RootTree;
648 // Base filename used by this HelpSource.
649 string tree_filename, zip_filename;
652 const int buffer_size = 65536;
653 ZipOutputStream zip_output;
656 HelpSource (string base_filename)
660 void SetupForOutput ()
662 Tree = new Tree (this, "", "");
664 FileStream stream = File.Create (zip_filename);
666 zip_output = new ZipOutputStream (stream);
667 zip_output.SetLevel (9);
669 buffer = new byte [buffer_size];
673 /// Saves the tree and the archive
677 Tree.Save (tree_filename);
678 zip_output.Finish ();
686 return String.Format ("{0}", code++);
690 /// Providers call this to store a file they will need, and the return value
691 /// is the name that was assigned to it
693 public string PackFile (string file)
695 string entry_name = GetNewCode ();
696 return PackFile (file, entry_name);
699 public string PackFile (string file, string entry_name)
701 using (FileStream input = File.OpenRead (file)) {
702 PackStream (input, entry_name, file);
708 public void PackStream (Stream s, string entry_name)
710 PackStream (s, entry_name, null);
713 void PackStream (Stream s, string entry_name, string realPath)
715 ZipEntry entry = new ZipEntry (entry_name);
717 if (realPath != null)
718 entry.ExtraData = ConvertToArray (realPath);
720 zip_output.PutNextEntry (entry);
723 while ((n = s.Read (buffer, 0, buffer_size)) > 0){
724 zip_output.Write (buffer, 0, n);
728 public void PackXml (string fname, XmlDocument doc, string real_path)
730 ZipEntry entry = new ZipEntry (fname);
731 if (real_path != null)
732 entry.ExtraData = ConvertToArray(real_path);
734 zip_output.PutNextEntry (entry);
735 XmlTextWriter xmlWriter = new XmlTextWriter (zip_output, Encoding.UTF8);
736 doc.WriteContentTo (xmlWriter);
740 public virtual void RenderPreviewDocs (XmlNode newNode, XmlWriter writer)
742 throw new NotImplementedException ();
745 public virtual string GetPublicUrl (string id)
750 public virtual string GetText (string url, out Node n)
756 protected string GetCachedText (string url)
760 string file = XmlDocUtils.GetCachedFileName (base_dir, url);
761 if (!File.Exists (file))
763 return File.OpenText (file).ReadToEnd ();
766 public virtual Stream GetImage (string url)
772 // Default method implementation does not satisfy the request
774 public virtual string RenderTypeLookup (string prefix, string ns, string type, string member, out Node n)
780 public virtual string RenderNamespaceLookup (string nsurl, out Node n)
787 // Populates the index.
789 public virtual void PopulateIndex (IndexMaker index_maker)
794 // Build an html document
796 public static string BuildHtml (string css, string html_code)
798 return BuildHtml (css, null, html_code);
801 internal static string BuildHtml (string css, string js, string html_code) {
805 StringWriter output = new StringWriter ();
806 output.Write ("<html><head>");
807 output.Write ("<style type=\"text/css\">");
808 output.Write (CssCode);
810 output.Write ("</style>");
812 System.Reflection.Assembly assembly = System.Reflection.Assembly.GetCallingAssembly ();
813 Stream str_js = assembly.GetManifestResourceStream ("helper.js");
814 StringBuilder sb = new StringBuilder ((new StreamReader (str_js)).ReadToEnd());
815 output.Write ("<script type=\"text/JavaScript\">\n");
816 output.Write (sb.ToString ());
817 output.Write ("</script>\n");
820 output.Write ("<script type=\"text/JavaScript\">\n");
822 output.Write ("\n</script>");
825 output.Write ("</head><body>");
826 output.Write (html_code);
827 output.Write ("</body></html>");
828 return output.ToString ();
832 // Create different Documents for adding to Lucene search index
833 // The default action is do nothing. Subclasses should add the docs
835 public virtual void PopulateSearchableIndex (IndexWriter writer) {
839 public void Message (TraceLevel level, string format, params object[] args)
841 if ((int) level <= (int) trace_level)
842 Console.WriteLine (format, args);
845 public void Error (string format, params object[] args)
847 Console.Error.WriteLine (format, args);
851 public abstract class Provider {
853 // This code is used to "tag" all the different sources
864 public abstract void PopulateTree (Tree tree);
867 // Called at shutdown time after the tree has been populated to perform
868 // any fixups or final tasks.
870 public abstract void CloseTree (HelpSource hs, Tree tree);
873 public class RootTree : Tree {
876 public static ArrayList UncompiledHelpSources = new ArrayList();
878 public const int MonodocVersion = 1;
880 public static RootTree LoadTree ()
882 return LoadTree (null);
885 const string MacMonoDocDir = "/Library/Frameworks/Mono.framework/Versions/Current/lib/monodoc";
888 // Loads the tree layout
890 public static RootTree LoadTree (string basedir)
892 if (basedir == null) {
893 string myPath = System.Reflection.Assembly.GetExecutingAssembly ().Location;
894 string cfgFile = myPath + ".config";
895 if (!File.Exists (cfgFile)) {
899 XmlDocument d = new XmlDocument ();
901 basedir = d.SelectSingleNode ("config/path").Attributes ["docsPath"].Value;
903 // Temporary workaround for developers distributing a monodoc.dll themselves on Mac
904 if (Directory.Exists (MacMonoDocDir)){
905 Console.WriteLine ("MacDir exists");
906 if (!File.Exists (Path.Combine (basedir, "monodoc.xml"))){
907 basedir = MacMonoDocDir;
915 XmlDocument doc = new XmlDocument ();
916 string layout = Path.Combine (basedir, "monodoc.xml");
919 string[] sourceDirs = new[]{
920 Path.Combine (basedir, "sources"),
921 "/Library/Frameworks/Mono.framework/External/monodoc",
923 Environment.GetFolderPath (Environment.SpecialFolder.LocalApplicationData),
926 var sources = new List<string> ();
927 foreach (var dir in sourceDirs) {
928 if (!Directory.Exists (dir))
930 sources.AddRange (Directory.GetFiles (dir, "*.source"));
933 return LoadTree (basedir, doc, sources);
936 // Compatibility shim w/ Mono 2.6
937 public static RootTree LoadTree (string indexDir, XmlDocument docTree, IEnumerable sourceFiles)
939 return LoadTree (indexDir, docTree, sourceFiles.Cast<string>());
942 public static RootTree LoadTree (string indexDir, XmlDocument docTree, IEnumerable<string> sourceFiles)
944 if (docTree == null) {
945 docTree = new XmlDocument ();
946 using (var defTree = typeof(RootTree).Assembly.GetManifestResourceStream ("monodoc.xml"))
947 docTree.Load (defTree);
951 sourceFiles = sourceFiles ?? new string [0];
957 RootTree root = new RootTree ();
958 root.basedir = indexDir;
960 XmlNodeList nodes = docTree.SelectNodes ("/node/node");
962 root.name_to_node ["root"] = root;
963 root.name_to_node ["libraries"] = root;
964 root.Populate (root, nodes);
966 Node third_party = root.LookupEntryPoint ("various");
967 if (third_party == null) {
968 Console.Error.WriteLine ("No 'various' doc node! Check monodoc.xml!");
975 foreach (var sourceFile in sourceFiles)
976 root.AddSourceFile (sourceFile);
978 foreach (string path in UncompiledHelpSources) {
979 EcmaUncompiledHelpSource hs = new EcmaUncompiledHelpSource(path);
981 root.help_sources.Add (hs);
982 string epath = "extra-help-source-" + hs.Name;
983 Node hsn = root.CreateNode (hs.Name, "root:/" + epath);
984 root.name_to_hs [epath] = hs;
986 foreach (Node n in hs.Tree.Nodes){
999 public void AddSource (string sources_dir)
1001 string [] files = Directory.GetFiles (sources_dir);
1003 foreach (string file in files){
1004 if (!file.EndsWith (".source"))
1006 AddSourceFile (file);
1010 Dictionary<string,string> loadedSourceFiles = new Dictionary<string,string> ();
1012 public void AddSourceFile (string sourceFile)
1014 if (loadedSourceFiles.ContainsKey (sourceFile))
1017 Node third_party = LookupEntryPoint ("various") ?? this;
1019 XmlDocument doc = new XmlDocument ();
1021 doc.Load (sourceFile);
1024 Console.Error.WriteLine ("Error: Could not load source file {0}", sourceFile);
1028 XmlNodeList extra_nodes = doc.SelectNodes ("/monodoc/node");
1029 if (extra_nodes.Count > 0)
1030 Populate (third_party, extra_nodes);
1032 XmlNodeList sources = doc.SelectNodes ("/monodoc/source");
1033 if (sources == null){
1034 Console.Error.WriteLine ("Error: No <source> section found in the {0} file", sourceFile);
1037 loadedSourceFiles [sourceFile] = sourceFile;
1038 foreach (XmlNode source in sources){
1039 XmlAttribute a = source.Attributes ["provider"];
1041 Console.Error.WriteLine ("Error: no provider in <source>");
1044 string provider = a.InnerText;
1045 a = source.Attributes ["basefile"];
1047 Console.Error.WriteLine ("Error: no basefile in <source>");
1050 string basefile = a.InnerText;
1051 a = source.Attributes ["path"];
1053 Console.Error.WriteLine ("Error: no path in <source>");
1056 string path = a.InnerText;
1058 string basefilepath = Path.Combine (Path.GetDirectoryName (sourceFile), basefile);
1059 HelpSource hs = GetHelpSource (provider, basefilepath);
1063 help_sources.Add (hs);
1064 name_to_hs [path] = hs;
1066 Node parent = LookupEntryPoint (path);
1067 if (parent == null){
1068 Console.Error.WriteLine ("node `{0}' is not defined on the documentation map", path);
1069 parent = third_party;
1072 foreach (Node n in hs.Tree.Nodes){
1079 // Delete nodes which does not have documentaiton (source)
1080 static bool PurgeNode(Node node)
1084 if (!node.Documented)
1086 ArrayList del_child = new ArrayList();
1087 //Delete node unless any child has documentation
1088 bool purged_child = false;
1089 foreach (Node child in node.Nodes)
1091 purged_child = PurgeNode(child);
1094 del_child.Add(child);
1098 // delete the node if all its children are to be deleted
1099 purge = (node.Nodes.Count == del_child.Count);
1102 foreach (Node child in del_child)
1104 node.DelNode(child);
1111 public static string[] GetSupportedFormats ()
1113 return new string[]{
1124 public static HelpSource GetHelpSource (string provider, string basefilepath)
1129 return new EcmaHelpSource (basefilepath, false);
1130 case "ecma-uncompiled":
1131 return new EcmaUncompiledHelpSource (basefilepath);
1133 return new MonoHBHelpSource(basefilepath, false);
1134 case "xhtml": case "hb":
1135 return new XhtmlHelpSource (basefilepath, false);
1137 return new ManHelpSource (basefilepath, false);
1139 return new SimpleHelpSource (basefilepath, false);
1141 return new ErrorHelpSource (basefilepath, false);
1143 return new EcmaSpecHelpSource (basefilepath, false);
1145 return new AddinsHelpSource (basefilepath, false);
1147 Console.Error.WriteLine ("Error: Unknown provider specified: {0}", provider);
1152 catch (FileNotFoundException) {
1153 Console.Error.WriteLine ("Error: did not find one of the files in sources/"+basefilepath);
1158 public static Provider GetProvider (string provider, params string[] basefilepaths)
1162 return new AddinsProvider (basefilepaths [0]);
1164 EcmaProvider p = new EcmaProvider ();
1165 foreach (string d in basefilepaths)
1170 return new EcmaSpecProvider (basefilepaths [0]);
1172 return new ErrorProvider (basefilepaths [0]);
1174 return new ManProvider (basefilepaths);
1176 return new SimpleProvider (basefilepaths [0]);
1179 return new XhtmlProvider (basefilepaths [0]);
1181 throw new NotSupportedException (provider);
1186 // Maintains the name to node mapping
1188 Hashtable name_to_node = new Hashtable ();
1189 Hashtable name_to_hs = new Hashtable ();
1191 void Populate (Node parent, XmlNodeList xml_node_list)
1193 foreach (XmlNode xml_node in xml_node_list){
1194 XmlAttribute e = xml_node.Attributes ["parent"];
1195 if (e != null && name_to_node.ContainsKey (e.InnerText)) {
1196 Node p = (Node) name_to_node [e.InnerText];
1197 xml_node.Attributes.Remove (e);
1198 Populate (p, xml_node.SelectNodes ("."));
1201 e = xml_node.Attributes ["label"];
1203 Console.Error.WriteLine ("`label' attribute missing in <node>");
1206 string label = e.InnerText;
1207 e = xml_node.Attributes ["name"];
1209 Console.Error.WriteLine ("`name' attribute missing in <node>");
1212 string name = e.InnerText;
1214 Node n = parent.LookupNode (label, "root:/" + name);
1216 name_to_node [name] = n;
1217 XmlNodeList children = xml_node.SelectNodes ("./node");
1218 if (children != null)
1219 Populate (n, children);
1223 public Node LookupEntryPoint (string name)
1225 return (Node) name_to_node [name];
1228 ArrayList help_sources;
1229 DateTime lastHelpSourceTime;
1231 RootTree () : base (null, "Mono Documentation", "root:")
1233 nodes = new ArrayList ();
1234 help_sources = new ArrayList ();
1235 lastHelpSourceTime = DateTime.MinValue;
1238 public DateTime LastHelpSourceTime {
1240 return lastHelpSourceTime;
1244 public static bool GetNamespaceAndType (string url, out string ns, out string type)
1248 for (int i = 0; i < url.Length; ++i) {
1271 ns = url.Substring (0, nsidx);
1272 type = url.Substring (nsidx + 1);
1274 //Console.Error.WriteLine ("GetNameSpaceAndType (ns={0}, type={1}", ns, type);
1278 public XmlDocument GetHelpXml (string url)
1280 string rest = url.Substring (2);
1283 if (!GetNamespaceAndType (rest, out ns, out type))
1286 foreach (HelpSource hs in help_sources) {
1287 EcmaHelpSource ehs = hs as EcmaHelpSource;
1290 string id = ehs.GetIdFromUrl ("T:", ns, type);
1293 XmlDocument doc = hs.GetHelpXmlWithChanges (id);
1300 public string TypeLookup (string url, out Node match_node)
1302 string rest = Regex.Replace (url, @"^T:\s*", "");
1305 if (!GetNamespaceAndType (rest, out ns, out type)){
1310 foreach (HelpSource hs in help_sources){
1311 string s = hs.RenderTypeLookup ("T:", ns, type, null, out match_node);
1314 lastHelpSourceTime = hs.ZipFileWriteTime;
1322 public string MemberLookup (string prefix, string url, out Node match_node)
1324 string rest = Regex.Replace (url, @"^.:\s*", "");
1326 // Dots in the arg list (for methods) confuse this.
1327 // Chop off the arg list for now and put it back later.
1328 string arglist = "";
1329 int argliststart = rest.IndexOf("(");
1330 if (argliststart >= 0) {
1331 arglist = rest.Substring(argliststart);
1332 rest = rest.Substring(0, argliststart);
1335 string ns_type, member;
1337 if (prefix != "C:") {
1338 int member_idx = rest.LastIndexOf (".");
1340 // The dot in .ctor (if it's a M: link) would confuse this.
1341 if (rest.EndsWith("..ctor")) member_idx--;
1343 ns_type = rest.Substring (0, member_idx);
1344 member = rest.Substring (member_idx + 1);
1346 // C: links don't have the .ctor member part as it would in a M: link
1347 // Even though externally C: links are different from M: links,
1348 // C: links get transformed into M:-style links (with .ctor) here.
1354 //Console.WriteLine ("NS_TYPE: {0} MEMBER: {1}", ns_type, member);
1357 if (!GetNamespaceAndType (ns_type, out ns, out type)){
1362 foreach (HelpSource hs in help_sources){
1363 string s = hs.RenderTypeLookup (prefix, ns, type, member + arglist, out match_node);
1366 lastHelpSourceTime = hs.ZipFileWriteTime;
1374 public Stream GetImage (string url)
1376 if (url.StartsWith ("source-id:")){
1377 string rest = url.Substring (10);
1378 int p = rest.IndexOf (":");
1379 string str_idx = rest.Substring (0, p);
1383 idx = Int32.Parse (str_idx);
1385 Console.Error.WriteLine ("Failed to parse source-id url: {0} `{1}'", url, str_idx);
1389 HelpSource hs = GetHelpSourceFromId (idx);
1390 lastHelpSourceTime = hs.ZipFileWriteTime;
1391 return hs.GetImage (rest.Substring (p + 1));
1393 System.Reflection.Assembly assembly = System.Reflection.Assembly.GetAssembly (typeof(RootTree));
1394 return assembly.GetManifestResourceStream (url);
1396 lastHelpSourceTime = DateTime.MinValue;
1400 public HelpSource GetHelpSourceFromId (int id)
1402 return (HelpSource) help_sources [id];
1406 // Fetches the node title
1408 public string GetTitle (string url)
1412 if (url == null || url.StartsWith ("root:"))
1413 return "Mono Documentation";
1415 if (url.Length > 2 && url [1] == ':'){
1418 return url.Substring (2) + " Namespace";
1421 string s = TypeLookup (url, out match_node);
1422 if (match_node != null)
1423 return match_node.Caption;
1424 return url.Substring (2) + " type";
1432 MemberLookup (url.Substring (0,2), url, out match_node);
1433 if (match_node != null)
1434 return match_node.Caption;
1439 return "Mono Documentation";
1444 /// Allows every HelpSource to try to provide the content for this
1447 public string RenderUrl (string url, out Node match_node)
1449 lastHelpSourceTime = DateTime.MinValue;
1450 if (url == "root:") {
1453 // look whether there are contribs
1454 GlobalChangeset chgs = EditingUtils.changes;
1455 StringBuilder con = new StringBuilder ();
1457 //add links to the contrib
1458 int oldContrib = 0, contribs = 0;
1459 con.Append ("<ul>");
1460 foreach (DocSetChangeset dscs in chgs.DocSetChangesets)
1461 foreach (FileChangeset fcs in dscs.FileChangesets)
1462 foreach (Change c in fcs.Changes) {
1463 if (c.NodeUrl == null) {
1464 if (c.Serial == SettingsHandler.Settings.SerialNumber)
1466 } else if (c.Serial == SettingsHandler.Settings.SerialNumber) {
1468 con.Append (String.Format ("<li><a href=\"{0}\">{0}</a></li>", c.NodeUrl));
1472 string contrib = (oldContrib + contribs) == 1?"There is {0} contribution":"There are {0} contributions";
1473 con.Insert (0, String.Format (contrib, oldContrib + contribs) + " pending upload <i>(Contributing--> Upload)</i>", 1);
1474 con.Append ("</ul>");
1475 if (oldContrib == 1)
1476 con.Append ("<i>You have 1 contribution that is not listed below that will be sent the next time you upload contributions. Only contributions made from now on will be listed.</i>");
1477 else if (oldContrib > 1)
1478 con.Append ("<i>You have " + oldContrib + "contributions that are not listed below and will be sent the next time you upload contributions. Only contributions made from now on will be listed.</i>");
1480 //start the rendering
1481 if (!HelpSource.use_css) {
1482 StringBuilder sb = new StringBuilder ("<table bgcolor=\"#b0c4de\" width=\"100%\" cellpadding=\"5\"><tr><td><h3>Mono Documentation Library</h3></td></tr></table>");
1484 foreach (Node n in Nodes)
1485 sb.AppendFormat ("<a href='{0}'>{1}</a><br/>", n.Element, n.Caption);
1488 sb.Append ("<br><table bgcolor=\"#fff3f3\" width=\"100%\" cellpadding=\"5\"><tr><td>");
1489 sb.Append ("<h5>Contributions</h5><br>");
1490 if ((oldContrib + contribs) == 0) {
1491 sb.Append ("<p><b>You have not made any contributions yet.</b></p>");
1492 sb.Append ("<p>The Documentation of the libraries is not complete and your contributions would be greatly appreciated. The procedure is easy, browse to the part of the documentation you want to contribute to and click on the <font color=\"blue\">[Edit]</font> link to start writing the documentation.</p>");
1493 sb.Append ("<p>When you are happy with your changes, use the Contributing--> Upload Contributions menu to send your contributions to our server.</p></div>");
1495 sb.Append (con.ToString ());
1497 sb.Append ("</td></tr></table>");
1498 return sb.ToString ();
1500 if (home_cache == null) {
1501 System.Reflection.Assembly assembly = System.Reflection.Assembly.GetAssembly (typeof (HelpSource));
1502 Stream hp_stream = assembly.GetManifestResourceStream ("home.html");
1503 home_cache = (new StreamReader (hp_stream)).ReadToEnd ();
1505 StringBuilder sb = new StringBuilder (home_cache);
1507 sb.Replace ("@@FONT_FAMILY@@", SettingsHandler.Settings.preferred_font_family);
1508 sb.Replace ("@@FONT_SIZE@@", SettingsHandler.Settings.preferred_font_size.ToString());
1510 var visible = SettingsHandler.Settings.EnableEditing ? "block;" : "none;";
1511 if ((oldContrib + contribs) == 0) {
1512 sb.Replace ("@@CONTRIB_DISP@@", "display: none;");
1513 sb.Replace ("@@NO_CONTRIB_DISP@@", "display: " + visible);
1515 sb.Replace ("@@CONTRIB_DISP@@", "display: " + visible);
1516 sb.Replace ("@@NO_CONTRIB_DISP@@", "display: none;");
1517 sb.Replace ("@@CONTRIBS@@", con.ToString ());
1519 sb.Replace ("@@EDITING_ENABLED@@", "display: " + visible);
1521 // load the url of nodes
1523 StringBuilder urls = new StringBuilder ();
1524 foreach (Node n in Nodes) {
1525 add_str = String.Format ("<li><a href=\"{0}\">{1}</a></li>", n.Element, n.Caption);
1526 urls.Append (add_str);
1528 sb.Replace ("@@API_DOCS@@", urls.ToString ());
1530 return sb.ToString ();
1534 if (url.StartsWith ("root:")) {
1535 match_node = ((Node)name_to_node [url.Substring (6)]);
1536 HelpSource hs = ((HelpSource)name_to_hs [url.Substring (6)]);
1539 return GenerateNodeIndex(match_node);
1543 lastHelpSourceTime = hs.ZipFileWriteTime;
1544 return hs.GetText ("root:", out dummy);
1548 if (url.StartsWith ("source-id:")){
1549 string rest = url.Substring (10);
1550 int p = rest.IndexOf (":");
1551 string str_idx = rest.Substring (0, p);
1555 idx = Int32.Parse (str_idx);
1557 Console.Error.WriteLine ("Failed to parse source-id url: {0} `{1}'", url, str_idx);
1561 HelpSource hs = (HelpSource) help_sources [idx];
1562 // Console.WriteLine ("Attempting to get docs from: " + rest.Substring (p + 1));
1563 lastHelpSourceTime = hs.ZipFileWriteTime;
1564 return hs.GetText (rest.Substring (p + 1), out match_node);
1567 if (url.Length < 2){
1572 string prefix = url.Substring (0, 2);
1574 switch (prefix.ToUpper ()){
1576 foreach (HelpSource hs in help_sources){
1577 string s = hs.RenderNamespaceLookup (url, out match_node);
1579 lastHelpSourceTime = hs.ZipFileWriteTime;
1587 return TypeLookup (url, out match_node);
1595 return MemberLookup (prefix, url, out match_node);
1598 foreach (HelpSource hs in help_sources){
1599 string s = hs.GetText (url, out match_node);
1602 lastHelpSourceTime = hs.ZipFileWriteTime;
1611 public string GenerateNodeIndex (Node node)
1613 StringBuilder buf = new StringBuilder();
1614 buf.AppendFormat("<H3>{0}</H3>", node.Caption);
1616 foreach (Node child in node.Nodes)
1618 buf.AppendFormat("<li><a href=\"{0}\">{1}</a>", child.URL, child.Caption);
1620 buf.Append("</ul>");
1621 return buf.ToString();
1624 public IndexReader GetIndex ()
1626 //try to load from basedir
1627 string index_file = Path.Combine (basedir, "monodoc.index");
1628 if (File.Exists (index_file))
1629 return IndexReader.Load (index_file);
1630 //then, try to load from config dir
1631 index_file = Path.Combine (SettingsHandler.Path, "monodoc.index");
1632 return IndexReader.Load (index_file);
1636 public static void MakeIndex ()
1638 MakeIndex (LoadTree ());
1641 public static void MakeIndex (RootTree root)
1646 IndexMaker index_maker = new IndexMaker ();
1648 foreach (HelpSource hs in root.help_sources){
1649 hs.PopulateIndex (index_maker);
1652 // if the user has no write permissions use config dir
1653 string path = Path.Combine (root.basedir, "monodoc.index");
1655 index_maker.Save (path);
1656 } catch (System.UnauthorizedAccessException) {
1657 path = Path.Combine (SettingsHandler.Path, "monodoc.index");
1659 index_maker.Save (path);
1660 } catch (System.UnauthorizedAccessException) {
1661 Console.WriteLine ("Unable to write index file in {0}", Path.Combine (SettingsHandler.Path, "monodoc.index"));
1667 // No octal in C#, how lame is that
1668 chmod (path, 0x1a4);
1670 Console.WriteLine ("Documentation index at {0} updated", path);
1673 static bool IsUnix {
1675 int p = (int) Environment.OSVersion.Platform;
1676 return ((p == 4) || (p == 128) || (p == 6));
1681 public SearchableIndex GetSearchIndex ()
1683 //try to load from basedir
1684 string index_file = Path.Combine (basedir, "search_index");
1685 if (Directory.Exists (index_file))
1686 return SearchableIndex.Load (index_file);
1687 //then, try to load from config dir
1688 index_file = Path.Combine (SettingsHandler.Path, "search_index");
1689 return SearchableIndex.Load (index_file);
1692 public static void MakeSearchIndex ()
1694 MakeSearchIndex (LoadTree ());
1697 public static void MakeSearchIndex (RootTree root)
1699 // Loads the RootTree
1700 Console.WriteLine ("Loading the monodoc tree...");
1705 string dir = Path.Combine (root.basedir, "search_index");
1707 //try to create the dir to store the index
1709 if (!Directory.Exists (dir))
1710 Directory.CreateDirectory (dir);
1712 writer = new IndexWriter(Mono.Lucene.Net.Store.FSDirectory.GetDirectory(dir, true), new StandardAnalyzer(), true);
1713 } catch (UnauthorizedAccessException) {
1714 //try in the .config directory
1716 dir = Path.Combine (SettingsHandler.Path, "search_index");
1717 if (!Directory.Exists (dir))
1718 Directory.CreateDirectory (dir);
1720 writer = new IndexWriter(Mono.Lucene.Net.Store.FSDirectory.GetDirectory(dir, true), new StandardAnalyzer(), true);
1721 } catch (UnauthorizedAccessException) {
1722 Console.WriteLine ("You don't have permissions to write on " + dir);
1727 //Collect all the documents
1728 Console.WriteLine ("Collecting and adding documents...");
1729 foreach (HelpSource hs in root.HelpSources)
1730 hs.PopulateSearchableIndex (writer);
1732 //Optimize and close
1733 Console.WriteLine ("Closing...");
1739 public ICollection HelpSources { get { return new ArrayList(help_sources); } }
1741 [System.Runtime.InteropServices.DllImport ("libc")]
1742 static extern int chmod (string filename, int mode);