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;
27 using System.Text.RegularExpressions;
29 using System.Xml.XPath;
30 using ICSharpCode.SharpZipLib.Zip;
32 using Mono.Lucene.Net.Index;
33 using Mono.Lucene.Net.Analysis.Standard;
35 using Mono.Documentation;
38 /// This tree is populated by the documentation providers, or populated
39 /// from a binary encoding of the tree. The format of the tree is designed
40 /// to minimize the need to load it in full.
42 public class Tree : Node {
44 #region Loading the tree from a file
47 /// Our HelpSource container
49 public readonly HelpSource HelpSource;
51 internal FileStream InputStream;
52 internal BinaryReader InputReader;
55 /// Load from file constructor
57 public Tree (HelpSource hs, string filename) : base (null, null)
59 Encoding utf8 = new UTF8Encoding (false, true);
61 if (!File.Exists (filename)){
62 throw new FileNotFoundException ();
65 InputStream = File.OpenRead (filename);
66 InputReader = new BinaryReader (InputStream, utf8);
67 byte [] sig = InputReader.ReadBytes (4);
70 throw new Exception ("Invalid file format");
72 InputStream.Position = 4;
73 position = InputReader.ReadInt32 ();
80 /// Tree creation and merged tree constructor
82 public Tree (HelpSource hs, string caption, string url) : base (caption, url)
87 public Tree (HelpSource hs, Node parent, string caption, string element) : base (parent, caption, element)
95 /// Saves the tree into the specified file using the help file format.
97 public void Save (string file)
99 Encoding utf8 = new UTF8Encoding (false, true);
100 using (FileStream output = File.OpenWrite (file)){
101 // Skip over the pointer to the first node.
104 using (BinaryWriter writer = new BinaryWriter (output, utf8)){
106 Dump (output, writer);
109 writer.Write (new byte [] { (byte) 'M', (byte) 'o', (byte) 'H', (byte) 'P' });
110 writer.Write (position);
115 static bool GoodSig (byte [] sig)
119 if (sig [0] != (byte) 'M' ||
120 sig [1] != (byte) 'o' ||
121 sig [2] != (byte) 'H' ||
122 sig [3] != (byte) 'P')
129 public class Node : IComparable {
130 string caption, element;
131 public bool Documented;
132 public readonly Tree tree;
134 protected ArrayList nodes;
135 protected internal int position;
137 static ArrayList empty = ArrayList.ReadOnly(new ArrayList(0));
140 /// Creates a node, called by the Tree.
142 public Node (string caption, string element)
144 this.tree = (Tree) this;
145 this.caption = caption;
146 this.element = element;
150 public Node (Node parent, string caption, string element)
152 this.parent = parent;
153 this.tree = parent.tree;
154 this.caption = caption;
155 this.element = element;
159 /// Creates a node from an on-disk representation
161 Node (Node parent, int address)
163 this.parent = parent;
165 this.tree = parent.tree;
170 public void AddNode (Node n)
177 public void DelNode (Node n)
182 public ArrayList Nodes {
186 return nodes != null ? nodes : empty;
190 public string Element {
202 public string Caption {
216 public void LoadNode ()
219 position = -position;
221 tree.InputStream.Position = position;
222 BinaryReader reader = tree.InputReader;
223 int count = DecodeInt (reader);
224 element = reader.ReadString ();
225 caption = reader.ReadString ();
229 nodes = new ArrayList (count);
230 for (int i = 0; i < count; i++){
231 int child_address = DecodeInt (reader);
233 Node t = new Node (this, -child_address);
239 /// Creates a new node, in the locator entry point, and with
240 /// a user visible caption of @caption
242 public Node CreateNode (string c_caption, string c_element)
245 nodes = new ArrayList ();
247 Node t = new Node (this, c_caption, c_element);
253 /// Looks up or creates a new node, in the locator entry point, and with
254 /// a user visible caption of @caption. This is different from
255 /// CreateNode in that it will look up an existing node for the given @locator.
257 public Node LookupNode (string c_caption, string c_element)
260 return CreateNode (c_caption, c_element);
262 foreach (Node n in nodes){
263 if (n.element == c_element)
266 return CreateNode (c_caption, c_element);
269 public void EnsureNodes ()
272 nodes = new ArrayList ();
277 return nodes == null;
281 void EncodeInt (BinaryWriter writer, int value)
284 int high = (value >> 7) & 0x01ffffff;
285 byte b = (byte)(value & 0x7f);
288 b = (byte)(b | 0x80);
296 int DecodeInt (BinaryReader reader)
303 b = reader.ReadByte();
305 ret = ret | ((b & 0x7f) << shift);
307 } while ((b & 0x80) == 0x80);
312 internal void Dump (FileStream output, BinaryWriter writer)
315 foreach (Node child in nodes){
316 child.Dump (output, writer);
319 position = (int) output.Position;
320 EncodeInt (writer, nodes == null ? 0 : (int) nodes.Count);
321 writer.Write (element);
322 writer.Write (caption);
325 foreach (Node child in nodes){
326 EncodeInt (writer, child.position);
333 static void Indent ()
335 for (int i = 0; i < indent; i++)
339 public static void PrintTree (Node node)
342 Console.WriteLine ("{0},{1}\t[PublicUrl: {2}]", node.Element, node.Caption, node.PublicUrl);
343 if (node.Nodes.Count == 0)
347 foreach (Node n in node.Nodes)
358 [Obsolete("Use PublicUrl")]
364 if (element.IndexOf (":") >= 0)
368 string url = parent.URL;
370 if (url.EndsWith ("/"))
371 return url + element;
373 return parent.URL + "/" + element;
379 public string PublicUrl {
381 return tree.HelpSource != null
382 ? tree.HelpSource.GetPublicUrl (URL)
387 int IComparable.CompareTo (object obj)
389 Node other = obj as Node;
395 if (other.position < 0)
398 Regex digits = new Regex (@"([\d]+)|([^\d]+)");
399 MatchEvaluator eval = delegate (Match m) {
400 return (m.Value.Length > 0 && char.IsDigit (m.Value [0]))
401 ? m.Value.PadLeft (System.Math.Max (caption.Length, other.caption.Length))
404 return digits.Replace (caption, eval).CompareTo (digits.Replace (other.caption, eval));
409 // The HelpSource class keeps track of the archived data, and its
412 public class HelpSource {
414 public static bool use_css = false;
415 public static string css_code;
416 public static string CssCode {
418 if (css_code != null)
421 System.Reflection.Assembly assembly = System.Reflection.Assembly.GetCallingAssembly ();
422 Stream str_css = assembly.GetManifestResourceStream ("base.css");
423 StringBuilder sb = new StringBuilder ((new StreamReader (str_css)).ReadToEnd());
424 sb.Replace ("@@FONT_FAMILY@@", SettingsHandler.Settings.preferred_font_family);
425 sb.Replace ("@@FONT_SIZE@@", SettingsHandler.Settings.preferred_font_size.ToString());
426 css_code = sb.ToString ();
429 set { css_code = value; }
432 public virtual string InlineCss {
433 get { return CssCode; }
436 public virtual string InlineJavaScript {
440 public static bool FullHtml = true;
442 // should only be enabled by ASP.NET webdoc
443 public static bool UseWebdocCache;
446 // The unique ID for this HelpSource.
449 DateTime zipFileWriteTime;
452 TraceLevel trace_level = TraceLevel.Warning;
453 protected bool nozip;
454 protected string base_dir;
456 public HelpSource (string base_filename, bool create)
458 this.name = Path.GetFileName (base_filename);
459 this.basepath = Path.GetDirectoryName (base_filename);
460 tree_filename = base_filename + ".tree";
461 zip_filename = base_filename + ".zip";
462 base_dir = XmlDocUtils.GetCacheDirectory (base_filename);
463 if (UseWebdocCache && !create && Directory.Exists (base_dir)) {
470 Tree = new Tree (this, tree_filename);
474 FileInfo fi = new FileInfo (zip_filename);
475 zipFileWriteTime = fi.LastWriteTime;
477 zipFileWriteTime = DateTime.Now;
481 public HelpSource() {
482 Tree = new Tree (this, "Blah", "Blah");
486 public DateTime ZipFileWriteTime {
488 return zipFileWriteTime;
492 public int SourceID {
504 /* This gives the full path of the source/ directory */
505 public string BaseFilePath {
511 public TraceLevel TraceLevel {
512 get { return trace_level; }
513 set { trace_level = value; }
516 public string BaseDir {
525 /// Returns a stream from the packaged help source archive
527 public virtual Stream GetHelpStream (string id)
530 string path = XmlDocUtils.GetCachedFileName (base_dir, id);
531 if (File.Exists (path))
532 return File.OpenRead (path);
536 if (zip_file == null)
537 zip_file = new ZipFile (zip_filename);
539 ZipEntry entry = zip_file.GetEntry (id);
541 return zip_file.GetInputStream (entry);
545 public string GetRealPath (string file)
547 if (zip_file == null)
548 zip_file = new ZipFile (zip_filename);
550 ZipEntry entry = zip_file.GetEntry (file);
551 if (entry != null && entry.ExtraData != null)
552 return ConvertToString (entry.ExtraData);
556 public XmlReader GetHelpXml (string id)
559 Stream s = File.OpenRead (XmlDocUtils.GetCachedFileName (base_dir, id));
560 string url = "monodoc:///" + SourceID + "@" + Uri.EscapeUriString (id) + "@";
561 return new XmlTextReader (url, s);
564 if (zip_file == null)
565 zip_file = new ZipFile (zip_filename);
567 ZipEntry entry = zip_file.GetEntry (id);
569 Stream s = zip_file.GetInputStream (entry);
570 string url = "monodoc:///" + SourceID + "@" + Uri.EscapeUriString (id) + "@";
571 return new XmlTextReader (url, s);
576 public virtual XmlDocument GetHelpXmlWithChanges (string id)
579 Stream s = File.OpenRead (XmlDocUtils.GetCachedFileName (base_dir, id));
580 string url = "monodoc:///" + SourceID + "@" + Uri.EscapeUriString (id) + "@";
581 XmlReader r = new XmlTextReader (url, s);
582 XmlDocument ret = new XmlDocument ();
587 if (zip_file == null)
588 zip_file = new ZipFile (zip_filename);
590 ZipEntry entry = zip_file.GetEntry (id);
592 Stream s = zip_file.GetInputStream (entry);
593 string url = "monodoc:///" + SourceID + "@" + Uri.EscapeUriString (id) + "@";
594 XmlReader r = new XmlTextReader (url, s);
595 XmlDocument ret = new XmlDocument ();
598 if (entry.ExtraData != null)
599 EditingUtils.AccountForChanges (ret, Name, ConvertToString (entry.ExtraData));
607 /// Get a nice, unique expression for any XPath node that you get.
608 /// This function is used by editing to get the expression to put
609 /// on to the file. The idea is to create an expression that is resistant
610 /// to changes in the structure of the XML.
612 public virtual string GetNodeXPath (XPathNavigator n)
614 return EditingUtils.GetXPath (n.Clone ());
617 public string GetEditUri (XPathNavigator n)
619 return EditingUtils.FormatEditUri (n.BaseURI, GetNodeXPath (n));
622 static string ConvertToString (byte[] data)
624 return Encoding.UTF8.GetString(data);
627 static byte[] ConvertToArray (string str)
629 return Encoding.UTF8.GetBytes(str);
633 /// The tree that is being populated
636 public RootTree RootTree;
638 // Base filename used by this HelpSource.
639 string tree_filename, zip_filename;
642 const int buffer_size = 65536;
643 ZipOutputStream zip_output;
646 HelpSource (string base_filename)
650 void SetupForOutput ()
652 Tree = new Tree (this, "", "");
654 FileStream stream = File.Create (zip_filename);
656 zip_output = new ZipOutputStream (stream);
657 zip_output.SetLevel (9);
659 buffer = new byte [buffer_size];
663 /// Saves the tree and the archive
667 Tree.Save (tree_filename);
668 zip_output.Finish ();
676 return String.Format ("{0}", code++);
680 /// Providers call this to store a file they will need, and the return value
681 /// is the name that was assigned to it
683 public string PackFile (string file)
685 string entry_name = GetNewCode ();
686 return PackFile (file, entry_name);
689 public string PackFile (string file, string entry_name)
691 using (FileStream input = File.OpenRead (file)) {
692 PackStream (input, entry_name, file);
698 public void PackStream (Stream s, string entry_name)
700 PackStream (s, entry_name, null);
703 void PackStream (Stream s, string entry_name, string realPath)
705 ZipEntry entry = new ZipEntry (entry_name);
707 if (realPath != null)
708 entry.ExtraData = ConvertToArray (realPath);
710 zip_output.PutNextEntry (entry);
713 while ((n = s.Read (buffer, 0, buffer_size)) > 0){
714 zip_output.Write (buffer, 0, n);
718 public void PackXml (string fname, XmlDocument doc, string real_path)
720 ZipEntry entry = new ZipEntry (fname);
721 if (real_path != null)
722 entry.ExtraData = ConvertToArray(real_path);
724 zip_output.PutNextEntry (entry);
725 XmlTextWriter xmlWriter = new XmlTextWriter (zip_output, Encoding.UTF8);
726 doc.WriteContentTo (xmlWriter);
730 public virtual void RenderPreviewDocs (XmlNode newNode, XmlWriter writer)
732 throw new NotImplementedException ();
735 public virtual string GetPublicUrl (string id)
740 public virtual string GetText (string url, out Node n)
746 protected string GetCachedText (string url)
750 string file = XmlDocUtils.GetCachedFileName (base_dir, url);
751 if (!File.Exists (file))
753 return File.OpenText (file).ReadToEnd ();
756 public virtual Stream GetImage (string url)
762 // Default method implementation does not satisfy the request
764 public virtual string RenderTypeLookup (string prefix, string ns, string type, string member, out Node n)
770 public virtual string RenderNamespaceLookup (string nsurl, out Node n)
777 // Populates the index.
779 public virtual void PopulateIndex (IndexMaker index_maker)
784 // Build an html document
786 public static string BuildHtml (string css, string html_code)
788 return BuildHtml (css, null, html_code);
791 internal static string BuildHtml (string css, string js, string html_code) {
795 StringWriter output = new StringWriter ();
796 output.Write ("<html><head>");
797 output.Write ("<style type=\"text/css\">");
798 output.Write (CssCode);
800 output.Write ("</style>");
802 System.Reflection.Assembly assembly = System.Reflection.Assembly.GetCallingAssembly ();
803 Stream str_js = assembly.GetManifestResourceStream ("helper.js");
804 StringBuilder sb = new StringBuilder ((new StreamReader (str_js)).ReadToEnd());
805 output.Write ("<script type=\"text/JavaScript\">\n");
806 output.Write (sb.ToString ());
807 output.Write ("</script>\n");
810 output.Write ("<script type=\"text/JavaScript\">\n");
812 output.Write ("\n</script>");
815 output.Write ("</head><body>");
816 output.Write (html_code);
817 output.Write ("</body></html>");
818 return output.ToString ();
822 // Create different Documents for adding to Lucene search index
823 // The default action is do nothing. Subclasses should add the docs
825 public virtual void PopulateSearchableIndex (IndexWriter writer) {
829 public void Message (TraceLevel level, string format, params object[] args)
831 if ((int) level <= (int) trace_level)
832 Console.WriteLine (format, args);
835 public void Error (string format, params object[] args)
837 Console.Error.WriteLine (format, args);
841 public abstract class Provider {
843 // This code is used to "tag" all the different sources
854 public abstract void PopulateTree (Tree tree);
857 // Called at shutdown time after the tree has been populated to perform
858 // any fixups or final tasks.
860 public abstract void CloseTree (HelpSource hs, Tree tree);
863 public class RootTree : Tree {
866 public static ArrayList UncompiledHelpSources = new ArrayList();
868 public const int MonodocVersion = 1;
870 public static RootTree LoadTree ()
872 return LoadTree (null);
875 const string MacMonoDocDir = "/Library/Frameworks/Mono.framework/Versions/Current/lib/monodoc";
878 // Loads the tree layout
880 public static RootTree LoadTree (string basedir)
882 if (basedir == null) {
883 string myPath = System.Reflection.Assembly.GetExecutingAssembly ().Location;
884 string cfgFile = myPath + ".config";
885 if (!File.Exists (cfgFile)) {
889 XmlDocument d = new XmlDocument ();
891 basedir = d.SelectSingleNode ("config/path").Attributes ["docsPath"].Value;
893 // Temporary workaround for developers distributing a monodoc.dll themselves on Mac
894 if (Directory.Exists (MacMonoDocDir)){
895 Console.WriteLine ("MacDir exists");
896 if (!File.Exists (Path.Combine (basedir, "monodoc.xml"))){
897 basedir = MacMonoDocDir;
905 XmlDocument doc = new XmlDocument ();
906 string layout = Path.Combine (basedir, "monodoc.xml");
909 string osxExternalDir = "/Library/Frameworks/Mono.framework/External/monodoc";
910 string[] osxExternalSources = Directory.Exists (osxExternalDir)
911 ? Directory.GetFiles (osxExternalDir, "*.source")
914 return LoadTree (basedir, doc,
915 Directory.GetFiles (Path.Combine (basedir, "sources"), "*.source")
916 .Concat (osxExternalSources));
919 // Compatibility shim w/ Mono 2.6
920 public static RootTree LoadTree (string indexDir, XmlDocument docTree, IEnumerable sourceFiles)
922 return LoadTree (indexDir, docTree, sourceFiles.Cast<string>());
925 public static RootTree LoadTree (string indexDir, XmlDocument docTree, IEnumerable<string> sourceFiles)
927 if (docTree == null) {
928 docTree = new XmlDocument ();
929 using (var defTree = typeof(RootTree).Assembly.GetManifestResourceStream ("monodoc.xml"))
930 docTree.Load (defTree);
934 sourceFiles = sourceFiles ?? new string [0];
940 RootTree root = new RootTree ();
941 root.basedir = indexDir;
943 XmlNodeList nodes = docTree.SelectNodes ("/node/node");
945 root.name_to_node ["root"] = root;
946 root.name_to_node ["libraries"] = root;
947 root.Populate (root, nodes);
949 Node third_party = root.LookupEntryPoint ("various");
950 if (third_party == null) {
951 Console.Error.WriteLine ("No 'various' doc node! Check monodoc.xml!");
958 foreach (var sourceFile in sourceFiles)
959 root.AddSourceFile (sourceFile);
961 foreach (string path in UncompiledHelpSources) {
962 EcmaUncompiledHelpSource hs = new EcmaUncompiledHelpSource(path);
964 root.help_sources.Add (hs);
965 string epath = "extra-help-source-" + hs.Name;
966 Node hsn = root.CreateNode (hs.Name, "root:/" + epath);
967 root.name_to_hs [epath] = hs;
969 foreach (Node n in hs.Tree.Nodes){
982 public void AddSource (string sources_dir)
984 string [] files = Directory.GetFiles (sources_dir);
986 foreach (string file in files){
987 if (!file.EndsWith (".source"))
989 AddSourceFile (file);
993 Dictionary<string,string> loadedSourceFiles = new Dictionary<string,string> ();
995 public void AddSourceFile (string sourceFile)
997 if (loadedSourceFiles.ContainsKey (sourceFile))
1000 Node third_party = LookupEntryPoint ("various") ?? this;
1002 XmlDocument doc = new XmlDocument ();
1004 doc.Load (sourceFile);
1007 Console.Error.WriteLine ("Error: Could not load source file {0}", sourceFile);
1011 XmlNodeList extra_nodes = doc.SelectNodes ("/monodoc/node");
1012 if (extra_nodes.Count > 0)
1013 Populate (third_party, extra_nodes);
1015 XmlNodeList sources = doc.SelectNodes ("/monodoc/source");
1016 if (sources == null){
1017 Console.Error.WriteLine ("Error: No <source> section found in the {0} file", sourceFile);
1020 loadedSourceFiles [sourceFile] = sourceFile;
1021 foreach (XmlNode source in sources){
1022 XmlAttribute a = source.Attributes ["provider"];
1024 Console.Error.WriteLine ("Error: no provider in <source>");
1027 string provider = a.InnerText;
1028 a = source.Attributes ["basefile"];
1030 Console.Error.WriteLine ("Error: no basefile in <source>");
1033 string basefile = a.InnerText;
1034 a = source.Attributes ["path"];
1036 Console.Error.WriteLine ("Error: no path in <source>");
1039 string path = a.InnerText;
1041 string basefilepath = Path.Combine (Path.GetDirectoryName (sourceFile), basefile);
1042 HelpSource hs = GetHelpSource (provider, basefilepath);
1046 help_sources.Add (hs);
1047 name_to_hs [path] = hs;
1049 Node parent = LookupEntryPoint (path);
1050 if (parent == null){
1051 Console.Error.WriteLine ("node `{0}' is not defined on the documentation map", path);
1052 parent = third_party;
1055 foreach (Node n in hs.Tree.Nodes){
1062 // Delete nodes which does not have documentaiton (source)
1063 static bool PurgeNode(Node node)
1067 if (!node.Documented)
1069 ArrayList del_child = new ArrayList();
1070 //Delete node unless any child has documentation
1071 bool purged_child = false;
1072 foreach (Node child in node.Nodes)
1074 purged_child = PurgeNode(child);
1077 del_child.Add(child);
1081 // delete the node if all its children are to be deleted
1082 purge = (node.Nodes.Count == del_child.Count);
1085 foreach (Node child in del_child)
1087 node.DelNode(child);
1094 public static string[] GetSupportedFormats ()
1096 return new string[]{
1107 public static HelpSource GetHelpSource (string provider, string basefilepath)
1112 return new EcmaHelpSource (basefilepath, false);
1113 case "ecma-uncompiled":
1114 return new EcmaUncompiledHelpSource (basefilepath);
1116 return new MonoHBHelpSource(basefilepath, false);
1117 case "xhtml": case "hb":
1118 return new XhtmlHelpSource (basefilepath, false);
1120 return new ManHelpSource (basefilepath, false);
1122 return new SimpleHelpSource (basefilepath, false);
1124 return new ErrorHelpSource (basefilepath, false);
1126 return new EcmaSpecHelpSource (basefilepath, false);
1128 return new AddinsHelpSource (basefilepath, false);
1130 Console.Error.WriteLine ("Error: Unknown provider specified: {0}", provider);
1135 catch (FileNotFoundException) {
1136 Console.Error.WriteLine ("Error: did not find one of the files in sources/"+basefilepath);
1141 public static Provider GetProvider (string provider, params string[] basefilepaths)
1145 return new AddinsProvider (basefilepaths [0]);
1147 EcmaProvider p = new EcmaProvider ();
1148 foreach (string d in basefilepaths)
1153 return new EcmaSpecProvider (basefilepaths [0]);
1155 return new ErrorProvider (basefilepaths [0]);
1157 return new ManProvider (basefilepaths);
1159 return new SimpleProvider (basefilepaths [0]);
1162 return new XhtmlProvider (basefilepaths [0]);
1164 throw new NotSupportedException (provider);
1169 // Maintains the name to node mapping
1171 Hashtable name_to_node = new Hashtable ();
1172 Hashtable name_to_hs = new Hashtable ();
1174 void Populate (Node parent, XmlNodeList xml_node_list)
1176 foreach (XmlNode xml_node in xml_node_list){
1177 XmlAttribute e = xml_node.Attributes ["parent"];
1178 if (e != null && name_to_node.ContainsKey (e.InnerText)) {
1179 Node p = (Node) name_to_node [e.InnerText];
1180 xml_node.Attributes.Remove (e);
1181 Populate (p, xml_node.SelectNodes ("."));
1184 e = xml_node.Attributes ["label"];
1186 Console.Error.WriteLine ("`label' attribute missing in <node>");
1189 string label = e.InnerText;
1190 e = xml_node.Attributes ["name"];
1192 Console.Error.WriteLine ("`name' attribute missing in <node>");
1195 string name = e.InnerText;
1197 Node n = parent.LookupNode (label, "root:/" + name);
1199 name_to_node [name] = n;
1200 XmlNodeList children = xml_node.SelectNodes ("./node");
1201 if (children != null)
1202 Populate (n, children);
1206 public Node LookupEntryPoint (string name)
1208 return (Node) name_to_node [name];
1211 ArrayList help_sources;
1212 DateTime lastHelpSourceTime;
1214 RootTree () : base (null, "Mono Documentation", "root:")
1216 nodes = new ArrayList ();
1217 help_sources = new ArrayList ();
1218 lastHelpSourceTime = DateTime.MinValue;
1221 public DateTime LastHelpSourceTime {
1223 return lastHelpSourceTime;
1227 public static bool GetNamespaceAndType (string url, out string ns, out string type)
1231 for (int i = 0; i < url.Length; ++i) {
1254 ns = url.Substring (0, nsidx);
1255 type = url.Substring (nsidx + 1);
1257 //Console.Error.WriteLine ("GetNameSpaceAndType (ns={0}, type={1}", ns, type);
1261 public XmlDocument GetHelpXml (string url)
1263 string rest = url.Substring (2);
1266 if (!GetNamespaceAndType (rest, out ns, out type))
1269 foreach (HelpSource hs in help_sources) {
1270 EcmaHelpSource ehs = hs as EcmaHelpSource;
1273 string id = ehs.GetIdFromUrl ("T:", ns, type);
1276 XmlDocument doc = hs.GetHelpXmlWithChanges (id);
1283 public string TypeLookup (string url, out Node match_node)
1285 string rest = Regex.Replace (url, @"^T:\s*", "");
1288 if (!GetNamespaceAndType (rest, out ns, out type)){
1293 foreach (HelpSource hs in help_sources){
1294 string s = hs.RenderTypeLookup ("T:", ns, type, null, out match_node);
1297 lastHelpSourceTime = hs.ZipFileWriteTime;
1305 public string MemberLookup (string prefix, string url, out Node match_node)
1307 string rest = Regex.Replace (url, @"^.:\s*", "");
1309 // Dots in the arg list (for methods) confuse this.
1310 // Chop off the arg list for now and put it back later.
1311 string arglist = "";
1312 int argliststart = rest.IndexOf("(");
1313 if (argliststart >= 0) {
1314 arglist = rest.Substring(argliststart);
1315 rest = rest.Substring(0, argliststart);
1318 string ns_type, member;
1320 if (prefix != "C:") {
1321 int member_idx = rest.LastIndexOf (".");
1323 // The dot in .ctor (if it's a M: link) would confuse this.
1324 if (rest.EndsWith("..ctor")) member_idx--;
1326 ns_type = rest.Substring (0, member_idx);
1327 member = rest.Substring (member_idx + 1);
1329 // C: links don't have the .ctor member part as it would in a M: link
1330 // Even though externally C: links are different from M: links,
1331 // C: links get transformed into M:-style links (with .ctor) here.
1337 //Console.WriteLine ("NS_TYPE: {0} MEMBER: {1}", ns_type, member);
1340 if (!GetNamespaceAndType (ns_type, out ns, out type)){
1345 foreach (HelpSource hs in help_sources){
1346 string s = hs.RenderTypeLookup (prefix, ns, type, member + arglist, out match_node);
1349 lastHelpSourceTime = hs.ZipFileWriteTime;
1357 public Stream GetImage (string url)
1359 if (url.StartsWith ("source-id:")){
1360 string rest = url.Substring (10);
1361 int p = rest.IndexOf (":");
1362 string str_idx = rest.Substring (0, p);
1366 idx = Int32.Parse (str_idx);
1368 Console.Error.WriteLine ("Failed to parse source-id url: {0} `{1}'", url, str_idx);
1372 HelpSource hs = GetHelpSourceFromId (idx);
1373 lastHelpSourceTime = hs.ZipFileWriteTime;
1374 return hs.GetImage (rest.Substring (p + 1));
1376 System.Reflection.Assembly assembly = System.Reflection.Assembly.GetAssembly (typeof(RootTree));
1377 return assembly.GetManifestResourceStream (url);
1379 lastHelpSourceTime = DateTime.MinValue;
1383 public HelpSource GetHelpSourceFromId (int id)
1385 return (HelpSource) help_sources [id];
1389 // Fetches the node title
1391 public string GetTitle (string url)
1395 if (url == null || url.StartsWith ("root:"))
1396 return "Mono Documentation";
1398 if (url.Length > 2 && url [1] == ':'){
1401 return url.Substring (2) + " Namespace";
1404 string s = TypeLookup (url, out match_node);
1405 if (match_node != null)
1406 return match_node.Caption;
1407 return url.Substring (2) + " type";
1415 MemberLookup (url.Substring (0,2), url, out match_node);
1416 if (match_node != null)
1417 return match_node.Caption;
1422 return "Mono Documentation";
1427 /// Allows every HelpSource to try to provide the content for this
1430 public string RenderUrl (string url, out Node match_node)
1432 lastHelpSourceTime = DateTime.MinValue;
1433 if (url == "root:") {
1436 // look whether there are contribs
1437 GlobalChangeset chgs = EditingUtils.changes;
1438 StringBuilder con = new StringBuilder ();
1440 //add links to the contrib
1441 int oldContrib = 0, contribs = 0;
1442 con.Append ("<ul>");
1443 foreach (DocSetChangeset dscs in chgs.DocSetChangesets)
1444 foreach (FileChangeset fcs in dscs.FileChangesets)
1445 foreach (Change c in fcs.Changes) {
1446 if (c.NodeUrl == null) {
1447 if (c.Serial == SettingsHandler.Settings.SerialNumber)
1449 } else if (c.Serial == SettingsHandler.Settings.SerialNumber) {
1451 con.Append (String.Format ("<li><a href=\"{0}\">{0}</a></li>", c.NodeUrl));
1455 string contrib = (oldContrib + contribs) == 1?"There is {0} contribution":"There are {0} contributions";
1456 con.Insert (0, String.Format (contrib, oldContrib + contribs) + " pending upload <i>(Contributing--> Upload)</i>", 1);
1457 con.Append ("</ul>");
1458 if (oldContrib == 1)
1459 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>");
1460 else if (oldContrib > 1)
1461 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>");
1463 //start the rendering
1464 if (!HelpSource.use_css) {
1465 StringBuilder sb = new StringBuilder ("<table bgcolor=\"#b0c4de\" width=\"100%\" cellpadding=\"5\"><tr><td><h3>Mono Documentation Library</h3></td></tr></table>");
1467 foreach (Node n in Nodes)
1468 sb.AppendFormat ("<a href='{0}'>{1}</a><br/>", n.Element, n.Caption);
1471 sb.Append ("<br><table bgcolor=\"#fff3f3\" width=\"100%\" cellpadding=\"5\"><tr><td>");
1472 sb.Append ("<h5>Contributions</h5><br>");
1473 if ((oldContrib + contribs) == 0) {
1474 sb.Append ("<p><b>You have not made any contributions yet.</b></p>");
1475 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>");
1476 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>");
1478 sb.Append (con.ToString ());
1480 sb.Append ("</td></tr></table>");
1481 return sb.ToString ();
1483 if (home_cache == null) {
1484 System.Reflection.Assembly assembly = System.Reflection.Assembly.GetAssembly (typeof (HelpSource));
1485 Stream hp_stream = assembly.GetManifestResourceStream ("home.html");
1486 home_cache = (new StreamReader (hp_stream)).ReadToEnd ();
1488 StringBuilder sb = new StringBuilder (home_cache);
1490 sb.Replace ("@@FONT_FAMILY@@", SettingsHandler.Settings.preferred_font_family);
1491 sb.Replace ("@@FONT_SIZE@@", SettingsHandler.Settings.preferred_font_size.ToString());
1493 var visible = SettingsHandler.Settings.EnableEditing ? "block;" : "none;";
1494 if ((oldContrib + contribs) == 0) {
1495 sb.Replace ("@@CONTRIB_DISP@@", "display: none;");
1496 sb.Replace ("@@NO_CONTRIB_DISP@@", "display: " + visible);
1498 sb.Replace ("@@CONTRIB_DISP@@", "display: " + visible);
1499 sb.Replace ("@@NO_CONTRIB_DISP@@", "display: none;");
1500 sb.Replace ("@@CONTRIBS@@", con.ToString ());
1502 sb.Replace ("@@EDITING_ENABLED@@", "display: " + visible);
1504 // load the url of nodes
1506 StringBuilder urls = new StringBuilder ();
1507 foreach (Node n in Nodes) {
1508 add_str = String.Format ("<li><a href=\"{0}\">{1}</a></li>", n.Element, n.Caption);
1509 urls.Append (add_str);
1511 sb.Replace ("@@API_DOCS@@", urls.ToString ());
1513 return sb.ToString ();
1517 if (url.StartsWith ("root:")) {
1518 match_node = ((Node)name_to_node [url.Substring (6)]);
1519 HelpSource hs = ((HelpSource)name_to_hs [url.Substring (6)]);
1522 return GenerateNodeIndex(match_node);
1526 lastHelpSourceTime = hs.ZipFileWriteTime;
1527 return hs.GetText ("root:", out dummy);
1531 if (url.StartsWith ("source-id:")){
1532 string rest = url.Substring (10);
1533 int p = rest.IndexOf (":");
1534 string str_idx = rest.Substring (0, p);
1538 idx = Int32.Parse (str_idx);
1540 Console.Error.WriteLine ("Failed to parse source-id url: {0} `{1}'", url, str_idx);
1544 HelpSource hs = (HelpSource) help_sources [idx];
1545 // Console.WriteLine ("Attempting to get docs from: " + rest.Substring (p + 1));
1546 lastHelpSourceTime = hs.ZipFileWriteTime;
1547 return hs.GetText (rest.Substring (p + 1), out match_node);
1550 if (url.Length < 2){
1555 string prefix = url.Substring (0, 2);
1557 switch (prefix.ToUpper ()){
1559 foreach (HelpSource hs in help_sources){
1560 string s = hs.RenderNamespaceLookup (url, out match_node);
1562 lastHelpSourceTime = hs.ZipFileWriteTime;
1570 return TypeLookup (url, out match_node);
1578 return MemberLookup (prefix, url, out match_node);
1581 foreach (HelpSource hs in help_sources){
1582 string s = hs.GetText (url, out match_node);
1585 lastHelpSourceTime = hs.ZipFileWriteTime;
1594 public string GenerateNodeIndex (Node node)
1596 StringBuilder buf = new StringBuilder();
1597 buf.AppendFormat("<H3>{0}</H3>", node.Caption);
1599 foreach (Node child in node.Nodes)
1601 buf.AppendFormat("<li><a href=\"{0}\">{1}</a>", child.URL, child.Caption);
1603 buf.Append("</ul>");
1604 return buf.ToString();
1607 public IndexReader GetIndex ()
1609 //try to load from basedir
1610 string index_file = Path.Combine (basedir, "monodoc.index");
1611 if (File.Exists (index_file))
1612 return IndexReader.Load (index_file);
1613 //then, try to load from config dir
1614 index_file = Path.Combine (SettingsHandler.Path, "monodoc.index");
1615 return IndexReader.Load (index_file);
1619 public static void MakeIndex ()
1621 MakeIndex (LoadTree ());
1624 public static void MakeIndex (RootTree root)
1629 IndexMaker index_maker = new IndexMaker ();
1631 foreach (HelpSource hs in root.help_sources){
1632 hs.PopulateIndex (index_maker);
1635 // if the user has no write permissions use config dir
1636 string path = Path.Combine (root.basedir, "monodoc.index");
1638 index_maker.Save (path);
1639 } catch (System.UnauthorizedAccessException) {
1640 path = Path.Combine (SettingsHandler.Path, "monodoc.index");
1642 index_maker.Save (path);
1643 } catch (System.UnauthorizedAccessException) {
1644 Console.WriteLine ("Unable to write index file in {0}", Path.Combine (SettingsHandler.Path, "monodoc.index"));
1650 // No octal in C#, how lame is that
1651 chmod (path, 0x1a4);
1653 Console.WriteLine ("Documentation index at {0} updated", path);
1656 static bool IsUnix {
1658 int p = (int) Environment.OSVersion.Platform;
1659 return ((p == 4) || (p == 128) || (p == 6));
1664 public SearchableIndex GetSearchIndex ()
1666 //try to load from basedir
1667 string index_file = Path.Combine (basedir, "search_index");
1668 if (Directory.Exists (index_file))
1669 return SearchableIndex.Load (index_file);
1670 //then, try to load from config dir
1671 index_file = Path.Combine (SettingsHandler.Path, "search_index");
1672 return SearchableIndex.Load (index_file);
1675 public static void MakeSearchIndex ()
1677 MakeSearchIndex (LoadTree ());
1680 public static void MakeSearchIndex (RootTree root)
1682 // Loads the RootTree
1683 Console.WriteLine ("Loading the monodoc tree...");
1688 string dir = Path.Combine (root.basedir, "search_index");
1690 //try to create the dir to store the 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 //try in the .config directory
1699 dir = Path.Combine (SettingsHandler.Path, "search_index");
1700 if (!Directory.Exists (dir))
1701 Directory.CreateDirectory (dir);
1703 writer = new IndexWriter(Mono.Lucene.Net.Store.FSDirectory.GetDirectory(dir, true), new StandardAnalyzer(), true);
1704 } catch (UnauthorizedAccessException) {
1705 Console.WriteLine ("You don't have permissions to write on " + dir);
1710 //Collect all the documents
1711 Console.WriteLine ("Collecting and adding documents...");
1712 foreach (HelpSource hs in root.HelpSources)
1713 hs.PopulateSearchableIndex (writer);
1715 //Optimize and close
1716 Console.WriteLine ("Closing...");
1722 public ICollection HelpSources { get { return new ArrayList(help_sources); } }
1724 [System.Runtime.InteropServices.DllImport ("libc")]
1725 static extern int chmod (string filename, int mode);