2 // Provider: shared code and interfaces for providers
5 // Miguel de Icaza (miguel@ximian.com)
7 // (C) 2002, Ximian, Inc.
10 // Each node should have a provider link
12 // Should encode numbers using a runlength encoding to save space
16 using System.Collections.Generic;
20 using System.Text.RegularExpressions;
21 using System.Collections;
22 using System.Diagnostics;
23 using System.Configuration;
24 using System.Reflection;
25 using System.Text.RegularExpressions;
27 using System.Xml.XPath;
28 using ICSharpCode.SharpZipLib.Zip;
30 using Mono.Lucene.Net.Index;
31 using Mono.Lucene.Net.Analysis.Standard;
33 using Mono.Documentation;
36 /// This tree is populated by the documentation providers, or populated
37 /// from a binary encoding of the tree. The format of the tree is designed
38 /// to minimize the need to load it in full.
40 public class Tree : Node {
42 #region Loading the tree from a file
45 /// Our HelpSource container
47 public readonly HelpSource HelpSource;
49 internal FileStream InputStream;
50 internal BinaryReader InputReader;
53 /// Load from file constructor
55 public Tree (HelpSource hs, string filename) : base (null, null)
57 Encoding utf8 = new UTF8Encoding (false, true);
59 if (!File.Exists (filename)){
60 throw new FileNotFoundException ();
63 InputStream = File.OpenRead (filename);
64 InputReader = new BinaryReader (InputStream, utf8);
65 byte [] sig = InputReader.ReadBytes (4);
68 throw new Exception ("Invalid file format");
70 InputStream.Position = 4;
71 position = InputReader.ReadInt32 ();
78 /// Tree creation and merged tree constructor
80 public Tree (HelpSource hs, string caption, string url) : base (caption, url)
85 public Tree (HelpSource hs, Node parent, string caption, string element) : base (parent, caption, element)
93 /// Saves the tree into the specified file using the help file format.
95 public void Save (string file)
97 Encoding utf8 = new UTF8Encoding (false, true);
98 using (FileStream output = File.OpenWrite (file)){
99 // Skip over the pointer to the first node.
102 using (BinaryWriter writer = new BinaryWriter (output, utf8)){
104 Dump (output, writer);
107 writer.Write (new byte [] { (byte) 'M', (byte) 'o', (byte) 'H', (byte) 'P' });
108 writer.Write (position);
113 static bool GoodSig (byte [] sig)
117 if (sig [0] != (byte) 'M' ||
118 sig [1] != (byte) 'o' ||
119 sig [2] != (byte) 'H' ||
120 sig [3] != (byte) 'P')
127 public class Node : IComparable {
128 string caption, element;
129 public bool Documented;
130 public readonly Tree tree;
132 protected ArrayList nodes;
133 protected internal int position;
135 static ArrayList empty = ArrayList.ReadOnly(new ArrayList(0));
138 /// Creates a node, called by the Tree.
140 public Node (string caption, string element)
142 this.tree = (Tree) this;
143 this.caption = caption;
144 this.element = element;
148 public Node (Node parent, string caption, string element)
150 this.parent = parent;
151 this.tree = parent.tree;
152 this.caption = caption;
153 this.element = element;
157 /// Creates a node from an on-disk representation
159 Node (Node parent, int address)
161 this.parent = parent;
163 this.tree = parent.tree;
168 public void AddNode (Node n)
175 public void DelNode (Node n)
180 public ArrayList Nodes {
184 return nodes != null ? nodes : empty;
188 public string Element {
200 public string Caption {
214 public void LoadNode ()
217 position = -position;
219 tree.InputStream.Position = position;
220 BinaryReader reader = tree.InputReader;
221 int count = DecodeInt (reader);
222 element = reader.ReadString ();
223 caption = reader.ReadString ();
227 nodes = new ArrayList (count);
228 for (int i = 0; i < count; i++){
229 int child_address = DecodeInt (reader);
231 Node t = new Node (this, -child_address);
237 /// Creates a new node, in the locator entry point, and with
238 /// a user visible caption of @caption
240 public Node CreateNode (string c_caption, string c_element)
243 nodes = new ArrayList ();
245 Node t = new Node (this, c_caption, c_element);
251 /// Looks up or creates a new node, in the locator entry point, and with
252 /// a user visible caption of @caption. This is different from
253 /// CreateNode in that it will look up an existing node for the given @locator.
255 public Node LookupNode (string c_caption, string c_element)
258 return CreateNode (c_caption, c_element);
260 foreach (Node n in nodes){
261 if (n.element == c_element)
264 return CreateNode (c_caption, c_element);
267 public void EnsureNodes ()
270 nodes = new ArrayList ();
275 return nodes == null;
279 void EncodeInt (BinaryWriter writer, int value)
282 int high = (value >> 7) & 0x01ffffff;
283 byte b = (byte)(value & 0x7f);
286 b = (byte)(b | 0x80);
294 int DecodeInt (BinaryReader reader)
301 b = reader.ReadByte();
303 ret = ret | ((b & 0x7f) << shift);
305 } while ((b & 0x80) == 0x80);
310 internal void Dump (FileStream output, BinaryWriter writer)
313 foreach (Node child in nodes){
314 child.Dump (output, writer);
317 position = (int) output.Position;
318 EncodeInt (writer, nodes == null ? 0 : (int) nodes.Count);
319 writer.Write (element);
320 writer.Write (caption);
323 foreach (Node child in nodes){
324 EncodeInt (writer, child.position);
331 static void Indent ()
333 for (int i = 0; i < indent; i++)
337 public static void PrintTree (Node node)
340 Console.WriteLine ("{0},{1}\t[PublicUrl: {2}]", node.Element, node.Caption, node.PublicUrl);
341 if (node.Nodes.Count == 0)
345 foreach (Node n in node.Nodes)
356 [Obsolete("Use PublicUrl")]
362 if (element.IndexOf (":") >= 0)
366 string url = parent.URL;
368 if (url.EndsWith ("/"))
369 return url + element;
371 return parent.URL + "/" + element;
377 public string PublicUrl {
379 return tree.HelpSource != null
380 ? tree.HelpSource.GetPublicUrl (URL)
385 int IComparable.CompareTo (object obj)
387 Node other = obj as Node;
393 if (other.position < 0)
396 Regex digits = new Regex (@"([\d]+)|([^\d]+)");
397 MatchEvaluator eval = delegate (Match m) {
398 return (m.Value.Length > 0 && char.IsDigit (m.Value [0]))
399 ? m.Value.PadLeft (System.Math.Max (caption.Length, other.caption.Length))
402 return digits.Replace (caption, eval).CompareTo (digits.Replace (other.caption, eval));
407 // The HelpSource class keeps track of the archived data, and its
410 public class HelpSource {
412 public static bool use_css = false;
413 public static string css_code;
414 public static string CssCode {
416 if (css_code != null)
419 System.Reflection.Assembly assembly = System.Reflection.Assembly.GetCallingAssembly ();
420 Stream str_css = assembly.GetManifestResourceStream ("base.css");
421 StringBuilder sb = new StringBuilder ((new StreamReader (str_css)).ReadToEnd());
422 sb.Replace ("@@FONT_FAMILY@@", SettingsHandler.Settings.preferred_font_family);
423 sb.Replace ("@@FONT_SIZE@@", SettingsHandler.Settings.preferred_font_size.ToString());
424 css_code = sb.ToString ();
427 set { css_code = value; }
430 public virtual string InlineCss {
431 get { return CssCode; }
434 public virtual string InlineJavaScript {
438 public static bool FullHtml = true;
440 // should only be enabled by ASP.NET webdoc
441 public static bool UseWebdocCache;
444 // The unique ID for this HelpSource.
447 DateTime zipFileWriteTime;
449 TraceLevel trace_level = TraceLevel.Warning;
450 protected bool nozip;
451 protected string base_dir;
453 public HelpSource (string base_filename, bool create)
455 this.name = Path.GetFileName (base_filename);
456 tree_filename = base_filename + ".tree";
457 zip_filename = base_filename + ".zip";
458 base_dir = XmlDocUtils.GetCacheDirectory (base_filename);
459 if (UseWebdocCache && !create && Directory.Exists (base_dir)) {
466 Tree = new Tree (this, tree_filename);
470 FileInfo fi = new FileInfo (zip_filename);
471 zipFileWriteTime = fi.LastWriteTime;
473 zipFileWriteTime = DateTime.Now;
477 public HelpSource() {
478 Tree = new Tree (this, "Blah", "Blah");
482 public DateTime ZipFileWriteTime {
484 return zipFileWriteTime;
488 public int SourceID {
500 public TraceLevel TraceLevel {
501 get { return trace_level; }
502 set { trace_level = value; }
505 public string BaseDir {
514 /// Returns a stream from the packaged help source archive
516 public virtual Stream GetHelpStream (string id)
519 string path = XmlDocUtils.GetCachedFileName (base_dir, id);
520 if (File.Exists (path))
521 return File.OpenRead (path);
525 if (zip_file == null)
526 zip_file = new ZipFile (zip_filename);
528 ZipEntry entry = zip_file.GetEntry (id);
530 return zip_file.GetInputStream (entry);
534 public string GetRealPath (string file)
536 if (zip_file == null)
537 zip_file = new ZipFile (zip_filename);
539 ZipEntry entry = zip_file.GetEntry (file);
540 if (entry != null && entry.ExtraData != null)
541 return ConvertToString (entry.ExtraData);
545 public XmlReader GetHelpXml (string id)
548 Stream s = File.OpenRead (XmlDocUtils.GetCachedFileName (base_dir, id));
549 string url = "monodoc:///" + SourceID + "@" + System.Web.HttpUtility.UrlEncode (id) + "@";
550 return new XmlTextReader (url, s);
553 if (zip_file == null)
554 zip_file = new ZipFile (zip_filename);
556 ZipEntry entry = zip_file.GetEntry (id);
558 Stream s = zip_file.GetInputStream (entry);
559 string url = "monodoc:///" + SourceID + "@" + System.Web.HttpUtility.UrlEncode (id) + "@";
560 return new XmlTextReader (url, s);
565 public virtual XmlDocument GetHelpXmlWithChanges (string id)
568 Stream s = File.OpenRead (XmlDocUtils.GetCachedFileName (base_dir, id));
569 string url = "monodoc:///" + SourceID + "@" + System.Web.HttpUtility.UrlEncode (id) + "@";
570 XmlReader r = new XmlTextReader (url, s);
571 XmlDocument ret = new XmlDocument ();
576 if (zip_file == null)
577 zip_file = new ZipFile (zip_filename);
579 ZipEntry entry = zip_file.GetEntry (id);
581 Stream s = zip_file.GetInputStream (entry);
582 string url = "monodoc:///" + SourceID + "@" + System.Web.HttpUtility.UrlEncode (id) + "@";
583 XmlReader r = new XmlTextReader (url, s);
584 XmlDocument ret = new XmlDocument ();
587 if (entry.ExtraData != null)
588 EditingUtils.AccountForChanges (ret, Name, ConvertToString (entry.ExtraData));
596 /// Get a nice, unique expression for any XPath node that you get.
597 /// This function is used by editing to get the expression to put
598 /// on to the file. The idea is to create an expression that is resistant
599 /// to changes in the structure of the XML.
601 public virtual string GetNodeXPath (XPathNavigator n)
603 return EditingUtils.GetXPath (n.Clone ());
606 public string GetEditUri (XPathNavigator n)
608 return EditingUtils.FormatEditUri (n.BaseURI, GetNodeXPath (n));
611 static string ConvertToString (byte[] data)
613 return Encoding.UTF8.GetString(data);
616 static byte[] ConvertToArray (string str)
618 return Encoding.UTF8.GetBytes(str);
622 /// The tree that is being populated
625 public RootTree RootTree;
627 // Base filename used by this HelpSource.
628 string tree_filename, zip_filename;
631 const int buffer_size = 65536;
632 ZipOutputStream zip_output;
635 HelpSource (string base_filename)
639 void SetupForOutput ()
641 Tree = new Tree (this, "", "");
643 FileStream stream = File.Create (zip_filename);
645 zip_output = new ZipOutputStream (stream);
646 zip_output.SetLevel (9);
648 buffer = new byte [buffer_size];
652 /// Saves the tree and the archive
656 Tree.Save (tree_filename);
657 zip_output.Finish ();
665 return String.Format ("{0}", code++);
669 /// Providers call this to store a file they will need, and the return value
670 /// is the name that was assigned to it
672 public string PackFile (string file)
674 string entry_name = GetNewCode ();
675 return PackFile (file, entry_name);
678 public string PackFile (string file, string entry_name)
680 using (FileStream input = File.OpenRead (file)) {
681 PackStream (input, entry_name, file);
687 public void PackStream (Stream s, string entry_name)
689 PackStream (s, entry_name, null);
692 void PackStream (Stream s, string entry_name, string realPath)
694 ZipEntry entry = new ZipEntry (entry_name);
696 if (realPath != null)
697 entry.ExtraData = ConvertToArray (realPath);
699 zip_output.PutNextEntry (entry);
702 while ((n = s.Read (buffer, 0, buffer_size)) > 0){
703 zip_output.Write (buffer, 0, n);
707 public void PackXml (string fname, XmlDocument doc, string real_path)
709 ZipEntry entry = new ZipEntry (fname);
710 if (real_path != null)
711 entry.ExtraData = ConvertToArray(real_path);
713 zip_output.PutNextEntry (entry);
714 XmlTextWriter xmlWriter = new XmlTextWriter (zip_output, Encoding.UTF8);
715 doc.WriteContentTo (xmlWriter);
719 public virtual void RenderPreviewDocs (XmlNode newNode, XmlWriter writer)
721 throw new NotImplementedException ();
724 public virtual string GetPublicUrl (string id)
729 public virtual string GetText (string url, out Node n)
735 protected string GetCachedText (string url)
739 string file = XmlDocUtils.GetCachedFileName (base_dir, url);
740 if (!File.Exists (file))
742 return File.OpenText (file).ReadToEnd ();
745 public virtual Stream GetImage (string url)
751 // Default method implementation does not satisfy the request
753 public virtual string RenderTypeLookup (string prefix, string ns, string type, string member, out Node n)
759 public virtual string RenderNamespaceLookup (string nsurl, out Node n)
766 // Populates the index.
768 public virtual void PopulateIndex (IndexMaker index_maker)
773 // Build an html document
775 public static string BuildHtml (string css, string html_code)
777 return BuildHtml (css, null, html_code);
780 internal static string BuildHtml (string css, string js, string html_code) {
784 StringWriter output = new StringWriter ();
785 output.Write ("<html><head>");
786 output.Write ("<style type=\"text/css\">");
787 output.Write (CssCode);
789 output.Write ("</style>");
791 System.Reflection.Assembly assembly = System.Reflection.Assembly.GetCallingAssembly ();
792 Stream str_js = assembly.GetManifestResourceStream ("helper.js");
793 StringBuilder sb = new StringBuilder ((new StreamReader (str_js)).ReadToEnd());
794 output.Write ("<script type=\"text/JavaScript\">\n");
795 output.Write (sb.ToString ());
796 output.Write ("</script>\n");
799 output.Write ("<script type=\"text/JavaScript\">\n");
801 output.Write ("\n</script>");
804 output.Write ("</head><body>");
805 output.Write (html_code);
806 output.Write ("</body></html>");
807 return output.ToString ();
811 // Create different Documents for adding to Lucene search index
812 // The default action is do nothing. Subclasses should add the docs
814 public virtual void PopulateSearchableIndex (IndexWriter writer) {
818 public void Message (TraceLevel level, string format, params object[] args)
820 if ((int) level <= (int) trace_level)
821 Console.WriteLine (format, args);
824 public void Error (string format, params object[] args)
826 Console.Error.WriteLine (format, args);
830 public abstract class Provider {
832 // This code is used to "tag" all the different sources
843 public abstract void PopulateTree (Tree tree);
846 // Called at shutdown time after the tree has been populated to perform
847 // any fixups or final tasks.
849 public abstract void CloseTree (HelpSource hs, Tree tree);
852 public class RootTree : Tree {
855 public static ArrayList UncompiledHelpSources = new ArrayList();
857 public const int MonodocVersion = 1;
859 public static RootTree LoadTree ()
861 return LoadTree (null);
864 const string MacMonoDocDir = "/Library/Frameworks/Mono.framework/Versions/Current/lib/monodoc";
867 // Loads the tree layout
869 public static RootTree LoadTree (string basedir)
871 if (basedir == null) {
872 string myPath = System.Reflection.Assembly.GetExecutingAssembly ().Location;
873 string cfgFile = myPath + ".config";
874 if (!File.Exists (cfgFile)) {
878 XmlDocument d = new XmlDocument ();
880 basedir = d.SelectSingleNode ("config/path").Attributes ["docsPath"].Value;
882 // Temporary workaround for developers distributing a monodoc.dll themselves on Mac
883 if (Directory.Exists (MacMonoDocDir)){
884 Console.WriteLine ("MacDir exists");
885 if (!File.Exists (Path.Combine (basedir, "monodoc.xml"))){
886 basedir = MacMonoDocDir;
890 Console.WriteLine ("Basedir={0}", basedir);
895 XmlDocument doc = new XmlDocument ();
896 string layout = Path.Combine (basedir, "monodoc.xml");
899 string osxExternalDir = "/Library/Frameworks/Mono.framework/External/monodoc";
900 string[] osxExternalSources = Directory.Exists (osxExternalDir)
901 ? Directory.GetFiles (osxExternalDir, "*.source")
904 return LoadTree (basedir, doc,
905 Directory.GetFiles (Path.Combine (basedir, "sources"), "*.source")
906 .Concat (osxExternalSources));
910 // Compatibility shim w/ Mono 2.6
911 public static RootTree LoadTree (string indexDir, XmlDocument docTree, IEnumerable sourceFiles)
913 return LoadTree (indexDir, docTree, sourceFiles.Cast<string>());
916 public static RootTree LoadTree (string indexDir, XmlDocument docTree, IEnumerable<string> sourceFiles)
918 if (docTree == null) {
919 docTree = new XmlDocument ();
920 using (var defTree = typeof(RootTree).Assembly.GetManifestResourceStream ("monodoc.xml"))
921 docTree.Load (defTree);
925 sourceFiles = sourceFiles ?? new string [0];
931 RootTree root = new RootTree ();
932 root.basedir = indexDir;
934 XmlNodeList nodes = docTree.SelectNodes ("/node/node");
936 root.name_to_node ["root"] = root;
937 root.name_to_node ["libraries"] = root;
938 root.Populate (root, nodes);
940 Node third_party = root.LookupEntryPoint ("various");
941 if (third_party == null) {
942 Console.Error.WriteLine ("No 'various' doc node! Check monodoc.xml!");
949 foreach (var sourceFile in sourceFiles){
950 root.AddSourceFile (sourceFile);
953 foreach (string path in UncompiledHelpSources) {
954 EcmaUncompiledHelpSource hs = new EcmaUncompiledHelpSource(path);
956 root.help_sources.Add (hs);
957 string epath = "extra-help-source-" + hs.Name;
958 Node hsn = root.CreateNode (hs.Name, "root:/" + epath);
959 root.name_to_hs [epath] = hs;
961 foreach (Node n in hs.Tree.Nodes){
974 public void AddSource (string sources_dir)
976 string [] files = Directory.GetFiles (sources_dir);
978 foreach (string file in files){
979 if (!file.EndsWith (".source"))
981 AddSourceFile (file);
985 Dictionary<string,string> loadedSourceFiles = new Dictionary<string,string> ();
987 public void AddSourceFile (string sourceFile)
989 if (loadedSourceFiles.ContainsKey (sourceFile))
992 Node third_party = LookupEntryPoint ("various") ?? this;
994 XmlDocument doc = new XmlDocument ();
996 doc.Load (sourceFile);
999 Console.Error.WriteLine ("Error: Could not load source file {0}", sourceFile);
1003 XmlNodeList extra_nodes = doc.SelectNodes ("/monodoc/node");
1004 if (extra_nodes.Count > 0)
1005 Populate (third_party, extra_nodes);
1007 XmlNodeList sources = doc.SelectNodes ("/monodoc/source");
1008 if (sources == null){
1009 Console.Error.WriteLine ("Error: No <source> section found in the {0} file", sourceFile);
1012 loadedSourceFiles [sourceFile] = sourceFile;
1013 foreach (XmlNode source in sources){
1014 XmlAttribute a = source.Attributes ["provider"];
1016 Console.Error.WriteLine ("Error: no provider in <source>");
1019 string provider = a.InnerText;
1020 a = source.Attributes ["basefile"];
1022 Console.Error.WriteLine ("Error: no basefile in <source>");
1025 string basefile = a.InnerText;
1026 a = source.Attributes ["path"];
1028 Console.Error.WriteLine ("Error: no path in <source>");
1031 string path = a.InnerText;
1033 string basefilepath = Path.Combine (Path.GetDirectoryName (sourceFile), basefile);
1034 HelpSource hs = GetHelpSource (provider, basefilepath);
1038 help_sources.Add (hs);
1039 name_to_hs [path] = hs;
1041 Node parent = LookupEntryPoint (path);
1042 if (parent == null){
1043 Console.Error.WriteLine ("node `{0}' is not defined on the documentation map", path);
1044 parent = third_party;
1047 foreach (Node n in hs.Tree.Nodes){
1054 // Delete nodes which does not have documentaiton (source)
1055 static bool PurgeNode(Node node)
1059 if (!node.Documented)
1061 ArrayList del_child = new ArrayList();
1062 //Delete node unless any child has documentation
1063 bool purged_child = false;
1064 foreach (Node child in node.Nodes)
1066 purged_child = PurgeNode(child);
1069 del_child.Add(child);
1073 // delete the node if all its children are to be deleted
1074 purge = (node.Nodes.Count == del_child.Count);
1077 foreach (Node child in del_child)
1079 node.DelNode(child);
1086 public static string[] GetSupportedFormats ()
1088 return new string[]{
1099 public static HelpSource GetHelpSource (string provider, string basefilepath)
1104 return new EcmaHelpSource (basefilepath, false);
1105 case "ecma-uncompiled":
1106 return new EcmaUncompiledHelpSource (basefilepath);
1108 return new MonoHBHelpSource(basefilepath, false);
1109 case "xhtml": case "hb":
1110 return new XhtmlHelpSource (basefilepath, false);
1112 return new ManHelpSource (basefilepath, false);
1114 return new SimpleHelpSource (basefilepath, false);
1116 return new ErrorHelpSource (basefilepath, false);
1118 return new EcmaSpecHelpSource (basefilepath, false);
1120 return new AddinsHelpSource (basefilepath, false);
1122 Console.Error.WriteLine ("Error: Unknown provider specified: {0}", provider);
1127 catch (FileNotFoundException) {
1128 Console.Error.WriteLine ("Error: did not find one of the files in sources/"+basefilepath);
1133 public static Provider GetProvider (string provider, params string[] basefilepaths)
1137 return new AddinsProvider (basefilepaths [0]);
1139 EcmaProvider p = new EcmaProvider ();
1140 foreach (string d in basefilepaths)
1145 return new EcmaSpecProvider (basefilepaths [0]);
1147 return new ErrorProvider (basefilepaths [0]);
1149 return new ManProvider (basefilepaths);
1151 return new SimpleProvider (basefilepaths [0]);
1154 return new XhtmlProvider (basefilepaths [0]);
1156 throw new NotSupportedException (provider);
1161 // Maintains the name to node mapping
1163 Hashtable name_to_node = new Hashtable ();
1164 Hashtable name_to_hs = new Hashtable ();
1166 void Populate (Node parent, XmlNodeList xml_node_list)
1168 foreach (XmlNode xml_node in xml_node_list){
1169 XmlAttribute e = xml_node.Attributes ["parent"];
1170 if (e != null && name_to_node.ContainsKey (e.InnerText)) {
1171 Node p = (Node) name_to_node [e.InnerText];
1172 xml_node.Attributes.Remove (e);
1173 Populate (p, xml_node.SelectNodes ("."));
1176 e = xml_node.Attributes ["label"];
1178 Console.Error.WriteLine ("`label' attribute missing in <node>");
1181 string label = e.InnerText;
1182 e = xml_node.Attributes ["name"];
1184 Console.Error.WriteLine ("`name' attribute missing in <node>");
1187 string name = e.InnerText;
1189 Node n = parent.LookupNode (label, "root:/" + name);
1191 name_to_node [name] = n;
1192 XmlNodeList children = xml_node.SelectNodes ("./node");
1193 if (children != null)
1194 Populate (n, children);
1198 public Node LookupEntryPoint (string name)
1200 return (Node) name_to_node [name];
1203 ArrayList help_sources;
1204 DateTime lastHelpSourceTime;
1206 RootTree () : base (null, "Mono Documentation", "root:")
1208 nodes = new ArrayList ();
1209 help_sources = new ArrayList ();
1210 lastHelpSourceTime = DateTime.MinValue;
1213 public DateTime LastHelpSourceTime {
1215 return lastHelpSourceTime;
1219 public static bool GetNamespaceAndType (string url, out string ns, out string type)
1223 for (int i = 0; i < url.Length; ++i) {
1246 ns = url.Substring (0, nsidx);
1247 type = url.Substring (nsidx + 1);
1249 //Console.Error.WriteLine ("GetNameSpaceAndType (ns={0}, type={1}", ns, type);
1253 public XmlDocument GetHelpXml (string url)
1255 string rest = url.Substring (2);
1258 if (!GetNamespaceAndType (rest, out ns, out type))
1261 foreach (HelpSource hs in help_sources) {
1262 EcmaHelpSource ehs = hs as EcmaHelpSource;
1265 string id = ehs.GetIdFromUrl ("T:", ns, type);
1268 XmlDocument doc = hs.GetHelpXmlWithChanges (id);
1275 public string TypeLookup (string url, out Node match_node)
1277 string rest = Regex.Replace (url, @"^T:\s*", "");
1280 if (!GetNamespaceAndType (rest, out ns, out type)){
1285 foreach (HelpSource hs in help_sources){
1286 string s = hs.RenderTypeLookup ("T:", ns, type, null, out match_node);
1289 lastHelpSourceTime = hs.ZipFileWriteTime;
1297 public string MemberLookup (string prefix, string url, out Node match_node)
1299 string rest = Regex.Replace (url, @"^.:\s*", "");
1301 // Dots in the arg list (for methods) confuse this.
1302 // Chop off the arg list for now and put it back later.
1303 string arglist = "";
1304 int argliststart = rest.IndexOf("(");
1305 if (argliststart >= 0) {
1306 arglist = rest.Substring(argliststart);
1307 rest = rest.Substring(0, argliststart);
1310 string ns_type, member;
1312 if (prefix != "C:") {
1313 int member_idx = rest.LastIndexOf (".");
1315 // The dot in .ctor (if it's a M: link) would confuse this.
1316 if (rest.EndsWith("..ctor")) member_idx--;
1318 ns_type = rest.Substring (0, member_idx);
1319 member = rest.Substring (member_idx + 1);
1321 // C: links don't have the .ctor member part as it would in a M: link
1322 // Even though externally C: links are different from M: links,
1323 // C: links get transformed into M:-style links (with .ctor) here.
1329 //Console.WriteLine ("NS_TYPE: {0} MEMBER: {1}", ns_type, member);
1332 if (!GetNamespaceAndType (ns_type, out ns, out type)){
1337 foreach (HelpSource hs in help_sources){
1338 string s = hs.RenderTypeLookup (prefix, ns, type, member + arglist, out match_node);
1341 lastHelpSourceTime = hs.ZipFileWriteTime;
1349 public Stream GetImage (string url)
1351 if (url.StartsWith ("source-id:")){
1352 string rest = url.Substring (10);
1353 int p = rest.IndexOf (":");
1354 string str_idx = rest.Substring (0, p);
1358 idx = Int32.Parse (str_idx);
1360 Console.Error.WriteLine ("Failed to parse source-id url: {0} `{1}'", url, str_idx);
1364 HelpSource hs = GetHelpSourceFromId (idx);
1365 lastHelpSourceTime = hs.ZipFileWriteTime;
1366 return hs.GetImage (rest.Substring (p + 1));
1368 System.Reflection.Assembly assembly = System.Reflection.Assembly.GetAssembly (typeof(RootTree));
1369 return assembly.GetManifestResourceStream (url);
1371 lastHelpSourceTime = DateTime.MinValue;
1375 public HelpSource GetHelpSourceFromId (int id)
1377 return (HelpSource) help_sources [id];
1381 // Fetches the node title
1383 public string GetTitle (string url)
1387 if (url == null || url.StartsWith ("root:"))
1388 return "Mono Documentation";
1390 if (url.Length > 2 && url [1] == ':'){
1393 return url.Substring (2) + " Namespace";
1396 string s = TypeLookup (url, out match_node);
1397 if (match_node != null)
1398 return match_node.Caption;
1399 return url.Substring (2) + " type";
1407 MemberLookup (url.Substring (0,2), url, out match_node);
1408 if (match_node != null)
1409 return match_node.Caption;
1414 return "Mono Documentation";
1419 /// Allows every HelpSource to try to provide the content for this
1422 public string RenderUrl (string url, out Node match_node)
1424 lastHelpSourceTime = DateTime.MinValue;
1425 if (url == "root:") {
1428 // look whether there are contribs
1429 GlobalChangeset chgs = EditingUtils.changes;
1430 StringBuilder con = new StringBuilder ();
1432 //add links to the contrib
1433 int oldContrib = 0, contribs = 0;
1434 con.Append ("<ul>");
1435 foreach (DocSetChangeset dscs in chgs.DocSetChangesets)
1436 foreach (FileChangeset fcs in dscs.FileChangesets)
1437 foreach (Change c in fcs.Changes) {
1438 if (c.NodeUrl == null) {
1439 if (c.Serial == SettingsHandler.Settings.SerialNumber)
1441 } else if (c.Serial == SettingsHandler.Settings.SerialNumber) {
1443 con.Append (String.Format ("<li><a href=\"{0}\">{0}</a></li>", c.NodeUrl));
1447 string contrib = (oldContrib + contribs) == 1?"There is {0} contribution":"There are {0} contributions";
1448 con.Insert (0, String.Format (contrib, oldContrib + contribs) + " pending upload <i>(Contributing--> Upload)</i>", 1);
1449 con.Append ("</ul>");
1450 if (oldContrib == 1)
1451 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>");
1452 else if (oldContrib > 1)
1453 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>");
1455 //start the rendering
1456 if (!HelpSource.use_css) {
1457 StringBuilder sb = new StringBuilder ("<table bgcolor=\"#b0c4de\" width=\"100%\" cellpadding=\"5\"><tr><td><h3>Mono Documentation Library</h3></td></tr></table>");
1459 foreach (Node n in Nodes)
1460 sb.AppendFormat ("<a href='{0}'>{1}</a><br/>", n.Element, n.Caption);
1463 sb.Append ("<br><table bgcolor=\"#fff3f3\" width=\"100%\" cellpadding=\"5\"><tr><td>");
1464 sb.Append ("<h5>Contributions</h5><br>");
1465 if ((oldContrib + contribs) == 0) {
1466 sb.Append ("<p><b>You have not made any contributions yet.</b></p>");
1467 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>");
1468 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>");
1470 sb.Append (con.ToString ());
1472 sb.Append ("</td></tr></table>");
1473 return sb.ToString ();
1475 if (home_cache == null) {
1476 System.Reflection.Assembly assembly = System.Reflection.Assembly.GetAssembly (typeof (HelpSource));
1477 Stream hp_stream = assembly.GetManifestResourceStream ("home.html");
1478 home_cache = (new StreamReader (hp_stream)).ReadToEnd ();
1480 StringBuilder sb = new StringBuilder (home_cache);
1482 sb.Replace ("@@FONT_FAMILY@@", SettingsHandler.Settings.preferred_font_family);
1483 sb.Replace ("@@FONT_SIZE@@", SettingsHandler.Settings.preferred_font_size.ToString());
1485 var visible = SettingsHandler.Settings.EnableEditing ? "block;" : "none;";
1486 if ((oldContrib + contribs) == 0) {
1487 sb.Replace ("@@CONTRIB_DISP@@", "display: none;");
1488 sb.Replace ("@@NO_CONTRIB_DISP@@", "display: " + visible);
1490 sb.Replace ("@@CONTRIB_DISP@@", "display: " + visible);
1491 sb.Replace ("@@NO_CONTRIB_DISP@@", "display: none;");
1492 sb.Replace ("@@CONTRIBS@@", con.ToString ());
1494 sb.Replace ("@@EDITING_ENABLED@@", "display: " + visible);
1496 // load the url of nodes
1498 StringBuilder urls = new StringBuilder ();
1499 foreach (Node n in Nodes) {
1500 add_str = String.Format ("<li><a href=\"{0}\">{1}</a></li>", n.Element, n.Caption);
1501 urls.Append (add_str);
1503 sb.Replace ("@@API_DOCS@@", urls.ToString ());
1505 return sb.ToString ();
1509 if (url.StartsWith ("root:")) {
1510 match_node = ((Node)name_to_node [url.Substring (6)]);
1511 HelpSource hs = ((HelpSource)name_to_hs [url.Substring (6)]);
1514 return GenerateNodeIndex(match_node);
1518 lastHelpSourceTime = hs.ZipFileWriteTime;
1519 return hs.GetText ("root:", out dummy);
1523 if (url.StartsWith ("source-id:")){
1524 string rest = url.Substring (10);
1525 int p = rest.IndexOf (":");
1526 string str_idx = rest.Substring (0, p);
1530 idx = Int32.Parse (str_idx);
1532 Console.Error.WriteLine ("Failed to parse source-id url: {0} `{1}'", url, str_idx);
1536 HelpSource hs = (HelpSource) help_sources [idx];
1537 // Console.WriteLine ("Attempting to get docs from: " + rest.Substring (p + 1));
1538 lastHelpSourceTime = hs.ZipFileWriteTime;
1539 return hs.GetText (rest.Substring (p + 1), out match_node);
1542 if (url.Length < 2){
1547 string prefix = url.Substring (0, 2);
1549 switch (prefix.ToUpper ()){
1551 foreach (HelpSource hs in help_sources){
1552 string s = hs.RenderNamespaceLookup (url, out match_node);
1554 lastHelpSourceTime = hs.ZipFileWriteTime;
1562 return TypeLookup (url, out match_node);
1570 return MemberLookup (prefix, url, out match_node);
1573 foreach (HelpSource hs in help_sources){
1574 string s = hs.GetText (url, out match_node);
1577 lastHelpSourceTime = hs.ZipFileWriteTime;
1586 public string GenerateNodeIndex (Node node)
1588 StringBuilder buf = new StringBuilder();
1589 buf.AppendFormat("<H3>{0}</H3>", node.Caption);
1591 foreach (Node child in node.Nodes)
1593 buf.AppendFormat("<li><a href=\"{0}\">{1}</a>", child.URL, child.Caption);
1595 buf.Append("</ul>");
1596 return buf.ToString();
1599 public IndexReader GetIndex ()
1601 //try to load from basedir
1602 string index_file = Path.Combine (basedir, "monodoc.index");
1603 if (File.Exists (index_file))
1604 return IndexReader.Load (index_file);
1605 //then, try to load from config dir
1606 index_file = Path.Combine (SettingsHandler.Path, "monodoc.index");
1607 return IndexReader.Load (index_file);
1611 public static void MakeIndex ()
1613 MakeIndex (LoadTree ());
1616 public static void MakeIndex (RootTree root)
1621 IndexMaker index_maker = new IndexMaker ();
1623 foreach (HelpSource hs in root.help_sources){
1624 hs.PopulateIndex (index_maker);
1627 // if the user has no write permissions use config dir
1628 string path = Path.Combine (root.basedir, "monodoc.index");
1630 index_maker.Save (path);
1631 } catch (System.UnauthorizedAccessException) {
1632 path = Path.Combine (SettingsHandler.Path, "monodoc.index");
1634 index_maker.Save (path);
1635 } catch (System.UnauthorizedAccessException) {
1636 Console.WriteLine ("Unable to write index file in {0}", Path.Combine (SettingsHandler.Path, "monodoc.index"));
1642 // No octal in C#, how lame is that
1643 chmod (path, 0x1a4);
1645 Console.WriteLine ("Documentation index updated");
1648 static bool IsUnix {
1650 int p = (int) Environment.OSVersion.Platform;
1651 return ((p == 4) || (p == 128) || (p == 6));
1656 public SearchableIndex GetSearchIndex ()
1658 //try to load from basedir
1659 string index_file = Path.Combine (basedir, "search_index");
1660 if (Directory.Exists (index_file))
1661 return SearchableIndex.Load (index_file);
1662 //then, try to load from config dir
1663 index_file = Path.Combine (SettingsHandler.Path, "search_index");
1664 return SearchableIndex.Load (index_file);
1667 public static void MakeSearchIndex ()
1669 MakeSearchIndex (LoadTree ());
1672 public static void MakeSearchIndex (RootTree root)
1674 // Loads the RootTree
1675 Console.WriteLine ("Loading the monodoc tree...");
1680 string dir = Path.Combine (root.basedir, "search_index");
1682 //try to create the dir to store the index
1684 if (!Directory.Exists (dir))
1685 Directory.CreateDirectory (dir);
1687 writer = new IndexWriter(Mono.Lucene.Net.Store.FSDirectory.GetDirectory(dir, true), new StandardAnalyzer(), true);
1688 } catch (UnauthorizedAccessException) {
1689 //try in the .config directory
1691 dir = Path.Combine (SettingsHandler.Path, "search_index");
1692 if (!Directory.Exists (dir))
1693 Directory.CreateDirectory (dir);
1695 writer = new IndexWriter(Mono.Lucene.Net.Store.FSDirectory.GetDirectory(dir, true), new StandardAnalyzer(), true);
1696 } catch (UnauthorizedAccessException) {
1697 Console.WriteLine ("You don't have permissions to write on " + dir);
1702 //Collect all the documents
1703 Console.WriteLine ("Collecting and adding documents...");
1704 foreach (HelpSource hs in root.HelpSources)
1705 hs.PopulateSearchableIndex (writer);
1707 //Optimize and close
1708 Console.WriteLine ("Closing...");
1714 public ICollection HelpSources { get { return new ArrayList(help_sources); } }
1716 [System.Runtime.InteropServices.DllImport ("libc")]
1717 static extern int chmod (string filename, int mode);