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
18 using System.Text.RegularExpressions;
19 using System.Collections;
20 using System.Diagnostics;
21 using System.Configuration;
22 using System.Text.RegularExpressions;
24 using System.Xml.XPath;
25 using ICSharpCode.SharpZipLib.Zip;
27 using Monodoc.Lucene.Net.Index;
28 using Monodoc.Lucene.Net.Analysis.Standard;
30 using Mono.Documentation;
33 /// This tree is populated by the documentation providers, or populated
34 /// from a binary encoding of the tree. The format of the tree is designed
35 /// to minimize the need to load it in full.
37 public class Tree : Node {
39 #region Loading the tree from a file
42 /// Our HelpSource container
44 public readonly HelpSource HelpSource;
46 internal FileStream InputStream;
47 internal BinaryReader InputReader;
50 /// Load from file constructor
52 public Tree (HelpSource hs, string filename) : base (null, null)
54 Encoding utf8 = new UTF8Encoding (false, true);
56 if (!File.Exists (filename)){
57 throw new FileNotFoundException ();
60 InputStream = File.OpenRead (filename);
61 InputReader = new BinaryReader (InputStream, utf8);
62 byte [] sig = InputReader.ReadBytes (4);
65 throw new Exception ("Invalid file format");
67 InputStream.Position = 4;
68 position = InputReader.ReadInt32 ();
75 /// Tree creation and merged tree constructor
77 public Tree (HelpSource hs, string caption, string url) : base (caption, url)
82 public Tree (HelpSource hs, Node parent, string caption, string element) : base (parent, caption, element)
90 /// Saves the tree into the specified file using the help file format.
92 public void Save (string file)
94 Encoding utf8 = new UTF8Encoding (false, true);
95 using (FileStream output = File.OpenWrite (file)){
96 // Skip over the pointer to the first node.
99 using (BinaryWriter writer = new BinaryWriter (output, utf8)){
101 Dump (output, writer);
104 writer.Write (new byte [] { (byte) 'M', (byte) 'o', (byte) 'H', (byte) 'P' });
105 writer.Write (position);
110 static bool GoodSig (byte [] sig)
114 if (sig [0] != (byte) 'M' ||
115 sig [1] != (byte) 'o' ||
116 sig [2] != (byte) 'H' ||
117 sig [3] != (byte) 'P')
124 public class Node : IComparable {
125 string caption, element;
126 public bool Documented;
127 public readonly Tree tree;
129 protected ArrayList nodes;
130 protected internal int position;
132 static ArrayList empty = ArrayList.ReadOnly(new ArrayList(0));
135 /// Creates a node, called by the Tree.
137 public Node (string caption, string element)
139 this.tree = (Tree) this;
140 this.caption = caption;
141 this.element = element;
145 public Node (Node parent, string caption, string element)
147 this.parent = parent;
148 this.tree = parent.tree;
149 this.caption = caption;
150 this.element = element;
154 /// Creates a node from an on-disk representation
156 Node (Node parent, int address)
158 this.parent = parent;
160 this.tree = parent.tree;
165 public void AddNode (Node n)
172 public void DelNode (Node n)
177 public ArrayList Nodes {
181 return nodes != null ? nodes : empty;
185 public string Element {
197 public string Caption {
211 public void LoadNode ()
214 position = -position;
216 tree.InputStream.Position = position;
217 BinaryReader reader = tree.InputReader;
218 int count = DecodeInt (reader);
219 element = reader.ReadString ();
220 caption = reader.ReadString ();
224 nodes = new ArrayList (count);
225 for (int i = 0; i < count; i++){
226 int child_address = DecodeInt (reader);
228 Node t = new Node (this, -child_address);
234 /// Creates a new node, in the locator entry point, and with
235 /// a user visible caption of @caption
237 public Node CreateNode (string c_caption, string c_element)
240 nodes = new ArrayList ();
242 Node t = new Node (this, c_caption, c_element);
248 /// Looks up or creates a new node, in the locator entry point, and with
249 /// a user visible caption of @caption. This is different from
250 /// CreateNode in that it will look up an existing node for the given @locator.
252 public Node LookupNode (string c_caption, string c_element)
255 return CreateNode (c_caption, c_element);
257 foreach (Node n in nodes){
258 if (n.element == c_element)
261 return CreateNode (c_caption, c_element);
264 public void EnsureNodes ()
267 nodes = new ArrayList ();
272 return nodes == null;
276 void EncodeInt (BinaryWriter writer, int value)
279 int high = (value >> 7) & 0x01ffffff;
280 byte b = (byte)(value & 0x7f);
283 b = (byte)(b | 0x80);
291 int DecodeInt (BinaryReader reader)
298 b = reader.ReadByte();
300 ret = ret | ((b & 0x7f) << shift);
302 } while ((b & 0x80) == 0x80);
307 internal void Dump (FileStream output, BinaryWriter writer)
310 foreach (Node child in nodes){
311 child.Dump (output, writer);
314 position = (int) output.Position;
315 EncodeInt (writer, nodes == null ? 0 : (int) nodes.Count);
316 writer.Write (element);
317 writer.Write (caption);
320 foreach (Node child in nodes){
321 EncodeInt (writer, child.position);
328 static void Indent ()
330 for (int i = 0; i < indent; i++)
334 public static void PrintTree (Node node)
337 Console.WriteLine ("{0},{1}", node.Element, node.Caption);
338 if (node.Nodes.Count == 0)
342 foreach (Node n in node.Nodes)
358 if (element.IndexOf (":") >= 0)
362 string url = parent.URL;
364 if (url.EndsWith ("/"))
365 return url + element;
367 return parent.URL + "/" + element;
373 int IComparable.CompareTo (object obj)
375 Node other = obj as Node;
381 if (other.position < 0)
384 Regex digits = new Regex (@"([\d]+)|([^\d]+)");
385 MatchEvaluator eval = delegate (Match m) {
386 return (m.Value.Length > 0 && char.IsDigit (m.Value [0]))
387 ? m.Value.PadLeft (System.Math.Max (caption.Length, other.caption.Length))
390 return digits.Replace (caption, eval).CompareTo (digits.Replace (other.caption, eval));
395 // The HelpSource class keeps track of the archived data, and its
398 public class HelpSource {
400 public static bool use_css = false;
401 public static string css_code;
402 public static string CssCode {
404 if (css_code != null)
407 System.Reflection.Assembly assembly = System.Reflection.Assembly.GetCallingAssembly ();
408 Stream str_css = assembly.GetManifestResourceStream ("base.css");
409 StringBuilder sb = new StringBuilder ((new StreamReader (str_css)).ReadToEnd());
410 sb.Replace ("@@FONT_FAMILY@@", SettingsHandler.Settings.preferred_font_family);
411 sb.Replace ("@@FONT_SIZE@@", SettingsHandler.Settings.preferred_font_size.ToString());
412 css_code = sb.ToString ();
415 set { css_code = value; }
418 public virtual string InlineCss {
419 get { return CssCode; }
422 public virtual string InlineJavaScript {
426 public static bool FullHtml = true;
428 // should only be enabled by ASP.NET webdoc
429 public static bool UseWebdocCache;
432 // The unique ID for this HelpSource.
435 DateTime zipFileWriteTime;
437 TraceLevel trace_level = TraceLevel.Warning;
441 public HelpSource (string base_filename, bool create)
443 this.name = Path.GetFileName (base_filename);
444 tree_filename = base_filename + ".tree";
445 zip_filename = base_filename + ".zip";
446 base_dir = XmlDocUtils.GetCacheDirectory (base_filename);
447 if (!UseWebdocCache && !create && Directory.Exists (base_dir)) {
454 Tree = new Tree (this, tree_filename);
458 FileInfo fi = new FileInfo (zip_filename);
459 zipFileWriteTime = fi.LastWriteTime;
461 zipFileWriteTime = DateTime.Now;
465 public HelpSource() {
466 Tree = new Tree (this, "Blah", "Blah");
470 public DateTime ZipFileWriteTime {
472 return zipFileWriteTime;
476 public int SourceID {
488 public TraceLevel TraceLevel {
489 get { return trace_level; }
490 set { trace_level = value; }
496 /// Returns a stream from the packaged help source archive
498 public virtual Stream GetHelpStream (string id)
501 string path = XmlDocUtils.GetCachedFileName (base_dir, id);
502 if (File.Exists (path))
503 return File.OpenRead (path);
507 if (zip_file == null)
508 zip_file = new ZipFile (zip_filename);
510 ZipEntry entry = zip_file.GetEntry (id);
512 return zip_file.GetInputStream (entry);
516 public string GetRealPath (string file)
518 if (zip_file == null)
519 zip_file = new ZipFile (zip_filename);
521 ZipEntry entry = zip_file.GetEntry (file);
522 if (entry != null && entry.ExtraData != null)
523 return ConvertToString (entry.ExtraData);
527 public XmlReader GetHelpXml (string id)
530 Stream s = File.OpenRead (XmlDocUtils.GetCachedFileName (base_dir, id));
531 string url = "monodoc:///" + SourceID + "@" + System.Web.HttpUtility.UrlEncode (id) + "@";
532 return new XmlTextReader (url, s);
535 if (zip_file == null)
536 zip_file = new ZipFile (zip_filename);
538 ZipEntry entry = zip_file.GetEntry (id);
540 Stream s = zip_file.GetInputStream (entry);
541 string url = "monodoc:///" + SourceID + "@" + System.Web.HttpUtility.UrlEncode (id) + "@";
542 return new XmlTextReader (url, s);
547 public virtual XmlDocument GetHelpXmlWithChanges (string id)
550 Stream s = File.OpenRead (XmlDocUtils.GetCachedFileName (base_dir, id));
551 string url = "monodoc:///" + SourceID + "@" + System.Web.HttpUtility.UrlEncode (id) + "@";
552 XmlReader r = new XmlTextReader (url, s);
553 XmlDocument ret = new XmlDocument ();
558 if (zip_file == null)
559 zip_file = new ZipFile (zip_filename);
561 ZipEntry entry = zip_file.GetEntry (id);
563 Stream s = zip_file.GetInputStream (entry);
564 string url = "monodoc:///" + SourceID + "@" + System.Web.HttpUtility.UrlEncode (id) + "@";
565 XmlReader r = new XmlTextReader (url, s);
566 XmlDocument ret = new XmlDocument ();
569 if (entry.ExtraData != null)
570 EditingUtils.AccountForChanges (ret, Name, ConvertToString (entry.ExtraData));
578 /// Get a nice, unique expression for any XPath node that you get.
579 /// This function is used by editing to get the expression to put
580 /// on to the file. The idea is to create an expression that is resistant
581 /// to changes in the structure of the XML.
583 public virtual string GetNodeXPath (XPathNavigator n)
585 return EditingUtils.GetXPath (n.Clone ());
588 public string GetEditUri (XPathNavigator n)
590 return EditingUtils.FormatEditUri (n.BaseURI, GetNodeXPath (n));
593 static string ConvertToString (byte[] data)
595 return Encoding.UTF8.GetString(data);
598 static byte[] ConvertToArray (string str)
600 return Encoding.UTF8.GetBytes(str);
604 /// The tree that is being populated
607 public RootTree RootTree;
609 // Base filename used by this HelpSource.
610 string tree_filename, zip_filename;
613 const int buffer_size = 65536;
614 ZipOutputStream zip_output;
617 HelpSource (string base_filename)
621 void SetupForOutput ()
623 Tree = new Tree (this, "", "");
625 FileStream stream = File.Create (zip_filename);
627 zip_output = new ZipOutputStream (stream);
628 zip_output.SetLevel (9);
630 buffer = new byte [buffer_size];
634 /// Saves the tree and the archive
638 Tree.Save (tree_filename);
639 zip_output.Finish ();
647 return String.Format ("{0}", code++);
651 /// Providers call this to store a file they will need, and the return value
652 /// is the name that was assigned to it
654 public string PackFile (string file)
656 string entry_name = GetNewCode ();
657 return PackFile (file, entry_name);
660 public string PackFile (string file, string entry_name)
662 using (FileStream input = File.OpenRead (file)) {
663 PackStream (input, entry_name, file);
669 public void PackStream (Stream s, string entry_name)
671 PackStream (s, entry_name, null);
674 void PackStream (Stream s, string entry_name, string realPath)
676 ZipEntry entry = new ZipEntry (entry_name);
678 if (realPath != null)
679 entry.ExtraData = ConvertToArray (realPath);
681 zip_output.PutNextEntry (entry);
684 while ((n = s.Read (buffer, 0, buffer_size)) > 0){
685 zip_output.Write (buffer, 0, n);
689 public void PackXml (string fname, XmlDocument doc, string real_path)
691 ZipEntry entry = new ZipEntry (fname);
692 if (real_path != null)
693 entry.ExtraData = ConvertToArray(real_path);
695 zip_output.PutNextEntry (entry);
696 XmlTextWriter xmlWriter = new XmlTextWriter (zip_output, Encoding.UTF8);
697 doc.WriteContentTo (xmlWriter);
701 public virtual void RenderPreviewDocs (XmlNode newNode, XmlWriter writer)
703 throw new NotImplementedException ();
706 public virtual string GetText (string url, out Node n)
712 protected string GetCachedText (string url)
716 string file = XmlDocUtils.GetCachedFileName (base_dir, url);
717 if (!File.Exists (file))
719 return File.OpenText (file).ReadToEnd ();
722 public virtual Stream GetImage (string url)
728 // Default method implementation does not satisfy the request
730 public virtual string RenderTypeLookup (string prefix, string ns, string type, string member, out Node n)
736 public virtual string RenderNamespaceLookup (string nsurl, out Node n)
743 // Populates the index.
745 public virtual void PopulateIndex (IndexMaker index_maker)
750 // Build an html document
752 public static string BuildHtml (string css, string html_code)
754 return BuildHtml (css, null, html_code);
757 internal static string BuildHtml (string css, string js, string html_code) {
761 StringWriter output = new StringWriter ();
762 output.Write ("<html><head>");
763 output.Write ("<style type=\"text/css\">");
764 output.Write (CssCode);
766 output.Write ("</style>");
768 System.Reflection.Assembly assembly = System.Reflection.Assembly.GetCallingAssembly ();
769 Stream str_js = assembly.GetManifestResourceStream ("helper.js");
770 StringBuilder sb = new StringBuilder ((new StreamReader (str_js)).ReadToEnd());
771 output.Write ("<script type=\"text/JavaScript\">\n");
772 output.Write (sb.ToString ());
773 output.Write ("</script>\n");
776 output.Write ("<script type=\"text/JavaScript\">\n");
778 output.Write ("\n</script>");
781 output.Write ("</head><body>");
782 output.Write (html_code);
783 output.Write ("</body></html>");
784 return output.ToString ();
788 // Create different Documents for adding to Lucene search index
789 // The default action is do nothing. Subclasses should add the docs
791 public virtual void PopulateSearchableIndex (IndexWriter writer) {
795 public void Message (TraceLevel level, string format, params object[] args)
797 if ((int) level <= (int) trace_level)
798 Console.WriteLine (format, args);
801 public void Error (string format, params object[] args)
803 Console.Error.WriteLine (format, args);
807 public abstract class Provider {
809 // This code is used to "tag" all the different sources
820 public abstract void PopulateTree (Tree tree);
823 // Called at shutdown time after the tree has been populated to perform
824 // any fixups or final tasks.
826 public abstract void CloseTree (HelpSource hs, Tree tree);
829 public class RootTree : Tree {
832 public static ArrayList UncompiledHelpSources = new ArrayList();
834 public const int MonodocVersion = 1;
836 public static RootTree LoadTree ()
839 string myPath = System.Reflection.Assembly.GetExecutingAssembly ().Location;
840 string cfgFile = myPath + ".config";
841 if (!File.Exists (cfgFile)) {
843 return LoadTree (basedir);
846 XmlDocument d = new XmlDocument ();
848 basedir = d.SelectSingleNode ("config/path").Attributes ["docsPath"].Value;
850 return LoadTree (basedir);
854 // Loads the tree layout
856 public static RootTree LoadTree (string basedir)
858 XmlDocument doc = new XmlDocument ();
860 RootTree root = new RootTree ();
861 root.basedir = basedir;
866 string layout = Path.Combine (basedir, "monodoc.xml");
868 XmlNodeList nodes = doc.SelectNodes ("/node/node");
870 root.name_to_node ["root"] = root;
871 root.name_to_node ["libraries"] = root;
872 root.Populate (root, nodes);
874 Node third_party = root.LookupEntryPoint ("various");
875 if (third_party == null) {
876 Console.Error.WriteLine ("No 'various' doc node! Check monodoc.xml!");
883 string sources_dir = Path.Combine (basedir, "sources");
885 string [] files = Directory.GetFiles (sources_dir);
886 foreach (string file in files){
887 if (!file.EndsWith (".source"))
890 doc = new XmlDocument ();
894 Console.Error.WriteLine ("Error: Could not load source file {0}", file);
898 XmlNodeList extra_nodes = doc.SelectNodes ("/monodoc/node");
899 if (extra_nodes.Count > 0)
900 root.Populate (third_party, extra_nodes);
902 XmlNodeList sources = doc.SelectNodes ("/monodoc/source");
903 if (sources == null){
904 Console.Error.WriteLine ("Error: No <source> section found in the {0} file", file);
907 foreach (XmlNode source in sources){
908 XmlAttribute a = source.Attributes ["provider"];
910 Console.Error.WriteLine ("Error: no provider in <source>");
913 string provider = a.InnerText;
914 a = source.Attributes ["basefile"];
916 Console.Error.WriteLine ("Error: no basefile in <source>");
919 string basefile = a.InnerText;
920 a = source.Attributes ["path"];
922 Console.Error.WriteLine ("Error: no path in <source>");
925 string path = a.InnerText;
927 string basefilepath = Path.Combine (sources_dir, basefile);
928 HelpSource hs = GetHelpSource (provider, basefilepath);
932 root.help_sources.Add (hs);
933 root.name_to_hs [path] = hs;
935 Node parent = root.LookupEntryPoint (path);
937 Console.Error.WriteLine ("node `{0}' is not defined on the documentation map", path);
938 parent = third_party;
941 foreach (Node n in hs.Tree.Nodes){
948 foreach (string path in UncompiledHelpSources) {
949 EcmaUncompiledHelpSource hs = new EcmaUncompiledHelpSource(path);
951 root.help_sources.Add (hs);
952 string epath = "extra-help-source-" + hs.Name;
953 Node hsn = root.CreateNode (hs.Name, "root:/" + epath);
954 root.name_to_hs [epath] = hs;
956 foreach (Node n in hs.Tree.Nodes){
969 // Delete nodes which does not have documentaiton (source)
970 static bool PurgeNode(Node node)
974 if (!node.Documented)
976 ArrayList del_child = new ArrayList();
977 //Delete node unless any child has documentation
978 bool purged_child = false;
979 foreach (Node child in node.Nodes)
981 purged_child = PurgeNode(child);
984 del_child.Add(child);
988 // delete the node if all its children are to be deleted
989 purge = (node.Nodes.Count == del_child.Count);
992 foreach (Node child in del_child)
1002 static HelpSource GetHelpSource (string provider, string basefilepath)
1007 return new EcmaHelpSource (basefilepath, false);
1008 case "ecma-uncompiled":
1009 return new EcmaUncompiledHelpSource (basefilepath);
1011 return new MonoHBHelpSource(basefilepath, false);
1013 return new XhtmlHelpSource (basefilepath, false);
1015 return new ManHelpSource (basefilepath, false);
1017 return new SimpleHelpSource (basefilepath, false);
1019 return new ErrorHelpSource (basefilepath, false);
1021 return new EcmaSpecHelpSource (basefilepath, false);
1023 return new AddinsHelpSource (basefilepath, false);
1025 Console.Error.WriteLine ("Error: Unknown provider specified: {0}", provider);
1030 catch (FileNotFoundException) {
1031 Console.Error.WriteLine ("Error: did not find one of the files in sources/"+basefilepath);
1038 // Maintains the name to node mapping
1040 Hashtable name_to_node = new Hashtable ();
1041 Hashtable name_to_hs = new Hashtable ();
1043 void Populate (Node parent, XmlNodeList xml_node_list)
1045 foreach (XmlNode xml_node in xml_node_list){
1046 XmlAttribute e = xml_node.Attributes ["parent"];
1047 if (e != null && name_to_node.ContainsKey (e.InnerText)) {
1048 Node p = (Node) name_to_node [e.InnerText];
1049 xml_node.Attributes.Remove (e);
1050 Populate (p, xml_node.SelectNodes ("."));
1053 e = xml_node.Attributes ["label"];
1055 Console.Error.WriteLine ("`label' attribute missing in <node>");
1058 string label = e.InnerText;
1059 e = xml_node.Attributes ["name"];
1061 Console.Error.WriteLine ("`name' attribute missing in <node>");
1064 string name = e.InnerText;
1066 Node n = parent.LookupNode (label, "root:/" + name);
1068 name_to_node [name] = n;
1069 XmlNodeList children = xml_node.SelectNodes ("./node");
1070 if (children != null)
1071 Populate (n, children);
1075 public Node LookupEntryPoint (string name)
1077 return (Node) name_to_node [name];
1080 ArrayList help_sources;
1081 DateTime lastHelpSourceTime;
1083 RootTree () : base (null, "Mono Documentation", "root:")
1085 nodes = new ArrayList ();
1086 help_sources = new ArrayList ();
1087 lastHelpSourceTime = DateTime.MinValue;
1090 public DateTime LastHelpSourceTime {
1092 return lastHelpSourceTime;
1096 public static bool GetNamespaceAndType (string url, out string ns, out string type)
1100 for (int i = 0; i < url.Length; ++i) {
1119 Console.Error.WriteLine ("Did not find dot in: " + url);
1124 ns = url.Substring (0, nsidx);
1125 type = url.Substring (nsidx + 1);
1127 //Console.Error.WriteLine ("GetNameSpaceAndType (ns={0}, type={1}", ns, type);
1131 public XmlDocument GetHelpXml (string url)
1133 string rest = url.Substring (2);
1136 if (!GetNamespaceAndType (rest, out ns, out type))
1139 foreach (HelpSource hs in help_sources) {
1140 EcmaHelpSource ehs = hs as EcmaHelpSource;
1143 string id = ehs.GetIdFromUrl ("T:", ns, type);
1146 XmlDocument doc = hs.GetHelpXmlWithChanges (id);
1153 public string TypeLookup (string url, out Node match_node)
1155 string rest = Regex.Replace (url, @"^T:\s*", "");
1158 if (!GetNamespaceAndType (rest, out ns, out type)){
1163 foreach (HelpSource hs in help_sources){
1164 string s = hs.RenderTypeLookup ("T:", ns, type, null, out match_node);
1167 lastHelpSourceTime = hs.ZipFileWriteTime;
1175 public string MemberLookup (string prefix, string url, out Node match_node)
1177 string rest = Regex.Replace (url, @"^.:\s*", "");
1179 // Dots in the arg list (for methods) confuse this.
1180 // Chop off the arg list for now and put it back later.
1181 string arglist = "";
1182 int argliststart = rest.IndexOf("(");
1183 if (argliststart >= 0) {
1184 arglist = rest.Substring(argliststart);
1185 rest = rest.Substring(0, argliststart);
1188 string ns_type, member;
1190 if (prefix != "C:") {
1191 int member_idx = rest.LastIndexOf (".");
1193 // The dot in .ctor (if it's a M: link) would confuse this.
1194 if (rest.EndsWith("..ctor")) member_idx--;
1196 ns_type = rest.Substring (0, member_idx);
1197 member = rest.Substring (member_idx + 1);
1199 // C: links don't have the .ctor member part as it would in a M: link
1200 // Even though externally C: links are different from M: links,
1201 // C: links get transformed into M:-style links (with .ctor) here.
1207 //Console.WriteLine ("NS_TYPE: {0} MEMBER: {1}", ns_type, member);
1210 if (!GetNamespaceAndType (ns_type, out ns, out type)){
1215 foreach (HelpSource hs in help_sources){
1216 string s = hs.RenderTypeLookup (prefix, ns, type, member + arglist, out match_node);
1219 lastHelpSourceTime = hs.ZipFileWriteTime;
1227 public Stream GetImage (string url)
1229 if (url.StartsWith ("source-id:")){
1230 string rest = url.Substring (10);
1231 int p = rest.IndexOf (":");
1232 string str_idx = rest.Substring (0, p);
1236 idx = Int32.Parse (str_idx);
1238 Console.Error.WriteLine ("Failed to parse source-id url: {0} `{1}'", url, str_idx);
1242 HelpSource hs = GetHelpSourceFromId (idx);
1243 lastHelpSourceTime = hs.ZipFileWriteTime;
1244 return hs.GetImage (rest.Substring (p + 1));
1246 System.Reflection.Assembly assembly = System.Reflection.Assembly.GetAssembly (typeof(RootTree));
1247 return assembly.GetManifestResourceStream (url);
1249 lastHelpSourceTime = DateTime.MinValue;
1253 public HelpSource GetHelpSourceFromId (int id)
1255 return (HelpSource) help_sources [id];
1260 /// Allows every HelpSource to try to provide the content for this
1263 public string RenderUrl (string url, out Node match_node)
1265 lastHelpSourceTime = DateTime.MinValue;
1266 if (url == "root:") {
1269 // look whether there are contribs
1270 GlobalChangeset chgs = EditingUtils.changes;
1271 StringBuilder con = new StringBuilder ();
1273 //add links to the contrib
1274 int oldContrib = 0, contribs = 0;
1275 con.Append ("<ul>");
1276 foreach (DocSetChangeset dscs in chgs.DocSetChangesets)
1277 foreach (FileChangeset fcs in dscs.FileChangesets)
1278 foreach (Change c in fcs.Changes) {
1279 if (c.NodeUrl == null) {
1280 if (c.Serial == SettingsHandler.Settings.SerialNumber)
1282 } else if (c.Serial == SettingsHandler.Settings.SerialNumber) {
1284 con.Append (String.Format ("<li><a href=\"{0}\">{0}</a></li>", c.NodeUrl));
1288 string contrib = (oldContrib + contribs) == 1?"There is {0} contribution":"There are {0} contributions";
1289 con.Insert (0, String.Format (contrib, oldContrib + contribs) + " pending upload <i>(Contributing--> Upload)</i>", 1);
1290 con.Append ("</ul>");
1291 if (oldContrib == 1)
1292 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>");
1293 else if (oldContrib > 1)
1294 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>");
1296 //start the rendering
1297 if (!HelpSource.use_css) {
1298 StringBuilder sb = new StringBuilder ("<table bgcolor=\"#b0c4de\" width=\"100%\" cellpadding=\"5\"><tr><td><h3>Mono Documentation Library</h3></td></tr></table>");
1300 foreach (Node n in Nodes)
1301 sb.AppendFormat ("<a href='{0}'>{1}</a><br/>", n.Element, n.Caption);
1304 sb.Append ("<br><table bgcolor=\"#fff3f3\" width=\"100%\" cellpadding=\"5\"><tr><td>");
1305 sb.Append ("<h5>Contributions</h5><br>");
1306 if ((oldContrib + contribs) == 0) {
1307 sb.Append ("<p><b>You have not made any contributions yet.</b></p>");
1308 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>");
1309 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>");
1311 sb.Append (con.ToString ());
1313 sb.Append ("</td></tr></table>");
1314 return sb.ToString ();
1316 if (home_cache == null) {
1317 System.Reflection.Assembly assembly = System.Reflection.Assembly.GetAssembly (typeof (HelpSource));
1318 Stream hp_stream = assembly.GetManifestResourceStream ("home.html");
1319 home_cache = (new StreamReader (hp_stream)).ReadToEnd ();
1321 StringBuilder sb = new StringBuilder (home_cache);
1323 sb.Replace ("@@FONT_FAMILY@@", SettingsHandler.Settings.preferred_font_family);
1324 sb.Replace ("@@FONT_SIZE@@", SettingsHandler.Settings.preferred_font_size.ToString());
1326 if ((oldContrib + contribs) == 0) {
1327 sb.Replace ("@@CONTRIB_DISP@@", "display: none;");
1329 sb.Replace ("@@NO_CONTRIB_DISP@@", "display: none;");
1330 sb.Replace ("@@CONTRIBS@@", con.ToString ());
1333 // load the url of nodes
1335 StringBuilder urls = new StringBuilder ();
1336 foreach (Node n in Nodes) {
1337 add_str = String.Format ("<li><a href=\"{0}\">{1}</a></li>", n.Element, n.Caption);
1338 urls.Append (add_str);
1340 sb.Replace ("@@API_DOCS@@", urls.ToString ());
1342 return sb.ToString ();
1346 if (url.StartsWith ("root:")) {
1347 match_node = ((Node)name_to_node [url.Substring (6)]);
1348 HelpSource hs = ((HelpSource)name_to_hs [url.Substring (6)]);
1351 return GenerateNodeIndex(match_node);
1355 lastHelpSourceTime = hs.ZipFileWriteTime;
1356 return hs.GetText ("root:", out dummy);
1360 if (url.StartsWith ("source-id:")){
1361 string rest = url.Substring (10);
1362 int p = rest.IndexOf (":");
1363 string str_idx = rest.Substring (0, p);
1367 idx = Int32.Parse (str_idx);
1369 Console.Error.WriteLine ("Failed to parse source-id url: {0} `{1}'", url, str_idx);
1373 HelpSource hs = (HelpSource) help_sources [idx];
1374 // Console.WriteLine ("Attempting to get docs from: " + rest.Substring (p + 1));
1375 lastHelpSourceTime = hs.ZipFileWriteTime;
1376 return hs.GetText (rest.Substring (p + 1), out match_node);
1379 if (url.Length < 2){
1384 string prefix = url.Substring (0, 2);
1386 switch (prefix.ToUpper ()){
1388 foreach (HelpSource hs in help_sources){
1389 string s = hs.RenderNamespaceLookup (url, out match_node);
1391 lastHelpSourceTime = hs.ZipFileWriteTime;
1399 return TypeLookup (url, out match_node);
1407 return MemberLookup (prefix, url, out match_node);
1410 foreach (HelpSource hs in help_sources){
1411 string s = hs.GetText (url, out match_node);
1414 lastHelpSourceTime = hs.ZipFileWriteTime;
1423 public string GenerateNodeIndex (Node node)
1425 StringBuilder buf = new StringBuilder();
1426 buf.AppendFormat("<H3>{0}</H3>", node.Caption);
1428 foreach (Node child in node.Nodes)
1430 buf.AppendFormat("<li><a href=\"{0}\">{1}</a>", child.URL, child.Caption);
1432 buf.Append("</ul>");
1433 return buf.ToString();
1436 public IndexReader GetIndex ()
1438 //try to load from basedir
1439 string index_file = Path.Combine (basedir, "monodoc.index");
1440 if (File.Exists (index_file))
1441 return IndexReader.Load (index_file);
1442 //then, try to load from config dir
1443 index_file = Path.Combine (SettingsHandler.Path, "monodoc.index");
1444 return IndexReader.Load (index_file);
1448 public static void MakeIndex ()
1450 RootTree root = LoadTree ();
1454 IndexMaker index_maker = new IndexMaker ();
1456 foreach (HelpSource hs in root.help_sources){
1457 hs.PopulateIndex (index_maker);
1460 // if the user has no write permissions use config dir
1461 string path = Path.Combine (root.basedir, "monodoc.index");
1463 index_maker.Save (path);
1464 } catch (System.UnauthorizedAccessException) {
1465 path = Path.Combine (SettingsHandler.Path, "monodoc.index");
1467 index_maker.Save (path);
1468 } catch (System.UnauthorizedAccessException) {
1469 Console.WriteLine ("Unable to write index file in {0}", Path.Combine (SettingsHandler.Path, "monodoc.index"));
1475 // No octal in C#, how lame is that
1476 chmod (path, 0x1a4);
1478 Console.WriteLine ("Documentation index updated");
1481 static bool IsUnix {
1483 int p = (int) Environment.OSVersion.Platform;
1484 return ((p == 4) || (p == 128) || (p == 6));
1489 public SearchableIndex GetSearchIndex ()
1491 //try to load from basedir
1492 string index_file = Path.Combine (basedir, "search_index");
1493 if (Directory.Exists (index_file))
1494 return SearchableIndex.Load (index_file);
1495 //then, try to load from config dir
1496 index_file = Path.Combine (SettingsHandler.Path, "search_index");
1497 return SearchableIndex.Load (index_file);
1500 public static void MakeSearchIndex ()
1502 // Loads the RootTree
1503 Console.WriteLine ("Loading the monodoc tree...");
1504 RootTree root = LoadTree ();
1508 string dir = Path.Combine (root.basedir, "search_index");
1510 //try to create the dir to store the index
1512 if (!Directory.Exists (dir))
1513 Directory.CreateDirectory (dir);
1515 writer = new IndexWriter(Lucene.Net.Store.FSDirectory.GetDirectory(dir, true), new StandardAnalyzer(), true);
1516 } catch (UnauthorizedAccessException) {
1517 //try in the .config directory
1519 dir = Path.Combine (SettingsHandler.Path, "search_index");
1520 if (!Directory.Exists (dir))
1521 Directory.CreateDirectory (dir);
1523 writer = new IndexWriter(Lucene.Net.Store.FSDirectory.GetDirectory(dir, true), new StandardAnalyzer(), true);
1524 } catch (UnauthorizedAccessException) {
1525 Console.WriteLine ("You don't have permissions to write on " + dir);
1530 //Collect all the documents
1531 Console.WriteLine ("Collecting and adding documents...");
1532 foreach (HelpSource hs in root.HelpSources)
1533 hs.PopulateSearchableIndex (writer);
1535 //Optimize and close
1536 Console.WriteLine ("Closing...");
1542 public ICollection HelpSources { get { return new ArrayList(help_sources); } }
1544 [System.Runtime.InteropServices.DllImport ("libc")]
1545 static extern int chmod (string filename, int mode);