using System; using System.IO; using System.Text; using System.Linq; using System.Xml; using System.Collections.Generic; namespace Monodoc { /// /// This tree is populated by the documentation providers, or populated /// from a binary encoding of the tree. The format of the tree is designed /// to minimize the need to load it in full. /// /* Ideally this class should also be abstracted to let user have something * else than a file as a backing store, a database for instance */ public class Tree #if LEGACY_MODE : Node #endif { public const long CurrentVersionNumber = 1; const int VersionNumberKey = -(int)'v'; public readonly HelpSource HelpSource; FileStream InputStream; BinaryReader InputReader; #if !LEGACY_MODE // This is the node which contains all the other node of the tree Node rootNode; #endif /// /// Load from file constructor /// public Tree (HelpSource hs, string filename) #if LEGACY_MODE : base (null, null) #endif { HelpSource = hs; Encoding utf8 = new UTF8Encoding (false, true); if (!File.Exists (filename)){ throw new FileNotFoundException (); } InputStream = File.OpenRead (filename); InputReader = new BinaryReader (InputStream, utf8); byte [] sig = InputReader.ReadBytes (4); if (!GoodSig (sig)) throw new Exception ("Invalid file format"); InputStream.Position = 4; // Try to read old version information if (InputReader.ReadInt32 () == VersionNumberKey) VersionNumber = InputReader.ReadInt64 (); else { // We try to see if there is a version number at the end of the file InputStream.Seek (-(4 + 8), SeekOrigin.End); // VersionNumberKey + long try { if (InputReader.ReadInt32 () == VersionNumberKey) VersionNumber = InputReader.ReadInt64 (); } catch {} // We set the stream back at the beginning of the node definition list InputStream.Position = 4; } var position = InputReader.ReadInt32 (); #if !LEGACY_MODE rootNode = new Node (this, position); #else Address = position; #endif InflateNode (RootNode); } /// /// Tree creation and merged tree constructor /// public Tree (HelpSource hs, string caption, string url) #if !LEGACY_MODE : this (hs, null, caption, url) { } #else : base (caption, url) { HelpSource = hs; } #endif public Tree (HelpSource hs, Node parent, string caption, string element) #if LEGACY_MODE : base (parent, caption, element) #endif { HelpSource = hs; #if !LEGACY_MODE rootNode = parent == null ? new Node (this, caption, element) : new Node (parent, caption, element); #endif } /// /// Saves the tree into the specified file using the help file format. /// public void Save (string file) { Encoding utf8 = new UTF8Encoding (false, true); using (FileStream output = File.OpenWrite (file)){ // Skip over the pointer to the first node. output.Position = 4 + 4; using (BinaryWriter writer = new BinaryWriter (output, utf8)) { // Recursively dump RootNode.Serialize (output, writer); // We want to generate 2.10 compatible files so we write the version number at the end writer.Write (VersionNumberKey); writer.Write (CurrentVersionNumber); output.Position = 0; writer.Write (new byte [] { (byte) 'M', (byte) 'o', (byte) 'H', (byte) 'P' }); writer.Write (RootNode.Address); } } } public Node RootNode { get { #if LEGACY_MODE return this; #else return rootNode; #endif } } public long VersionNumber { get; private set; } static bool GoodSig (byte [] sig) { if (sig.Length != 4) return false; return sig [0] == (byte) 'M' && sig [1] == (byte) 'o' && sig [2] == (byte) 'H' && sig [3] == (byte) 'P'; } public void InflateNode (Node baseNode) { var address = baseNode.Address; if (address < 0) address = -address; InputStream.Position = address; baseNode.Deserialize (InputReader); } // Nodes use this value to know if they should manually re-sort their child // if they come from an older generator version internal bool ForceResort { get { return VersionNumber == 0; } } } public static class TreeDumper { static int indent; static void Indent () { for (int i = 0; i < indent; i++) Console.Write (" "); } public static void PrintTree (Node node) { Indent (); Console.WriteLine ("{0},{1}\t[PublicUrl: {2}]", node.Element, node.Caption, node.PublicUrl); if (node.ChildNodes.Count == 0) return; indent++; foreach (Node n in node.ChildNodes) PrintTree (n); indent--; } public static string ExportToTocXml (Node root, string title, string desc) { if (root == null) throw new ArgumentNullException ("root"); // Return a toc index of sub-nodes StringBuilder buf = new StringBuilder (); var writer = XmlWriter.Create (buf); writer.WriteStartElement ("toc"); writer.WriteAttributeString ("title", title ?? string.Empty); writer.WriteElementString ("description", desc ?? string.Empty); writer.WriteStartElement ("list"); foreach (Node n in root.ChildNodes) { writer.WriteStartElement ("item"); writer.WriteAttributeString ("url", n.Element); writer.WriteValue (n.Caption); writer.WriteEndElement (); } writer.WriteEndElement (); writer.WriteEndElement (); writer.Flush (); writer.Close (); return buf.ToString (); } } }