536cd135cc
Former-commit-id: 5624ac747d633e885131e8349322922b6a59baaa
385 lines
8.3 KiB
C#
385 lines
8.3 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Text;
|
|
using System.Linq;
|
|
using System.Xml;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
|
|
namespace Monodoc
|
|
{
|
|
public
|
|
#if LEGACY_MODE
|
|
partial
|
|
#endif
|
|
class Node : IComparable<Node>, IComparable
|
|
{
|
|
readonly Tree parentTree;
|
|
string caption, element, pubUrl;
|
|
public bool Documented;
|
|
bool loaded;
|
|
Node parent;
|
|
List<Node> nodes;
|
|
#if LEGACY_MODE
|
|
ArrayList legacyNodes;
|
|
#endif
|
|
Dictionary<string, Node> childrenLookup;
|
|
bool elementSort;
|
|
/* Address has three types of value,
|
|
* _ 0 is for no on-disk representation
|
|
* _ >0 is a valid address that is loaded immediately
|
|
* _ <0 is a valid negated address to indicate lazy loading
|
|
*/
|
|
int address;
|
|
|
|
#if LEGACY_MODE
|
|
[Obsolete ("Tree inheriting Node is being phased out. Use the `Tree.RootNode' property instead")]
|
|
public Node (string caption, string element)
|
|
{
|
|
this.parentTree = (Tree) this;
|
|
this.caption = caption;
|
|
this.element = element;
|
|
parent = null;
|
|
}
|
|
#endif
|
|
|
|
public Node (Node parent, string caption, string element) : this (parent.Tree, caption, element)
|
|
{
|
|
this.parent = parent;
|
|
}
|
|
|
|
internal Node (Tree tree, string caption, string element)
|
|
{
|
|
this.parentTree = tree;
|
|
this.caption = caption;
|
|
this.element = element;
|
|
this.elementSort = parentTree.HelpSource != null && parentTree.HelpSource.SortType == SortType.Element;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a node from an on-disk representation
|
|
/// </summary>
|
|
internal Node (Node parent, int address) : this (parent.parentTree, address)
|
|
{
|
|
this.parent = parent;
|
|
}
|
|
|
|
internal Node (Tree tree, int address)
|
|
{
|
|
this.address = address;
|
|
this.parentTree = tree;
|
|
this.elementSort = parentTree.HelpSource != null && parentTree.HelpSource.SortType == SortType.Element;
|
|
if (address > 0)
|
|
LoadNode ();
|
|
}
|
|
|
|
/* This is solely used for MatchNode to check for equality */
|
|
internal Node ()
|
|
{
|
|
}
|
|
|
|
void LoadNode ()
|
|
{
|
|
parentTree.InflateNode (this);
|
|
if (parent != null)
|
|
parent.RegisterFullNode (this);
|
|
}
|
|
|
|
public void AddNode (Node n)
|
|
{
|
|
nodes.Add (n);
|
|
n.parent = this;
|
|
n.Documented = true;
|
|
RegisterFullNode (n);
|
|
}
|
|
|
|
public void DeleteNode (Node n)
|
|
{
|
|
nodes.Remove (n);
|
|
if (!string.IsNullOrEmpty (n.element))
|
|
childrenLookup.Remove (n.element);
|
|
}
|
|
|
|
// When a child node is inflated, it calls this method
|
|
// so that we can add it to our lookup for quick search
|
|
void RegisterFullNode (Node child)
|
|
{
|
|
if (childrenLookup == null)
|
|
childrenLookup = new Dictionary<string, Node> ();
|
|
if (!string.IsNullOrEmpty (child.element))
|
|
childrenLookup[child.element] = child;
|
|
}
|
|
|
|
#if LEGACY_MODE
|
|
[Obsolete ("Use ChildNodes")]
|
|
public ArrayList Nodes {
|
|
get {
|
|
if (legacyNodes == null)
|
|
legacyNodes = new ArrayList (ChildNodes as ICollection);
|
|
return legacyNodes;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
public IList<Node> ChildNodes {
|
|
get {
|
|
EnsureLoaded ();
|
|
return nodes != null ? nodes : new List<Node> ();
|
|
}
|
|
}
|
|
|
|
public string Element {
|
|
get {
|
|
EnsureLoaded ();
|
|
return element;
|
|
}
|
|
set {
|
|
element = value;
|
|
}
|
|
}
|
|
|
|
public string Caption {
|
|
get {
|
|
EnsureLoaded ();
|
|
return caption;
|
|
}
|
|
internal set {
|
|
caption = value;
|
|
}
|
|
}
|
|
|
|
public Node Parent {
|
|
get {
|
|
return parent;
|
|
}
|
|
}
|
|
|
|
public Tree Tree {
|
|
get {
|
|
return parentTree;
|
|
}
|
|
}
|
|
|
|
internal int Address {
|
|
get {
|
|
return address;
|
|
}
|
|
#if LEGACY_MODE
|
|
set {
|
|
address = value;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new node, in the locator entry point, and with
|
|
/// a user visible caption of @caption
|
|
/// </summary>
|
|
public Node CreateNode (string c_caption, string c_element)
|
|
{
|
|
EnsureNodes ();
|
|
if (string.IsNullOrEmpty (c_caption))
|
|
throw new ArgumentNullException ("c_caption");
|
|
if (string.IsNullOrEmpty (c_element))
|
|
throw new ArgumentNullException ("c_element");
|
|
|
|
Node t = new Node (this, c_caption, c_element);
|
|
nodes.Add (t);
|
|
childrenLookup[c_element] = t;
|
|
|
|
return t;
|
|
}
|
|
|
|
public Node GetOrCreateNode (string c_caption, string c_element)
|
|
{
|
|
if (nodes == null)
|
|
return CreateNode (c_caption, c_element);
|
|
if (childrenLookup.Count != nodes.Count || (nodes.Count == 0 && childrenLookup.Count != nodes.Capacity))
|
|
UpdateLookup ();
|
|
|
|
Node result;
|
|
if (!childrenLookup.TryGetValue (c_element, out result))
|
|
result = CreateNode (c_caption, c_element);
|
|
return result;
|
|
}
|
|
|
|
public void EnsureNodes ()
|
|
{
|
|
if (nodes == null) {
|
|
nodes = new List<Node> ();
|
|
childrenLookup = new Dictionary<string, Node> ();
|
|
}
|
|
}
|
|
|
|
public void EnsureLoaded ()
|
|
{
|
|
if (address < 0 && !loaded) {
|
|
LoadNode ();
|
|
loaded = true;
|
|
}
|
|
}
|
|
|
|
void UpdateLookup ()
|
|
{
|
|
foreach (var node in nodes)
|
|
childrenLookup[node.Element] = node;
|
|
}
|
|
|
|
public bool IsLeaf {
|
|
get {
|
|
return nodes == null || nodes.Count == 0;
|
|
}
|
|
}
|
|
|
|
void EncodeInt (BinaryWriter writer, int value)
|
|
{
|
|
do {
|
|
int high = (value >> 7) & 0x01ffffff;
|
|
byte b = (byte)(value & 0x7f);
|
|
|
|
if (high != 0) {
|
|
b = (byte)(b | 0x80);
|
|
}
|
|
|
|
writer.Write(b);
|
|
value = high;
|
|
} while(value != 0);
|
|
}
|
|
|
|
int DecodeInt (BinaryReader reader)
|
|
{
|
|
int ret = 0;
|
|
int shift = 0;
|
|
byte b;
|
|
|
|
do {
|
|
b = reader.ReadByte();
|
|
|
|
ret = ret | ((b & 0x7f) << shift);
|
|
shift += 7;
|
|
} while ((b & 0x80) == 0x80);
|
|
|
|
return ret;
|
|
}
|
|
|
|
internal void Deserialize (BinaryReader reader)
|
|
{
|
|
int count = DecodeInt (reader);
|
|
element = reader.ReadString ();
|
|
caption = reader.ReadString ();
|
|
|
|
if (count == 0)
|
|
return;
|
|
|
|
nodes = new List<Node> (count);
|
|
for (int i = 0; i < count; i++) {
|
|
int child_address = DecodeInt (reader);
|
|
|
|
Node t = new Node (this, -child_address);
|
|
nodes.Add (t);
|
|
}
|
|
|
|
if (parentTree.ForceResort)
|
|
nodes.Sort ();
|
|
}
|
|
|
|
internal void Serialize (FileStream output, BinaryWriter writer)
|
|
{
|
|
if (nodes != null)
|
|
foreach (Node child in nodes)
|
|
child.Serialize (output, writer);
|
|
|
|
address = (int) output.Position;
|
|
EncodeInt (writer, nodes == null ? 0 : (int) nodes.Count);
|
|
writer.Write (element);
|
|
writer.Write (caption);
|
|
|
|
if (nodes != null)
|
|
foreach (Node child in nodes)
|
|
EncodeInt (writer, child.address);
|
|
}
|
|
|
|
public void Sort ()
|
|
{
|
|
if (nodes != null)
|
|
nodes.Sort ();
|
|
}
|
|
|
|
internal string GetInternalUrl ()
|
|
{
|
|
EnsureLoaded ();
|
|
if (element.IndexOf (":") != -1 || parent == null)
|
|
return element;
|
|
|
|
var parentUrl = parent.GetInternalUrl ();
|
|
return parentUrl.EndsWith ("/") ? parentUrl + element : parentUrl + "/" + element;
|
|
}
|
|
|
|
public string PublicUrl {
|
|
get {
|
|
if (pubUrl != null)
|
|
return pubUrl;
|
|
return pubUrl = parentTree.HelpSource != null ? parentTree.HelpSource.GetPublicUrl (this) : GetInternalUrl ();
|
|
}
|
|
}
|
|
|
|
int IComparable.CompareTo (object obj)
|
|
{
|
|
Node other = obj as Node;
|
|
if (other == null)
|
|
return -1;
|
|
return CompareToInternal (other);
|
|
}
|
|
|
|
int IComparable<Node>.CompareTo (Node obj)
|
|
{
|
|
return CompareToInternal (obj);
|
|
}
|
|
|
|
int CompareToInternal (Node other)
|
|
{
|
|
EnsureLoaded ();
|
|
other.EnsureLoaded ();
|
|
|
|
var cap1 = elementSort ? element : caption;
|
|
var cap2 = elementSort ? other.element : other.caption;
|
|
|
|
/* Some node (notably from ecmaspec) have number prepended to them
|
|
* which we need to sort better by padding them to the same number
|
|
* of digits
|
|
*/
|
|
if (char.IsDigit (cap1[0]) && char.IsDigit (cap2[0])) {
|
|
int c1 = cap1.TakeWhile (char.IsDigit).Count ();
|
|
int c2 = cap2.TakeWhile (char.IsDigit).Count ();
|
|
|
|
if (c1 != c2) {
|
|
cap1 = cap1.PadLeft (cap1.Length + Math.Max (0, c2 - c1), '0');
|
|
cap2 = cap2.PadLeft (cap2.Length + Math.Max (0, c1 - c2), '0');
|
|
}
|
|
}
|
|
|
|
return string.Compare (cap1, cap2, StringComparison.Ordinal);
|
|
}
|
|
}
|
|
|
|
internal static class IListExtensions
|
|
{
|
|
// TODO: if the backing store ever change from List<T>, we need to tune these methods to have a fallback mechanism
|
|
public static int BinarySearch<T> (this IList<T> ilist, T item)
|
|
{
|
|
var list = ilist as List<T>;
|
|
if (list == null)
|
|
throw new NotSupportedException ();
|
|
return list.BinarySearch (item);
|
|
}
|
|
|
|
public static int BinarySearch<T> (this IList<T> ilist, T item, IComparer<T> comparer)
|
|
{
|
|
var list = ilist as List<T>;
|
|
if (list == null)
|
|
throw new NotSupportedException ();
|
|
return list.BinarySearch (item, comparer);
|
|
}
|
|
}
|
|
}
|