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 osxExternalDir = "/Library/Frameworks/Mono.framework/External/monodoc";
920 string[] osxExternalSources = Directory.Exists (osxExternalDir)
921 ? Directory.GetFiles (osxExternalDir, "*.source")
924 return LoadTree (basedir, doc,
925 Directory.GetFiles (Path.Combine (basedir, "sources"), "*.source")
926 .Concat (osxExternalSources));
929 // Compatibility shim w/ Mono 2.6
930 public static RootTree LoadTree (string indexDir, XmlDocument docTree, IEnumerable sourceFiles)
932 return LoadTree (indexDir, docTree, sourceFiles.Cast<string>());
935 public static RootTree LoadTree (string indexDir, XmlDocument docTree, IEnumerable<string> sourceFiles)
937 if (docTree == null) {
938 docTree = new XmlDocument ();
939 using (var defTree = typeof(RootTree).Assembly.GetManifestResourceStream ("monodoc.xml"))
940 docTree.Load (defTree);
944 sourceFiles = sourceFiles ?? new string [0];
950 RootTree root = new RootTree ();
951 root.basedir = indexDir;
953 XmlNodeList nodes = docTree.SelectNodes ("/node/node");
955 root.name_to_node ["root"] = root;
956 root.name_to_node ["libraries"] = root;
957 root.Populate (root, nodes);
959 Node third_party = root.LookupEntryPoint ("various");
960 if (third_party == null) {
961 Console.Error.WriteLine ("No 'various' doc node! Check monodoc.xml!");
968 foreach (var sourceFile in sourceFiles)
969 root.AddSourceFile (sourceFile);
971 foreach (string path in UncompiledHelpSources) {
972 EcmaUncompiledHelpSource hs = new EcmaUncompiledHelpSource(path);
974 root.help_sources.Add (hs);
975 string epath = "extra-help-source-" + hs.Name;
976 Node hsn = root.CreateNode (hs.Name, "root:/" + epath);
977 root.name_to_hs [epath] = hs;
979 foreach (Node n in hs.Tree.Nodes){
992 public void AddSource (string sources_dir)
994 string [] files = Directory.GetFiles (sources_dir);
996 foreach (string file in files){
997 if (!file.EndsWith (".source"))
999 AddSourceFile (file);
1003 Dictionary<string,string> loadedSourceFiles = new Dictionary<string,string> ();
1005 public void AddSourceFile (string sourceFile)
1007 if (loadedSourceFiles.ContainsKey (sourceFile))
1010 Node third_party = LookupEntryPoint ("various") ?? this;
1012 XmlDocument doc = new XmlDocument ();
1014 doc.Load (sourceFile);
1017 Console.Error.WriteLine ("Error: Could not load source file {0}", sourceFile);
1021 XmlNodeList extra_nodes = doc.SelectNodes ("/monodoc/node");
1022 if (extra_nodes.Count > 0)
1023 Populate (third_party, extra_nodes);
1025 XmlNodeList sources = doc.SelectNodes ("/monodoc/source");
1026 if (sources == null){
1027 Console.Error.WriteLine ("Error: No <source> section found in the {0} file", sourceFile);
1030 loadedSourceFiles [sourceFile] = sourceFile;
1031 foreach (XmlNode source in sources){
1032 XmlAttribute a = source.Attributes ["provider"];
1034 Console.Error.WriteLine ("Error: no provider in <source>");
1037 string provider = a.InnerText;
1038 a = source.Attributes ["basefile"];
1040 Console.Error.WriteLine ("Error: no basefile in <source>");
1043 string basefile = a.InnerText;
1044 a = source.Attributes ["path"];
1046 Console.Error.WriteLine ("Error: no path in <source>");
1049 string path = a.InnerText;
1051 string basefilepath = Path.Combine (Path.GetDirectoryName (sourceFile), basefile);
1052 HelpSource hs = GetHelpSource (provider, basefilepath);
1056 help_sources.Add (hs);
1057 name_to_hs [path] = hs;
1059 Node parent = LookupEntryPoint (path);
1060 if (parent == null){
1061 Console.Error.WriteLine ("node `{0}' is not defined on the documentation map", path);
1062 parent = third_party;
1065 foreach (Node n in hs.Tree.Nodes){
1072 // Delete nodes which does not have documentaiton (source)
1073 static bool PurgeNode(Node node)
1077 if (!node.Documented)
1079 ArrayList del_child = new ArrayList();
1080 //Delete node unless any child has documentation
1081 bool purged_child = false;
1082 foreach (Node child in node.Nodes)
1084 purged_child = PurgeNode(child);
1087 del_child.Add(child);
1091 // delete the node if all its children are to be deleted
1092 purge = (node.Nodes.Count == del_child.Count);
1095 foreach (Node child in del_child)
1097 node.DelNode(child);
1104 public static string[] GetSupportedFormats ()
1106 return new string[]{
1117 public static HelpSource GetHelpSource (string provider, string basefilepath)
1122 return new EcmaHelpSource (basefilepath, false);
1123 case "ecma-uncompiled":
1124 return new EcmaUncompiledHelpSource (basefilepath);
1126 return new MonoHBHelpSource(basefilepath, false);
1127 case "xhtml": case "hb":
1128 return new XhtmlHelpSource (basefilepath, false);
1130 return new ManHelpSource (basefilepath, false);
1132 return new SimpleHelpSource (basefilepath, false);
1134 return new ErrorHelpSource (basefilepath, false);
1136 return new EcmaSpecHelpSource (basefilepath, false);
1138 return new AddinsHelpSource (basefilepath, false);
1140 Console.Error.WriteLine ("Error: Unknown provider specified: {0}", provider);
1145 catch (FileNotFoundException) {
1146 Console.Error.WriteLine ("Error: did not find one of the files in sources/"+basefilepath);
1151 public static Provider GetProvider (string provider, params string[] basefilepaths)
1155 return new AddinsProvider (basefilepaths [0]);
1157 EcmaProvider p = new EcmaProvider ();
1158 foreach (string d in basefilepaths)
1163 return new EcmaSpecProvider (basefilepaths [0]);
1165 return new ErrorProvider (basefilepaths [0]);
1167 return new ManProvider (basefilepaths);
1169 return new SimpleProvider (basefilepaths [0]);
1172 return new XhtmlProvider (basefilepaths [0]);
1174 throw new NotSupportedException (provider);
1179 // Maintains the name to node mapping
1181 Hashtable name_to_node = new Hashtable ();
1182 Hashtable name_to_hs = new Hashtable ();
1184 void Populate (Node parent, XmlNodeList xml_node_list)
1186 foreach (XmlNode xml_node in xml_node_list){
1187 XmlAttribute e = xml_node.Attributes ["parent"];
1188 if (e != null && name_to_node.ContainsKey (e.InnerText)) {
1189 Node p = (Node) name_to_node [e.InnerText];
1190 xml_node.Attributes.Remove (e);
1191 Populate (p, xml_node.SelectNodes ("."));
1194 e = xml_node.Attributes ["label"];
1196 Console.Error.WriteLine ("`label' attribute missing in <node>");
1199 string label = e.InnerText;
1200 e = xml_node.Attributes ["name"];
1202 Console.Error.WriteLine ("`name' attribute missing in <node>");
1205 string name = e.InnerText;
1207 Node n = parent.LookupNode (label, "root:/" + name);
1209 name_to_node [name] = n;
1210 XmlNodeList children = xml_node.SelectNodes ("./node");
1211 if (children != null)
1212 Populate (n, children);
1216 public Node LookupEntryPoint (string name)
1218 return (Node) name_to_node [name];
1221 ArrayList help_sources;
1222 DateTime lastHelpSourceTime;
1224 RootTree () : base (null, "Mono Documentation", "root:")
1226 nodes = new ArrayList ();
1227 help_sources = new ArrayList ();
1228 lastHelpSourceTime = DateTime.MinValue;
1231 public DateTime LastHelpSourceTime {
1233 return lastHelpSourceTime;
1237 public static bool GetNamespaceAndType (string url, out string ns, out string type)
1241 for (int i = 0; i < url.Length; ++i) {
1264 ns = url.Substring (0, nsidx);
1265 type = url.Substring (nsidx + 1);
1267 //Console.Error.WriteLine ("GetNameSpaceAndType (ns={0}, type={1}", ns, type);
1271 public XmlDocument GetHelpXml (string url)
1273 string rest = url.Substring (2);
1276 if (!GetNamespaceAndType (rest, out ns, out type))
1279 foreach (HelpSource hs in help_sources) {
1280 EcmaHelpSource ehs = hs as EcmaHelpSource;
1283 string id = ehs.GetIdFromUrl ("T:", ns, type);
1286 XmlDocument doc = hs.GetHelpXmlWithChanges (id);
1293 public string TypeLookup (string url, out Node match_node)
1295 string rest = Regex.Replace (url, @"^T:\s*", "");
1298 if (!GetNamespaceAndType (rest, out ns, out type)){
1303 foreach (HelpSource hs in help_sources){
1304 string s = hs.RenderTypeLookup ("T:", ns, type, null, out match_node);
1307 lastHelpSourceTime = hs.ZipFileWriteTime;
1315 public string MemberLookup (string prefix, string url, out Node match_node)
1317 string rest = Regex.Replace (url, @"^.:\s*", "");
1319 // Dots in the arg list (for methods) confuse this.
1320 // Chop off the arg list for now and put it back later.
1321 string arglist = "";
1322 int argliststart = rest.IndexOf("(");
1323 if (argliststart >= 0) {
1324 arglist = rest.Substring(argliststart);
1325 rest = rest.Substring(0, argliststart);
1328 string ns_type, member;
1330 if (prefix != "C:") {
1331 int member_idx = rest.LastIndexOf (".");
1333 // The dot in .ctor (if it's a M: link) would confuse this.
1334 if (rest.EndsWith("..ctor")) member_idx--;
1336 ns_type = rest.Substring (0, member_idx);
1337 member = rest.Substring (member_idx + 1);
1339 // C: links don't have the .ctor member part as it would in a M: link
1340 // Even though externally C: links are different from M: links,
1341 // C: links get transformed into M:-style links (with .ctor) here.
1347 //Console.WriteLine ("NS_TYPE: {0} MEMBER: {1}", ns_type, member);
1350 if (!GetNamespaceAndType (ns_type, out ns, out type)){
1355 foreach (HelpSource hs in help_sources){
1356 string s = hs.RenderTypeLookup (prefix, ns, type, member + arglist, out match_node);
1359 lastHelpSourceTime = hs.ZipFileWriteTime;
1367 public Stream GetImage (string url)
1369 if (url.StartsWith ("source-id:")){
1370 string rest = url.Substring (10);
1371 int p = rest.IndexOf (":");
1372 string str_idx = rest.Substring (0, p);
1376 idx = Int32.Parse (str_idx);
1378 Console.Error.WriteLine ("Failed to parse source-id url: {0} `{1}'", url, str_idx);
1382 HelpSource hs = GetHelpSourceFromId (idx);
1383 lastHelpSourceTime = hs.ZipFileWriteTime;
1384 return hs.GetImage (rest.Substring (p + 1));
1386 System.Reflection.Assembly assembly = System.Reflection.Assembly.GetAssembly (typeof(RootTree));
1387 return assembly.GetManifestResourceStream (url);
1389 lastHelpSourceTime = DateTime.MinValue;
1393 public HelpSource GetHelpSourceFromId (int id)
1395 return (HelpSource) help_sources [id];
1399 // Fetches the node title
1401 public string GetTitle (string url)
1405 if (url == null || url.StartsWith ("root:"))
1406 return "Mono Documentation";
1408 if (url.Length > 2 && url [1] == ':'){
1411 return url.Substring (2) + " Namespace";
1414 string s = TypeLookup (url, out match_node);
1415 if (match_node != null)
1416 return match_node.Caption;
1417 return url.Substring (2) + " type";
1425 MemberLookup (url.Substring (0,2), url, out match_node);
1426 if (match_node != null)
1427 return match_node.Caption;
1432 return "Mono Documentation";
1437 /// Allows every HelpSource to try to provide the content for this
1440 public string RenderUrl (string url, out Node match_node)
1442 lastHelpSourceTime = DateTime.MinValue;
1443 if (url == "root:") {
1446 // look whether there are contribs
1447 GlobalChangeset chgs = EditingUtils.changes;
1448 StringBuilder con = new StringBuilder ();
1450 //add links to the contrib
1451 int oldContrib = 0, contribs = 0;
1452 con.Append ("<ul>");
1453 foreach (DocSetChangeset dscs in chgs.DocSetChangesets)
1454 foreach (FileChangeset fcs in dscs.FileChangesets)
1455 foreach (Change c in fcs.Changes) {
1456 if (c.NodeUrl == null) {
1457 if (c.Serial == SettingsHandler.Settings.SerialNumber)
1459 } else if (c.Serial == SettingsHandler.Settings.SerialNumber) {
1461 con.Append (String.Format ("<li><a href=\"{0}\">{0}</a></li>", c.NodeUrl));
1465 string contrib = (oldContrib + contribs) == 1?"There is {0} contribution":"There are {0} contributions";
1466 con.Insert (0, String.Format (contrib, oldContrib + contribs) + " pending upload <i>(Contributing--> Upload)</i>", 1);
1467 con.Append ("</ul>");
1468 if (oldContrib == 1)
1469 con.Append ("<i>You have 1 contribution that is not listed below that will be sent the next time you upload contributions. Only contributions made from now on will be listed.</i>");
1470 else if (oldContrib > 1)
1471 con.Append ("<i>You have " + oldContrib + "contributions that are not listed below and will be sent the next time you upload contributions. Only contributions made from now on will be listed.</i>");
1473 //start the rendering
1474 if (!HelpSource.use_css) {
1475 StringBuilder sb = new StringBuilder ("<table bgcolor=\"#b0c4de\" width=\"100%\" cellpadding=\"5\"><tr><td><h3>Mono Documentation Library</h3></td></tr></table>");
1477 foreach (Node n in Nodes)
1478 sb.AppendFormat ("<a href='{0}'>{1}</a><br/>", n.Element, n.Caption);
1481 sb.Append ("<br><table bgcolor=\"#fff3f3\" width=\"100%\" cellpadding=\"5\"><tr><td>");
1482 sb.Append ("<h5>Contributions</h5><br>");
1483 if ((oldContrib + contribs) == 0) {
1484 sb.Append ("<p><b>You have not made any contributions yet.</b></p>");
1485 sb.Append ("<p>The Documentation of the libraries is not complete and your contributions would be greatly appreciated. The procedure is easy, browse to the part of the documentation you want to contribute to and click on the <font color=\"blue\">[Edit]</font> link to start writing the documentation.</p>");
1486 sb.Append ("<p>When you are happy with your changes, use the Contributing--> Upload Contributions menu to send your contributions to our server.</p></div>");
1488 sb.Append (con.ToString ());
1490 sb.Append ("</td></tr></table>");
1491 return sb.ToString ();
1493 if (home_cache == null) {
1494 System.Reflection.Assembly assembly = System.Reflection.Assembly.GetAssembly (typeof (HelpSource));
1495 Stream hp_stream = assembly.GetManifestResourceStream ("home.html");
1496 home_cache = (new StreamReader (hp_stream)).ReadToEnd ();
1498 StringBuilder sb = new StringBuilder (home_cache);
1500 sb.Replace ("@@FONT_FAMILY@@", SettingsHandler.Settings.preferred_font_family);
1501 sb.Replace ("@@FONT_SIZE@@", SettingsHandler.Settings.preferred_font_size.ToString());
1503 var visible = SettingsHandler.Settings.EnableEditing ? "block;" : "none;";
1504 if ((oldContrib + contribs) == 0) {
1505 sb.Replace ("@@CONTRIB_DISP@@", "display: none;");
1506 sb.Replace ("@@NO_CONTRIB_DISP@@", "display: " + visible);
1508 sb.Replace ("@@CONTRIB_DISP@@", "display: " + visible);
1509 sb.Replace ("@@NO_CONTRIB_DISP@@", "display: none;");
1510 sb.Replace ("@@CONTRIBS@@", con.ToString ());
1512 sb.Replace ("@@EDITING_ENABLED@@", "display: " + visible);
1514 // load the url of nodes
1516 StringBuilder urls = new StringBuilder ();
1517 foreach (Node n in Nodes) {
1518 add_str = String.Format ("<li><a href=\"{0}\">{1}</a></li>", n.Element, n.Caption);
1519 urls.Append (add_str);
1521 sb.Replace ("@@API_DOCS@@", urls.ToString ());
1523 return sb.ToString ();
1527 if (url.StartsWith ("root:")) {
1528 match_node = ((Node)name_to_node [url.Substring (6)]);
1529 HelpSource hs = ((HelpSource)name_to_hs [url.Substring (6)]);
1532 return GenerateNodeIndex(match_node);
1536 lastHelpSourceTime = hs.ZipFileWriteTime;
1537 return hs.GetText ("root:", out dummy);
1541 if (url.StartsWith ("source-id:")){
1542 string rest = url.Substring (10);
1543 int p = rest.IndexOf (":");
1544 string str_idx = rest.Substring (0, p);
1548 idx = Int32.Parse (str_idx);
1550 Console.Error.WriteLine ("Failed to parse source-id url: {0} `{1}'", url, str_idx);
1554 HelpSource hs = (HelpSource) help_sources [idx];
1555 // Console.WriteLine ("Attempting to get docs from: " + rest.Substring (p + 1));
1556 lastHelpSourceTime = hs.ZipFileWriteTime;
1557 return hs.GetText (rest.Substring (p + 1), out match_node);
1560 if (url.Length < 2){
1565 string prefix = url.Substring (0, 2);
1567 switch (prefix.ToUpper ()){
1569 foreach (HelpSource hs in help_sources){
1570 string s = hs.RenderNamespaceLookup (url, out match_node);
1572 lastHelpSourceTime = hs.ZipFileWriteTime;
1580 return TypeLookup (url, out match_node);
1588 return MemberLookup (prefix, url, out match_node);
1591 foreach (HelpSource hs in help_sources){
1592 string s = hs.GetText (url, out match_node);
1595 lastHelpSourceTime = hs.ZipFileWriteTime;
1604 public string GenerateNodeIndex (Node node)
1606 StringBuilder buf = new StringBuilder();
1607 buf.AppendFormat("<H3>{0}</H3>", node.Caption);
1609 foreach (Node child in node.Nodes)
1611 buf.AppendFormat("<li><a href=\"{0}\">{1}</a>", child.URL, child.Caption);
1613 buf.Append("</ul>");
1614 return buf.ToString();
1617 public IndexReader GetIndex ()
1619 //try to load from basedir
1620 string index_file = Path.Combine (basedir, "monodoc.index");
1621 if (File.Exists (index_file))
1622 return IndexReader.Load (index_file);
1623 //then, try to load from config dir
1624 index_file = Path.Combine (SettingsHandler.Path, "monodoc.index");
1625 return IndexReader.Load (index_file);
1629 public static void MakeIndex ()
1631 MakeIndex (LoadTree ());
1634 public static void MakeIndex (RootTree root)
1639 IndexMaker index_maker = new IndexMaker ();
1641 foreach (HelpSource hs in root.help_sources){
1642 hs.PopulateIndex (index_maker);
1645 // if the user has no write permissions use config dir
1646 string path = Path.Combine (root.basedir, "monodoc.index");
1648 index_maker.Save (path);
1649 } catch (System.UnauthorizedAccessException) {
1650 path = Path.Combine (SettingsHandler.Path, "monodoc.index");
1652 index_maker.Save (path);
1653 } catch (System.UnauthorizedAccessException) {
1654 Console.WriteLine ("Unable to write index file in {0}", Path.Combine (SettingsHandler.Path, "monodoc.index"));
1660 // No octal in C#, how lame is that
1661 chmod (path, 0x1a4);
1663 Console.WriteLine ("Documentation index at {0} updated", path);
1666 static bool IsUnix {
1668 int p = (int) Environment.OSVersion.Platform;
1669 return ((p == 4) || (p == 128) || (p == 6));
1674 public SearchableIndex GetSearchIndex ()
1676 //try to load from basedir
1677 string index_file = Path.Combine (basedir, "search_index");
1678 if (Directory.Exists (index_file))
1679 return SearchableIndex.Load (index_file);
1680 //then, try to load from config dir
1681 index_file = Path.Combine (SettingsHandler.Path, "search_index");
1682 return SearchableIndex.Load (index_file);
1685 public static void MakeSearchIndex ()
1687 MakeSearchIndex (LoadTree ());
1690 public static void MakeSearchIndex (RootTree root)
1692 // Loads the RootTree
1693 Console.WriteLine ("Loading the monodoc tree...");
1698 string dir = Path.Combine (root.basedir, "search_index");
1700 //try to create the dir to store the index
1702 if (!Directory.Exists (dir))
1703 Directory.CreateDirectory (dir);
1705 writer = new IndexWriter(Mono.Lucene.Net.Store.FSDirectory.GetDirectory(dir, true), new StandardAnalyzer(), true);
1706 } catch (UnauthorizedAccessException) {
1707 //try in the .config directory
1709 dir = Path.Combine (SettingsHandler.Path, "search_index");
1710 if (!Directory.Exists (dir))
1711 Directory.CreateDirectory (dir);
1713 writer = new IndexWriter(Mono.Lucene.Net.Store.FSDirectory.GetDirectory(dir, true), new StandardAnalyzer(), true);
1714 } catch (UnauthorizedAccessException) {
1715 Console.WriteLine ("You don't have permissions to write on " + dir);
1720 //Collect all the documents
1721 Console.WriteLine ("Collecting and adding documents...");
1722 foreach (HelpSource hs in root.HelpSources)
1723 hs.PopulateSearchableIndex (writer);
1725 //Optimize and close
1726 Console.WriteLine ("Closing...");
1732 public ICollection HelpSources { get { return new ArrayList(help_sources); } }
1734 [System.Runtime.InteropServices.DllImport ("libc")]
1735 static extern int chmod (string filename, int mode);