2 // Provider: shared code and interfaces for providers
5 // Miguel de Icaza (miguel@ximian.com)
7 // (C) 2002, Ximian, Inc.
8 // Copyright 2003-2011 Novell
9 // Copyright 2011 Xamarin Inc
12 // Each node should have a provider link
14 // Should encode numbers using a runlength encoding to save space
18 using System.Collections.Generic;
22 using System.Text.RegularExpressions;
23 using System.Collections;
24 using System.Diagnostics;
25 using System.Configuration;
26 using System.Reflection;
28 using System.Xml.XPath;
29 using ICSharpCode.SharpZipLib.Zip;
31 using Mono.Lucene.Net.Index;
32 using Mono.Lucene.Net.Analysis.Standard;
34 using Mono.Documentation;
37 /// This tree is populated by the documentation providers, or populated
38 /// from a binary encoding of the tree. The format of the tree is designed
39 /// to minimize the need to load it in full.
41 public class Tree : Node {
43 #region Loading the tree from a file
46 /// Our HelpSource container
48 public readonly HelpSource HelpSource;
50 internal FileStream InputStream;
51 internal BinaryReader InputReader;
54 /// Load from file constructor
56 public Tree (HelpSource hs, string filename) : base (null, null)
58 Encoding utf8 = new UTF8Encoding (false, true);
60 if (!File.Exists (filename)){
61 throw new FileNotFoundException ();
64 InputStream = File.OpenRead (filename);
65 InputReader = new BinaryReader (InputStream, utf8);
66 byte [] sig = InputReader.ReadBytes (4);
69 throw new Exception ("Invalid file format");
71 InputStream.Position = 4;
72 position = InputReader.ReadInt32 ();
79 /// Tree creation and merged tree constructor
81 public Tree (HelpSource hs, string caption, string url) : base (caption, url)
86 public Tree (HelpSource hs, Node parent, string caption, string element) : base (parent, caption, element)
94 /// Saves the tree into the specified file using the help file format.
96 public void Save (string file)
98 Encoding utf8 = new UTF8Encoding (false, true);
99 using (FileStream output = File.OpenWrite (file)){
100 // Skip over the pointer to the first node.
103 using (BinaryWriter writer = new BinaryWriter (output, utf8)){
105 Dump (output, writer);
108 writer.Write (new byte [] { (byte) 'M', (byte) 'o', (byte) 'H', (byte) 'P' });
109 writer.Write (position);
114 static bool GoodSig (byte [] sig)
118 if (sig [0] != (byte) 'M' ||
119 sig [1] != (byte) 'o' ||
120 sig [2] != (byte) 'H' ||
121 sig [3] != (byte) 'P')
128 public class Node : IComparable {
129 string caption, element;
130 public bool Documented;
131 public readonly Tree tree;
133 protected ArrayList nodes;
134 protected internal int position;
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)
397 if (compare_key == null || other.compare_key == null) {
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 if (compare_key == null)
405 compare_key = digits.Replace (caption, eval);
406 if (other.compare_key == null)
407 other.compare_key = digits.Replace (other.caption, eval);
409 return compare_key.CompareTo (other.compare_key);
414 // The HelpSource class keeps track of the archived data, and its
417 public class HelpSource {
419 public static bool use_css = false;
420 public static string css_code;
421 public static string CssCode {
423 if (css_code != null)
426 System.Reflection.Assembly assembly = System.Reflection.Assembly.GetCallingAssembly ();
427 Stream str_css = assembly.GetManifestResourceStream ("base.css");
428 StringBuilder sb = new StringBuilder ((new StreamReader (str_css)).ReadToEnd());
429 sb.Replace ("@@FONT_FAMILY@@", SettingsHandler.Settings.preferred_font_family);
430 sb.Replace ("@@FONT_SIZE@@", SettingsHandler.Settings.preferred_font_size.ToString());
431 css_code = sb.ToString ();
434 set { css_code = value; }
437 public virtual string InlineCss {
438 get { return CssCode; }
441 public virtual string InlineJavaScript {
445 public static bool FullHtml = true;
447 // should only be enabled by ASP.NET webdoc
448 public static bool UseWebdocCache;
451 // The unique ID for this HelpSource.
454 DateTime zipFileWriteTime;
457 TraceLevel trace_level = TraceLevel.Warning;
458 protected bool nozip;
459 protected string base_dir;
461 public HelpSource (string base_filename, bool create)
463 this.name = Path.GetFileName (base_filename);
464 this.basepath = Path.GetDirectoryName (base_filename);
465 tree_filename = base_filename + ".tree";
466 zip_filename = base_filename + ".zip";
467 base_dir = XmlDocUtils.GetCacheDirectory (base_filename);
468 if (UseWebdocCache && !create && Directory.Exists (base_dir)) {
475 Tree = new Tree (this, tree_filename);
479 FileInfo fi = new FileInfo (zip_filename);
480 zipFileWriteTime = fi.LastWriteTime;
482 zipFileWriteTime = DateTime.Now;
486 public HelpSource() {
487 Tree = new Tree (this, "Blah", "Blah");
491 public DateTime ZipFileWriteTime {
493 return zipFileWriteTime;
497 public int SourceID {
509 /* This gives the full path of the source/ directory */
510 public string BaseFilePath {
516 public TraceLevel TraceLevel {
517 get { return trace_level; }
518 set { trace_level = value; }
521 public string BaseDir {
530 /// Returns a stream from the packaged help source archive
532 public virtual Stream GetHelpStream (string id)
535 string path = XmlDocUtils.GetCachedFileName (base_dir, id);
536 if (File.Exists (path))
537 return File.OpenRead (path);
541 if (zip_file == null)
542 zip_file = new ZipFile (zip_filename);
544 ZipEntry entry = zip_file.GetEntry (id);
546 return zip_file.GetInputStream (entry);
550 public string GetRealPath (string file)
552 if (zip_file == null)
553 zip_file = new ZipFile (zip_filename);
555 ZipEntry entry = zip_file.GetEntry (file);
556 if (entry != null && entry.ExtraData != null)
557 return ConvertToString (entry.ExtraData);
561 public XmlReader GetHelpXml (string id)
564 Stream s = File.OpenRead (XmlDocUtils.GetCachedFileName (base_dir, id));
565 string url = "monodoc:///" + SourceID + "@" + Uri.EscapeUriString (id) + "@";
566 return new XmlTextReader (url, s);
569 if (zip_file == null)
570 zip_file = new ZipFile (zip_filename);
572 ZipEntry entry = zip_file.GetEntry (id);
574 Stream s = zip_file.GetInputStream (entry);
575 string url = "monodoc:///" + SourceID + "@" + Uri.EscapeUriString (id) + "@";
576 return new XmlTextReader (url, s);
581 public virtual XmlDocument GetHelpXmlWithChanges (string id)
584 Stream s = File.OpenRead (XmlDocUtils.GetCachedFileName (base_dir, id));
585 string url = "monodoc:///" + SourceID + "@" + Uri.EscapeUriString (id) + "@";
586 XmlReader r = new XmlTextReader (url, s);
587 XmlDocument ret = new XmlDocument ();
592 if (zip_file == null)
593 zip_file = new ZipFile (zip_filename);
595 ZipEntry entry = zip_file.GetEntry (id);
597 Stream s = zip_file.GetInputStream (entry);
598 string url = "monodoc:///" + SourceID + "@" + Uri.EscapeUriString (id) + "@";
599 XmlReader r = new XmlTextReader (url, s);
600 XmlDocument ret = new XmlDocument ();
603 if (entry.ExtraData != null)
604 EditingUtils.AccountForChanges (ret, Name, ConvertToString (entry.ExtraData));
612 /// Get a nice, unique expression for any XPath node that you get.
613 /// This function is used by editing to get the expression to put
614 /// on to the file. The idea is to create an expression that is resistant
615 /// to changes in the structure of the XML.
617 public virtual string GetNodeXPath (XPathNavigator n)
619 return EditingUtils.GetXPath (n.Clone ());
622 public string GetEditUri (XPathNavigator n)
624 return EditingUtils.FormatEditUri (n.BaseURI, GetNodeXPath (n));
627 static string ConvertToString (byte[] data)
629 return Encoding.UTF8.GetString(data);
632 static byte[] ConvertToArray (string str)
634 return Encoding.UTF8.GetBytes(str);
638 /// The tree that is being populated
641 public RootTree RootTree;
643 // Base filename used by this HelpSource.
644 string tree_filename, zip_filename;
647 const int buffer_size = 65536;
648 ZipOutputStream zip_output;
651 HelpSource (string base_filename)
655 void SetupForOutput ()
657 Tree = new Tree (this, "", "");
659 FileStream stream = File.Create (zip_filename);
661 zip_output = new ZipOutputStream (stream);
662 zip_output.SetLevel (9);
664 buffer = new byte [buffer_size];
668 /// Saves the tree and the archive
672 Tree.Save (tree_filename);
673 zip_output.Finish ();
681 return String.Format ("{0}", code++);
685 /// Providers call this to store a file they will need, and the return value
686 /// is the name that was assigned to it
688 public string PackFile (string file)
690 string entry_name = GetNewCode ();
691 return PackFile (file, entry_name);
694 public string PackFile (string file, string entry_name)
696 using (FileStream input = File.OpenRead (file)) {
697 PackStream (input, entry_name, file);
703 public void PackStream (Stream s, string entry_name)
705 PackStream (s, entry_name, null);
708 void PackStream (Stream s, string entry_name, string realPath)
710 ZipEntry entry = new ZipEntry (entry_name);
712 if (realPath != null)
713 entry.ExtraData = ConvertToArray (realPath);
715 zip_output.PutNextEntry (entry);
718 while ((n = s.Read (buffer, 0, buffer_size)) > 0){
719 zip_output.Write (buffer, 0, n);
723 public void PackXml (string fname, XmlDocument doc, string real_path)
725 ZipEntry entry = new ZipEntry (fname);
726 if (real_path != null)
727 entry.ExtraData = ConvertToArray(real_path);
729 zip_output.PutNextEntry (entry);
730 XmlTextWriter xmlWriter = new XmlTextWriter (zip_output, Encoding.UTF8);
731 doc.WriteContentTo (xmlWriter);
735 public virtual void RenderPreviewDocs (XmlNode newNode, XmlWriter writer)
737 throw new NotImplementedException ();
740 public virtual string GetPublicUrl (string id)
745 public virtual string GetText (string url, out Node n)
751 protected string GetCachedText (string url)
755 string file = XmlDocUtils.GetCachedFileName (base_dir, url);
756 if (!File.Exists (file))
758 return File.OpenText (file).ReadToEnd ();
761 public virtual Stream GetImage (string url)
767 // Default method implementation does not satisfy the request
769 public virtual string RenderTypeLookup (string prefix, string ns, string type, string member, out Node n)
775 public virtual string RenderNamespaceLookup (string nsurl, out Node n)
782 // Populates the index.
784 public virtual void PopulateIndex (IndexMaker index_maker)
789 // Build an html document
791 public static string BuildHtml (string css, string html_code)
793 return BuildHtml (css, null, html_code);
796 internal static string BuildHtml (string css, string js, string html_code) {
800 StringWriter output = new StringWriter ();
801 output.Write ("<html><head>");
802 output.Write ("<style type=\"text/css\">");
803 output.Write (CssCode);
805 output.Write ("</style>");
807 System.Reflection.Assembly assembly = System.Reflection.Assembly.GetCallingAssembly ();
808 Stream str_js = assembly.GetManifestResourceStream ("helper.js");
809 StringBuilder sb = new StringBuilder ((new StreamReader (str_js)).ReadToEnd());
810 output.Write ("<script type=\"text/JavaScript\">\n");
811 output.Write (sb.ToString ());
812 output.Write ("</script>\n");
815 output.Write ("<script type=\"text/JavaScript\">\n");
817 output.Write ("\n</script>");
820 output.Write ("</head><body>");
821 output.Write (html_code);
822 output.Write ("</body></html>");
823 return output.ToString ();
827 // Create different Documents for adding to Lucene search index
828 // The default action is do nothing. Subclasses should add the docs
830 public virtual void PopulateSearchableIndex (IndexWriter writer) {
834 public void Message (TraceLevel level, string format, params object[] args)
836 if ((int) level <= (int) trace_level)
837 Console.WriteLine (format, args);
840 public void Error (string format, params object[] args)
842 Console.Error.WriteLine (format, args);
846 public abstract class Provider {
848 // This code is used to "tag" all the different sources
859 public abstract void PopulateTree (Tree tree);
862 // Called at shutdown time after the tree has been populated to perform
863 // any fixups or final tasks.
865 public abstract void CloseTree (HelpSource hs, Tree tree);
868 public class RootTree : Tree {
871 public static ArrayList UncompiledHelpSources = new ArrayList();
873 public const int MonodocVersion = 1;
875 public static RootTree LoadTree ()
877 return LoadTree (null);
880 const string MacMonoDocDir = "/Library/Frameworks/Mono.framework/Versions/Current/lib/monodoc";
883 // Loads the tree layout
885 public static RootTree LoadTree (string basedir)
887 if (basedir == null) {
888 string myPath = System.Reflection.Assembly.GetExecutingAssembly ().Location;
889 string cfgFile = myPath + ".config";
890 if (!File.Exists (cfgFile)) {
894 XmlDocument d = new XmlDocument ();
896 basedir = d.SelectSingleNode ("config/path").Attributes ["docsPath"].Value;
898 // Temporary workaround for developers distributing a monodoc.dll themselves on Mac
899 if (Directory.Exists (MacMonoDocDir)){
900 Console.WriteLine ("MacDir exists");
901 if (!File.Exists (Path.Combine (basedir, "monodoc.xml"))){
902 basedir = MacMonoDocDir;
910 XmlDocument doc = new XmlDocument ();
911 string layout = Path.Combine (basedir, "monodoc.xml");
914 string osxExternalDir = "/Library/Frameworks/Mono.framework/External/monodoc";
915 string[] osxExternalSources = Directory.Exists (osxExternalDir)
916 ? Directory.GetFiles (osxExternalDir, "*.source")
919 return LoadTree (basedir, doc,
920 Directory.GetFiles (Path.Combine (basedir, "sources"), "*.source")
921 .Concat (osxExternalSources));
924 // Compatibility shim w/ Mono 2.6
925 public static RootTree LoadTree (string indexDir, XmlDocument docTree, IEnumerable sourceFiles)
927 return LoadTree (indexDir, docTree, sourceFiles.Cast<string>());
930 public static RootTree LoadTree (string indexDir, XmlDocument docTree, IEnumerable<string> sourceFiles)
932 if (docTree == null) {
933 docTree = new XmlDocument ();
934 using (var defTree = typeof(RootTree).Assembly.GetManifestResourceStream ("monodoc.xml"))
935 docTree.Load (defTree);
939 sourceFiles = sourceFiles ?? new string [0];
945 RootTree root = new RootTree ();
946 root.basedir = indexDir;
948 XmlNodeList nodes = docTree.SelectNodes ("/node/node");
950 root.name_to_node ["root"] = root;
951 root.name_to_node ["libraries"] = root;
952 root.Populate (root, nodes);
954 Node third_party = root.LookupEntryPoint ("various");
955 if (third_party == null) {
956 Console.Error.WriteLine ("No 'various' doc node! Check monodoc.xml!");
963 foreach (var sourceFile in sourceFiles)
964 root.AddSourceFile (sourceFile);
966 foreach (string path in UncompiledHelpSources) {
967 EcmaUncompiledHelpSource hs = new EcmaUncompiledHelpSource(path);
969 root.help_sources.Add (hs);
970 string epath = "extra-help-source-" + hs.Name;
971 Node hsn = root.CreateNode (hs.Name, "root:/" + epath);
972 root.name_to_hs [epath] = hs;
974 foreach (Node n in hs.Tree.Nodes){
987 public void AddSource (string sources_dir)
989 string [] files = Directory.GetFiles (sources_dir);
991 foreach (string file in files){
992 if (!file.EndsWith (".source"))
994 AddSourceFile (file);
998 Dictionary<string,string> loadedSourceFiles = new Dictionary<string,string> ();
1000 public void AddSourceFile (string sourceFile)
1002 if (loadedSourceFiles.ContainsKey (sourceFile))
1005 Node third_party = LookupEntryPoint ("various") ?? this;
1007 XmlDocument doc = new XmlDocument ();
1009 doc.Load (sourceFile);
1012 Console.Error.WriteLine ("Error: Could not load source file {0}", sourceFile);
1016 XmlNodeList extra_nodes = doc.SelectNodes ("/monodoc/node");
1017 if (extra_nodes.Count > 0)
1018 Populate (third_party, extra_nodes);
1020 XmlNodeList sources = doc.SelectNodes ("/monodoc/source");
1021 if (sources == null){
1022 Console.Error.WriteLine ("Error: No <source> section found in the {0} file", sourceFile);
1025 loadedSourceFiles [sourceFile] = sourceFile;
1026 foreach (XmlNode source in sources){
1027 XmlAttribute a = source.Attributes ["provider"];
1029 Console.Error.WriteLine ("Error: no provider in <source>");
1032 string provider = a.InnerText;
1033 a = source.Attributes ["basefile"];
1035 Console.Error.WriteLine ("Error: no basefile in <source>");
1038 string basefile = a.InnerText;
1039 a = source.Attributes ["path"];
1041 Console.Error.WriteLine ("Error: no path in <source>");
1044 string path = a.InnerText;
1046 string basefilepath = Path.Combine (Path.GetDirectoryName (sourceFile), basefile);
1047 HelpSource hs = GetHelpSource (provider, basefilepath);
1051 help_sources.Add (hs);
1052 name_to_hs [path] = hs;
1054 Node parent = LookupEntryPoint (path);
1055 if (parent == null){
1056 Console.Error.WriteLine ("node `{0}' is not defined on the documentation map", path);
1057 parent = third_party;
1060 foreach (Node n in hs.Tree.Nodes){
1067 // Delete nodes which does not have documentaiton (source)
1068 static bool PurgeNode(Node node)
1072 if (!node.Documented)
1074 ArrayList del_child = new ArrayList();
1075 //Delete node unless any child has documentation
1076 bool purged_child = false;
1077 foreach (Node child in node.Nodes)
1079 purged_child = PurgeNode(child);
1082 del_child.Add(child);
1086 // delete the node if all its children are to be deleted
1087 purge = (node.Nodes.Count == del_child.Count);
1090 foreach (Node child in del_child)
1092 node.DelNode(child);
1099 public static string[] GetSupportedFormats ()
1101 return new string[]{
1112 public static HelpSource GetHelpSource (string provider, string basefilepath)
1117 return new EcmaHelpSource (basefilepath, false);
1118 case "ecma-uncompiled":
1119 return new EcmaUncompiledHelpSource (basefilepath);
1121 return new MonoHBHelpSource(basefilepath, false);
1122 case "xhtml": case "hb":
1123 return new XhtmlHelpSource (basefilepath, false);
1125 return new ManHelpSource (basefilepath, false);
1127 return new SimpleHelpSource (basefilepath, false);
1129 return new ErrorHelpSource (basefilepath, false);
1131 return new EcmaSpecHelpSource (basefilepath, false);
1133 return new AddinsHelpSource (basefilepath, false);
1135 Console.Error.WriteLine ("Error: Unknown provider specified: {0}", provider);
1140 catch (FileNotFoundException) {
1141 Console.Error.WriteLine ("Error: did not find one of the files in sources/"+basefilepath);
1146 public static Provider GetProvider (string provider, params string[] basefilepaths)
1150 return new AddinsProvider (basefilepaths [0]);
1152 EcmaProvider p = new EcmaProvider ();
1153 foreach (string d in basefilepaths)
1158 return new EcmaSpecProvider (basefilepaths [0]);
1160 return new ErrorProvider (basefilepaths [0]);
1162 return new ManProvider (basefilepaths);
1164 return new SimpleProvider (basefilepaths [0]);
1167 return new XhtmlProvider (basefilepaths [0]);
1169 throw new NotSupportedException (provider);
1174 // Maintains the name to node mapping
1176 Hashtable name_to_node = new Hashtable ();
1177 Hashtable name_to_hs = new Hashtable ();
1179 void Populate (Node parent, XmlNodeList xml_node_list)
1181 foreach (XmlNode xml_node in xml_node_list){
1182 XmlAttribute e = xml_node.Attributes ["parent"];
1183 if (e != null && name_to_node.ContainsKey (e.InnerText)) {
1184 Node p = (Node) name_to_node [e.InnerText];
1185 xml_node.Attributes.Remove (e);
1186 Populate (p, xml_node.SelectNodes ("."));
1189 e = xml_node.Attributes ["label"];
1191 Console.Error.WriteLine ("`label' attribute missing in <node>");
1194 string label = e.InnerText;
1195 e = xml_node.Attributes ["name"];
1197 Console.Error.WriteLine ("`name' attribute missing in <node>");
1200 string name = e.InnerText;
1202 Node n = parent.LookupNode (label, "root:/" + name);
1204 name_to_node [name] = n;
1205 XmlNodeList children = xml_node.SelectNodes ("./node");
1206 if (children != null)
1207 Populate (n, children);
1211 public Node LookupEntryPoint (string name)
1213 return (Node) name_to_node [name];
1216 ArrayList help_sources;
1217 DateTime lastHelpSourceTime;
1219 RootTree () : base (null, "Mono Documentation", "root:")
1221 nodes = new ArrayList ();
1222 help_sources = new ArrayList ();
1223 lastHelpSourceTime = DateTime.MinValue;
1226 public DateTime LastHelpSourceTime {
1228 return lastHelpSourceTime;
1232 public static bool GetNamespaceAndType (string url, out string ns, out string type)
1236 for (int i = 0; i < url.Length; ++i) {
1259 ns = url.Substring (0, nsidx);
1260 type = url.Substring (nsidx + 1);
1262 //Console.Error.WriteLine ("GetNameSpaceAndType (ns={0}, type={1}", ns, type);
1266 public XmlDocument GetHelpXml (string url)
1268 string rest = url.Substring (2);
1271 if (!GetNamespaceAndType (rest, out ns, out type))
1274 foreach (HelpSource hs in help_sources) {
1275 EcmaHelpSource ehs = hs as EcmaHelpSource;
1278 string id = ehs.GetIdFromUrl ("T:", ns, type);
1281 XmlDocument doc = hs.GetHelpXmlWithChanges (id);
1288 public string TypeLookup (string url, out Node match_node)
1290 string rest = Regex.Replace (url, @"^T:\s*", "");
1293 if (!GetNamespaceAndType (rest, out ns, out type)){
1298 foreach (HelpSource hs in help_sources){
1299 string s = hs.RenderTypeLookup ("T:", ns, type, null, out match_node);
1302 lastHelpSourceTime = hs.ZipFileWriteTime;
1310 public string MemberLookup (string prefix, string url, out Node match_node)
1312 string rest = Regex.Replace (url, @"^.:\s*", "");
1314 // Dots in the arg list (for methods) confuse this.
1315 // Chop off the arg list for now and put it back later.
1316 string arglist = "";
1317 int argliststart = rest.IndexOf("(");
1318 if (argliststart >= 0) {
1319 arglist = rest.Substring(argliststart);
1320 rest = rest.Substring(0, argliststart);
1323 string ns_type, member;
1325 if (prefix != "C:") {
1326 int member_idx = rest.LastIndexOf (".");
1328 // The dot in .ctor (if it's a M: link) would confuse this.
1329 if (rest.EndsWith("..ctor")) member_idx--;
1331 ns_type = rest.Substring (0, member_idx);
1332 member = rest.Substring (member_idx + 1);
1334 // C: links don't have the .ctor member part as it would in a M: link
1335 // Even though externally C: links are different from M: links,
1336 // C: links get transformed into M:-style links (with .ctor) here.
1342 //Console.WriteLine ("NS_TYPE: {0} MEMBER: {1}", ns_type, member);
1345 if (!GetNamespaceAndType (ns_type, out ns, out type)){
1350 foreach (HelpSource hs in help_sources){
1351 string s = hs.RenderTypeLookup (prefix, ns, type, member + arglist, out match_node);
1354 lastHelpSourceTime = hs.ZipFileWriteTime;
1362 public Stream GetImage (string url)
1364 if (url.StartsWith ("source-id:")){
1365 string rest = url.Substring (10);
1366 int p = rest.IndexOf (":");
1367 string str_idx = rest.Substring (0, p);
1371 idx = Int32.Parse (str_idx);
1373 Console.Error.WriteLine ("Failed to parse source-id url: {0} `{1}'", url, str_idx);
1377 HelpSource hs = GetHelpSourceFromId (idx);
1378 lastHelpSourceTime = hs.ZipFileWriteTime;
1379 return hs.GetImage (rest.Substring (p + 1));
1381 System.Reflection.Assembly assembly = System.Reflection.Assembly.GetAssembly (typeof(RootTree));
1382 return assembly.GetManifestResourceStream (url);
1384 lastHelpSourceTime = DateTime.MinValue;
1388 public HelpSource GetHelpSourceFromId (int id)
1390 return (HelpSource) help_sources [id];
1394 // Fetches the node title
1396 public string GetTitle (string url)
1400 if (url == null || url.StartsWith ("root:"))
1401 return "Mono Documentation";
1403 if (url.Length > 2 && url [1] == ':'){
1406 return url.Substring (2) + " Namespace";
1409 string s = TypeLookup (url, out match_node);
1410 if (match_node != null)
1411 return match_node.Caption;
1412 return url.Substring (2) + " type";
1420 MemberLookup (url.Substring (0,2), url, out match_node);
1421 if (match_node != null)
1422 return match_node.Caption;
1427 return "Mono Documentation";
1432 /// Allows every HelpSource to try to provide the content for this
1435 public string RenderUrl (string url, out Node match_node)
1437 lastHelpSourceTime = DateTime.MinValue;
1438 if (url == "root:") {
1441 // look whether there are contribs
1442 GlobalChangeset chgs = EditingUtils.changes;
1443 StringBuilder con = new StringBuilder ();
1445 //add links to the contrib
1446 int oldContrib = 0, contribs = 0;
1447 con.Append ("<ul>");
1448 foreach (DocSetChangeset dscs in chgs.DocSetChangesets)
1449 foreach (FileChangeset fcs in dscs.FileChangesets)
1450 foreach (Change c in fcs.Changes) {
1451 if (c.NodeUrl == null) {
1452 if (c.Serial == SettingsHandler.Settings.SerialNumber)
1454 } else if (c.Serial == SettingsHandler.Settings.SerialNumber) {
1456 con.Append (String.Format ("<li><a href=\"{0}\">{0}</a></li>", c.NodeUrl));
1460 string contrib = (oldContrib + contribs) == 1?"There is {0} contribution":"There are {0} contributions";
1461 con.Insert (0, String.Format (contrib, oldContrib + contribs) + " pending upload <i>(Contributing--> Upload)</i>", 1);
1462 con.Append ("</ul>");
1463 if (oldContrib == 1)
1464 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>");
1465 else if (oldContrib > 1)
1466 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>");
1468 //start the rendering
1469 if (!HelpSource.use_css) {
1470 StringBuilder sb = new StringBuilder ("<table bgcolor=\"#b0c4de\" width=\"100%\" cellpadding=\"5\"><tr><td><h3>Mono Documentation Library</h3></td></tr></table>");
1472 foreach (Node n in Nodes)
1473 sb.AppendFormat ("<a href='{0}'>{1}</a><br/>", n.Element, n.Caption);
1476 sb.Append ("<br><table bgcolor=\"#fff3f3\" width=\"100%\" cellpadding=\"5\"><tr><td>");
1477 sb.Append ("<h5>Contributions</h5><br>");
1478 if ((oldContrib + contribs) == 0) {
1479 sb.Append ("<p><b>You have not made any contributions yet.</b></p>");
1480 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>");
1481 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>");
1483 sb.Append (con.ToString ());
1485 sb.Append ("</td></tr></table>");
1486 return sb.ToString ();
1488 if (home_cache == null) {
1489 System.Reflection.Assembly assembly = System.Reflection.Assembly.GetAssembly (typeof (HelpSource));
1490 Stream hp_stream = assembly.GetManifestResourceStream ("home.html");
1491 home_cache = (new StreamReader (hp_stream)).ReadToEnd ();
1493 StringBuilder sb = new StringBuilder (home_cache);
1495 sb.Replace ("@@FONT_FAMILY@@", SettingsHandler.Settings.preferred_font_family);
1496 sb.Replace ("@@FONT_SIZE@@", SettingsHandler.Settings.preferred_font_size.ToString());
1498 var visible = SettingsHandler.Settings.EnableEditing ? "block;" : "none;";
1499 if ((oldContrib + contribs) == 0) {
1500 sb.Replace ("@@CONTRIB_DISP@@", "display: none;");
1501 sb.Replace ("@@NO_CONTRIB_DISP@@", "display: " + visible);
1503 sb.Replace ("@@CONTRIB_DISP@@", "display: " + visible);
1504 sb.Replace ("@@NO_CONTRIB_DISP@@", "display: none;");
1505 sb.Replace ("@@CONTRIBS@@", con.ToString ());
1507 sb.Replace ("@@EDITING_ENABLED@@", "display: " + visible);
1509 // load the url of nodes
1511 StringBuilder urls = new StringBuilder ();
1512 foreach (Node n in Nodes) {
1513 add_str = String.Format ("<li><a href=\"{0}\">{1}</a></li>", n.Element, n.Caption);
1514 urls.Append (add_str);
1516 sb.Replace ("@@API_DOCS@@", urls.ToString ());
1518 return sb.ToString ();
1522 if (url.StartsWith ("root:")) {
1523 match_node = ((Node)name_to_node [url.Substring (6)]);
1524 HelpSource hs = ((HelpSource)name_to_hs [url.Substring (6)]);
1527 return GenerateNodeIndex(match_node);
1531 lastHelpSourceTime = hs.ZipFileWriteTime;
1532 return hs.GetText ("root:", out dummy);
1536 if (url.StartsWith ("source-id:")){
1537 string rest = url.Substring (10);
1538 int p = rest.IndexOf (":");
1539 string str_idx = rest.Substring (0, p);
1543 idx = Int32.Parse (str_idx);
1545 Console.Error.WriteLine ("Failed to parse source-id url: {0} `{1}'", url, str_idx);
1549 HelpSource hs = (HelpSource) help_sources [idx];
1550 // Console.WriteLine ("Attempting to get docs from: " + rest.Substring (p + 1));
1551 lastHelpSourceTime = hs.ZipFileWriteTime;
1552 return hs.GetText (rest.Substring (p + 1), out match_node);
1555 if (url.Length < 2){
1560 string prefix = url.Substring (0, 2);
1562 switch (prefix.ToUpper ()){
1564 foreach (HelpSource hs in help_sources){
1565 string s = hs.RenderNamespaceLookup (url, out match_node);
1567 lastHelpSourceTime = hs.ZipFileWriteTime;
1575 return TypeLookup (url, out match_node);
1583 return MemberLookup (prefix, url, out match_node);
1586 foreach (HelpSource hs in help_sources){
1587 string s = hs.GetText (url, out match_node);
1590 lastHelpSourceTime = hs.ZipFileWriteTime;
1599 public string GenerateNodeIndex (Node node)
1601 StringBuilder buf = new StringBuilder();
1602 buf.AppendFormat("<H3>{0}</H3>", node.Caption);
1604 foreach (Node child in node.Nodes)
1606 buf.AppendFormat("<li><a href=\"{0}\">{1}</a>", child.URL, child.Caption);
1608 buf.Append("</ul>");
1609 return buf.ToString();
1612 public IndexReader GetIndex ()
1614 //try to load from basedir
1615 string index_file = Path.Combine (basedir, "monodoc.index");
1616 if (File.Exists (index_file))
1617 return IndexReader.Load (index_file);
1618 //then, try to load from config dir
1619 index_file = Path.Combine (SettingsHandler.Path, "monodoc.index");
1620 return IndexReader.Load (index_file);
1624 public static void MakeIndex ()
1626 MakeIndex (LoadTree ());
1629 public static void MakeIndex (RootTree root)
1634 IndexMaker index_maker = new IndexMaker ();
1636 foreach (HelpSource hs in root.help_sources){
1637 hs.PopulateIndex (index_maker);
1640 // if the user has no write permissions use config dir
1641 string path = Path.Combine (root.basedir, "monodoc.index");
1643 index_maker.Save (path);
1644 } catch (System.UnauthorizedAccessException) {
1645 path = Path.Combine (SettingsHandler.Path, "monodoc.index");
1647 index_maker.Save (path);
1648 } catch (System.UnauthorizedAccessException) {
1649 Console.WriteLine ("Unable to write index file in {0}", Path.Combine (SettingsHandler.Path, "monodoc.index"));
1655 // No octal in C#, how lame is that
1656 chmod (path, 0x1a4);
1658 Console.WriteLine ("Documentation index at {0} updated", path);
1661 static bool IsUnix {
1663 int p = (int) Environment.OSVersion.Platform;
1664 return ((p == 4) || (p == 128) || (p == 6));
1669 public SearchableIndex GetSearchIndex ()
1671 //try to load from basedir
1672 string index_file = Path.Combine (basedir, "search_index");
1673 if (Directory.Exists (index_file))
1674 return SearchableIndex.Load (index_file);
1675 //then, try to load from config dir
1676 index_file = Path.Combine (SettingsHandler.Path, "search_index");
1677 return SearchableIndex.Load (index_file);
1680 public static void MakeSearchIndex ()
1682 MakeSearchIndex (LoadTree ());
1685 public static void MakeSearchIndex (RootTree root)
1687 // Loads the RootTree
1688 Console.WriteLine ("Loading the monodoc tree...");
1693 string dir = Path.Combine (root.basedir, "search_index");
1695 //try to create the dir to store the index
1697 if (!Directory.Exists (dir))
1698 Directory.CreateDirectory (dir);
1700 writer = new IndexWriter(Mono.Lucene.Net.Store.FSDirectory.GetDirectory(dir, true), new StandardAnalyzer(), true);
1701 } catch (UnauthorizedAccessException) {
1702 //try in the .config directory
1704 dir = Path.Combine (SettingsHandler.Path, "search_index");
1705 if (!Directory.Exists (dir))
1706 Directory.CreateDirectory (dir);
1708 writer = new IndexWriter(Mono.Lucene.Net.Store.FSDirectory.GetDirectory(dir, true), new StandardAnalyzer(), true);
1709 } catch (UnauthorizedAccessException) {
1710 Console.WriteLine ("You don't have permissions to write on " + dir);
1715 //Collect all the documents
1716 Console.WriteLine ("Collecting and adding documents...");
1717 foreach (HelpSource hs in root.HelpSources)
1718 hs.PopulateSearchableIndex (writer);
1720 //Optimize and close
1721 Console.WriteLine ("Closing...");
1727 public ICollection HelpSources { get { return new ArrayList(help_sources); } }
1729 [System.Runtime.InteropServices.DllImport ("libc")]
1730 static extern int chmod (string filename, int mode);