6 using System.Collections.Generic;
11 /// This tree is populated by the documentation providers, or populated
12 /// from a binary encoding of the tree. The format of the tree is designed
13 /// to minimize the need to load it in full.
16 /* Ideally this class should also be abstracted to let user have something
17 * else than a file as a backing store, a database for instance
21 public readonly HelpSource HelpSource;
23 FileStream InputStream;
24 BinaryReader InputReader;
26 // This is the node which contains all the other node of the tree
30 /// Load from file constructor
32 public Tree (HelpSource hs, string filename)
34 Encoding utf8 = new UTF8Encoding (false, true);
36 if (!File.Exists (filename)){
37 throw new FileNotFoundException ();
40 InputStream = File.OpenRead (filename);
41 InputReader = new BinaryReader (InputStream, utf8);
42 byte [] sig = InputReader.ReadBytes (4);
45 throw new Exception ("Invalid file format");
47 InputStream.Position = 4;
48 var position = InputReader.ReadInt32 ();
49 rootNode = new Node (this, position);
50 InflateNode (rootNode);
56 /// Tree creation and merged tree constructor
58 public Tree (HelpSource hs, string caption, string url) : this (hs, null, caption, url)
62 public Tree (HelpSource hs, Node parent, string caption, string element)
65 rootNode = parent == null ? new Node (this, caption, element) : new Node (parent, caption, element);
69 /// Saves the tree into the specified file using the help file format.
71 public void Save (string file)
73 Encoding utf8 = new UTF8Encoding (false, true);
74 using (FileStream output = File.OpenWrite (file)){
75 // Skip over the pointer to the first node.
78 using (BinaryWriter writer = new BinaryWriter (output, utf8)) {
80 rootNode.Serialize (output, writer);
83 writer.Write (new byte [] { (byte) 'M', (byte) 'o', (byte) 'H', (byte) 'P' });
84 writer.Write (rootNode.Address);
89 public Node RootNode {
95 static bool GoodSig (byte [] sig)
99 return sig [0] == (byte) 'M'
100 && sig [1] == (byte) 'o'
101 && sig [2] == (byte) 'H'
102 && sig [3] == (byte) 'P';
105 public void InflateNode (Node baseNode)
107 var address = baseNode.Address;
111 InputStream.Position = address;
112 baseNode.Deserialize (InputReader);
116 public class Node : IComparable<Node>, IComparable
119 string caption, element;
120 public bool Documented;
124 Dictionary<string, Node> childrenLookup;
125 /* Address has three types of value,
126 * _ 0 is for no on-disk representation
127 * _ >0 is a valid address that is loaded immediately
128 * _ <0 is a valid negated address to indicate lazy loading
132 public Node (Node parent, string caption, string element) : this (parent.Tree, caption, element)
134 this.parent = parent;
137 internal Node (Tree tree, string caption, string element)
140 this.caption = caption;
141 this.element = element;
145 /// Creates a node from an on-disk representation
147 internal Node (Node parent, int address) : this (parent.tree, address)
149 this.parent = parent;
152 internal Node (Tree tree, int address)
154 this.address = address;
160 /* This is solely used for MatchNode to check for equality */
167 tree.InflateNode (this);
169 parent.RegisterFullNode (this);
172 public void AddNode (Node n)
177 RegisterFullNode (n);
180 public void DeleteNode (Node n)
183 if (!string.IsNullOrEmpty (n.element))
184 childrenLookup.Remove (n.element);
187 // When a child node is inflated, it calls this method
188 // so that we can add it to our lookup for quick search
189 void RegisterFullNode (Node child)
191 if (childrenLookup == null)
192 childrenLookup = new Dictionary<string, Node> ();
193 if (!string.IsNullOrEmpty (child.element))
194 childrenLookup[child.element] = child;
197 public List<Node> Nodes {
200 return nodes != null ? nodes : new List<Node> ();
204 public string Element {
214 public string Caption {
236 internal int Address {
243 /// Creates a new node, in the locator entry point, and with
244 /// a user visible caption of @caption
246 public Node CreateNode (string c_caption, string c_element)
250 Node t = new Node (this, c_caption, c_element);
252 childrenLookup[c_element] = t;
257 public Node GetOrCreateNode (string c_caption, string c_element)
260 return CreateNode (c_caption, c_element);
261 if (childrenLookup.Count != nodes.Count || (nodes.Count == 0 && childrenLookup.Count != nodes.Capacity))
265 if (!childrenLookup.TryGetValue (c_element, out result))
266 result = CreateNode (c_caption, c_element);
270 public void EnsureNodes ()
273 nodes = new List<Node> ();
274 childrenLookup = new Dictionary<string, Node> ();
278 public void EnsureLoaded ()
280 if (address < 0 && !loaded) {
288 foreach (var node in nodes)
289 childrenLookup[node.Element] = node;
294 return nodes == null || nodes.Count == 0;
298 void EncodeInt (BinaryWriter writer, int value)
301 int high = (value >> 7) & 0x01ffffff;
302 byte b = (byte)(value & 0x7f);
305 b = (byte)(b | 0x80);
313 int DecodeInt (BinaryReader reader)
320 b = reader.ReadByte();
322 ret = ret | ((b & 0x7f) << shift);
324 } while ((b & 0x80) == 0x80);
329 internal void Deserialize (BinaryReader reader)
331 int count = DecodeInt (reader);
332 element = reader.ReadString ();
333 caption = reader.ReadString ();
338 nodes = new List<Node> (count);
339 for (int i = 0; i < count; i++) {
340 int child_address = DecodeInt (reader);
342 Node t = new Node (this, -child_address);
347 internal void Serialize (FileStream output, BinaryWriter writer)
350 foreach (Node child in nodes)
351 child.Serialize (output, writer);
353 address = (int) output.Position;
354 EncodeInt (writer, nodes == null ? 0 : (int) nodes.Count);
355 writer.Write (element);
356 writer.Write (caption);
359 foreach (Node child in nodes)
360 EncodeInt (writer, child.address);
369 internal string GetInternalUrl ()
372 if (element.IndexOf (":") != -1 || parent == null)
375 var parentUrl = parent.GetInternalUrl ();
376 return parentUrl.EndsWith ("/") ? parentUrl + element : parentUrl + "/" + element;
379 public string PublicUrl {
381 var url = GetInternalUrl ();
382 return tree.HelpSource != null ? tree.HelpSource.GetPublicUrl (this) : url;
386 int IComparable.CompareTo (object obj)
388 Node other = obj as Node;
391 return CompareToInternal (other);
394 int IComparable<Node>.CompareTo (Node obj)
396 return CompareToInternal (obj);
399 int CompareToInternal (Node other)
402 other.EnsureLoaded ();
405 var cap2 = other.caption;
407 /* Some node (notably from ecmaspec) have number prepended to them
408 * which we need to sort better by padding them to the same number
411 if (char.IsDigit (cap1[0]) && char.IsDigit (cap2[0])) {
412 int c1 = cap1.TakeWhile (char.IsDigit).Count ();
413 int c2 = cap2.TakeWhile (char.IsDigit).Count ();
416 cap1 = cap1.PadLeft (cap1.Length + Math.Max (0, c2 - c1), '0');
417 cap2 = cap2.PadLeft (cap2.Length + Math.Max (0, c1 - c2), '0');
421 return string.Compare (cap1, cap2, StringComparison.OrdinalIgnoreCase);
425 public static class TreeDumper
429 static void Indent ()
431 for (int i = 0; i < indent; i++)
435 public static void PrintTree (Node node)
438 Console.WriteLine ("{0},{1}\t[PublicUrl: {2}]", node.Element, node.Caption, node.PublicUrl);
439 if (node.Nodes.Count == 0)
443 foreach (Node n in node.Nodes)
448 public static string ExportToTocXml (Node root, string title, string desc)
451 throw new ArgumentNullException ("root");
452 // Return a toc index of sub-nodes
453 StringBuilder buf = new StringBuilder ();
454 var writer = XmlWriter.Create (buf);
455 writer.WriteStartElement ("toc");
456 writer.WriteAttributeString ("title", title ?? string.Empty);
457 writer.WriteElementString ("description", desc ?? string.Empty);
458 writer.WriteStartElement ("list");
459 foreach (Node n in root.Nodes) {
460 writer.WriteStartElement ("item");
461 writer.WriteAttributeString ("url", n.Element);
462 writer.WriteValue (n.Caption);
463 writer.WriteEndElement ();
465 writer.WriteEndElement ();
466 writer.WriteEndElement ();
470 return buf.ToString ();