// HtmlAgilityPack V1.0 - Simon Mourier <simon underscore mourier at hotmail dot com>
using System;
using System.Collections;
using System.IO;
using System.Xml;
using System.Xml.XPath;

namespace HtmlAgilityPack
{
	/// <summary>
	/// Represents an HTML node.
	/// </summary>
	public class HtmlNode: IXPathNavigable
	{
		/// <summary>
		/// Gets the name of a comment node. It is actually defined as '#comment'.
		/// </summary>
		public static readonly string HtmlNodeTypeNameComment = "#comment";

		/// <summary>
		/// Gets the name of the document node. It is actually defined as '#document'.
		/// </summary>
		public static readonly string HtmlNodeTypeNameDocument = "#document";

		/// <summary>
		/// Gets the name of a text node. It is actually defined as '#text'.
		/// </summary>
		public static readonly string HtmlNodeTypeNameText = "#text";

		/// <summary>
		/// Gets a collection of flags that define specific behaviors for specific element nodes.
		/// The table contains a DictionaryEntry list with the lowercase tag name as the Key, and a combination of HtmlElementFlags as the Value.
		/// </summary>
		public static Hashtable ElementsFlags;

		internal HtmlNodeType _nodetype;
		internal HtmlNode _nextnode;
		internal HtmlNode _prevnode;
		internal HtmlNode _parentnode;
		internal HtmlDocument _ownerdocument;
		internal HtmlNodeCollection _childnodes;
		internal HtmlAttributeCollection _attributes;
		internal int _line = 0;
		internal int _lineposition = 0;
		internal int _streamposition = 0;
		internal int _innerstartindex = 0;
		internal int _innerlength = 0;
		internal int _outerstartindex = 0;
		internal int _outerlength = 0;
		internal int _namestartindex = 0;
		internal int _namelength = 0;
		internal bool _starttag = false;
		internal string _name;
		internal HtmlNode _prevwithsamename = null;
		internal HtmlNode _endnode;

		internal bool _innerchanged = false;
		internal bool _outerchanged = false;
		internal string _innerhtml;
		internal string _outerhtml;

		static HtmlNode()
		{
			// tags whose content may be anything
			ElementsFlags = new Hashtable();
			ElementsFlags.Add("script", HtmlElementFlag.CData);
			ElementsFlags.Add("style", HtmlElementFlag.CData);
			ElementsFlags.Add("noxhtml", HtmlElementFlag.CData);

			// tags that can not contain other tags
			ElementsFlags.Add("base", HtmlElementFlag.Empty);
			ElementsFlags.Add("link", HtmlElementFlag.Empty);
			ElementsFlags.Add("meta", HtmlElementFlag.Empty);
			ElementsFlags.Add("isindex", HtmlElementFlag.Empty);
			ElementsFlags.Add("hr", HtmlElementFlag.Empty);
			ElementsFlags.Add("col", HtmlElementFlag.Empty);
			ElementsFlags.Add("img", HtmlElementFlag.Empty);
			ElementsFlags.Add("param", HtmlElementFlag.Empty);
			ElementsFlags.Add("embed", HtmlElementFlag.Empty);
			ElementsFlags.Add("frame", HtmlElementFlag.Empty);
			ElementsFlags.Add("wbr", HtmlElementFlag.Empty);
			ElementsFlags.Add("bgsound", HtmlElementFlag.Empty);
			ElementsFlags.Add("spacer", HtmlElementFlag.Empty);
			ElementsFlags.Add("keygen", HtmlElementFlag.Empty);
			ElementsFlags.Add("area", HtmlElementFlag.Empty);
			ElementsFlags.Add("input", HtmlElementFlag.Empty);
			ElementsFlags.Add("basefont", HtmlElementFlag.Empty);

			ElementsFlags.Add("form", HtmlElementFlag.CanOverlap | HtmlElementFlag.Empty);

			// they sometimes contain, and sometimes they don 't...
			ElementsFlags.Add("option", HtmlElementFlag.Empty);

			// tag whose closing tag is equivalent to open tag:
			// <p>bla</p>bla will be transformed into <p>bla</p>bla
			// <p>bla<p>bla will be transformed into <p>bla<p>bla and not <p>bla></p><p>bla</p> or <p>bla<p>bla</p></p>
			//<br> see above
			ElementsFlags.Add("br", HtmlElementFlag.Empty | HtmlElementFlag.Closed);
			ElementsFlags.Add("p", HtmlElementFlag.Empty | HtmlElementFlag.Closed);
		}

		/// <summary>
		/// Determines if an element node is closed.
		/// </summary>
		/// <param name="name">The name of the element node to check. May not be null.</param>
		/// <returns>true if the name is the name of a closed element node, false otherwise.</returns>
		public static bool IsClosedElement(string name)
		{
			if (name == null)
			{
				throw new ArgumentNullException("name");
			}

			object flag = ElementsFlags[name.ToLower()];
			if (flag == null)
			{
				return false;
			}
			return (((HtmlElementFlag)flag)&HtmlElementFlag.Closed) != 0;
		}

		/// <summary>
		/// Determines if an element node can be kept overlapped.
		/// </summary>
		/// <param name="name">The name of the element node to check. May not be null.</param>
		/// <returns>true if the name is the name of an element node that can be kept overlapped, false otherwise.</returns>
		public static bool CanOverlapElement(string name)
		{
			if (name == null)
			{
				throw new ArgumentNullException("name");
			}

			object flag = ElementsFlags[name.ToLower()];
			if (flag == null)
			{
				return false;
			}
			return (((HtmlElementFlag)flag)&HtmlElementFlag.CanOverlap) != 0;
		}

		/// <summary>
		/// Determines if a text corresponds to the closing tag of an node that can be kept overlapped.
		/// </summary>
		/// <param name="text">The text to check. May not be null.</param>
		/// <returns>true or false.</returns>
		public static bool IsOverlappedClosingElement(string text)
		{
			if (text == null)
			{
				throw new ArgumentNullException("text");
			}
			// min is </x>: 4
			if (text.Length <= 4)
				return false;

			if ((text[0] != '<') ||
				(text[text.Length - 1] != '>') ||
				(text[1] != '/'))
				return false;

			string name = text.Substring(2, text.Length - 3);
			return CanOverlapElement(name);
		}

		/// <summary>
		/// Determines if an element node is a CDATA element node.
		/// </summary>
		/// <param name="name">The name of the element node to check. May not be null.</param>
		/// <returns>true if the name is the name of a CDATA element node, false otherwise.</returns>
		public static bool IsCDataElement(string name)
		{
			if (name == null)
			{
				throw new ArgumentNullException("name");
			}

			object flag = ElementsFlags[name.ToLower()];
			if (flag == null)
			{
				return false;
			}
			return (((HtmlElementFlag)flag)&HtmlElementFlag.CData) != 0;
		}

		/// <summary>
		/// Determines if an element node is defined as empty.
		/// </summary>
		/// <param name="name">The name of the element node to check. May not be null.</param>
		/// <returns>true if the name is the name of an empty element node, false otherwise.</returns>
		public static bool IsEmptyElement(string name)
		{
			if (name == null)
			{
				throw new ArgumentNullException("name");
			}

			if (name.Length == 0)
			{
				return true;
			}

			// <!DOCTYPE ...
			if ('!' == name[0])
			{
				return true;
			}
			
			// <?xml ...
			if ('?' == name[0])
			{
				return true;
			}
			
			object flag = ElementsFlags[name.ToLower()];
			if (flag == null)
			{
				return false;
			}
			return (((HtmlElementFlag)flag)&HtmlElementFlag.Empty) != 0;
		}

		/// <summary>
		/// Creates an HTML node from a string representing literal HTML.
		/// </summary>
		/// <param name="html">The HTML text.</param>
		/// <returns>The newly created node instance.</returns>
		public static HtmlNode CreateNode(string html)
		{
			// REVIEW: this is *not* optimum...
			HtmlDocument doc = new HtmlDocument();
			doc.LoadHtml(html);
			return doc.DocumentNode.FirstChild;
		}

		/// <summary>
		/// Creates a duplicate of the node and the subtree under it.
		/// </summary>
		/// <param name="node">The node to duplicate. May not be null.</param>
		public void CopyFrom(HtmlNode node)
		{
			CopyFrom(node, true);
		}

		/// <summary>
		/// Creates a duplicate of the node.
		/// </summary>
		/// <param name="node">The node to duplicate. May not be null.</param>
		/// <param name="deep">true to recursively clone the subtree under the specified node, false to clone only the node itself.</param>
		public void CopyFrom(HtmlNode node, bool deep)
		{
			if (node == null)
			{
				throw new ArgumentNullException("node");
			}

			Attributes.RemoveAll();
			if (node.HasAttributes)
			{
				foreach(HtmlAttribute att in node.Attributes)
				{
					SetAttributeValue(att.Name, att.Value);
				}
			}

			if (!deep)
			{
				RemoveAllChildren();
				if (node.HasChildNodes)
				{
					foreach(HtmlNode child in node.ChildNodes)
					{
						AppendChild(child.CloneNode(true));
					}
				}
			}
		}

		internal HtmlNode(HtmlNodeType type, HtmlDocument ownerdocument, int index)
		{
			_nodetype = type;
			_ownerdocument = ownerdocument;
			_outerstartindex = index;

			switch(type)
			{
				case HtmlNodeType.Comment:
					_name = HtmlNodeTypeNameComment;
					_endnode = this;
					break;

				case HtmlNodeType.Document:
					_name = HtmlNodeTypeNameDocument;
					_endnode = this;
					break;

				case HtmlNodeType.Text:
					_name = HtmlNodeTypeNameText;
					_endnode = this;
					break;
			}

			if (_ownerdocument._openednodes != null)
			{
				if (!Closed)
				{
					// we use the index as the key

					// -1 means the node comes from public
					if (-1 != index)
					{
						_ownerdocument._openednodes.Add(index, this);
					}
				}
			}
			
			if ((-1 == index) && (type != HtmlNodeType.Comment) && (type != HtmlNodeType.Text))
			{
				// innerhtml and outerhtml must be calculated
				_outerchanged = true;
				_innerchanged = true;
			}
		}

		internal void CloseNode(HtmlNode endnode)
		{
			if (!_ownerdocument.OptionAutoCloseOnEnd)
			{
				// close all children
				if (_childnodes != null)
				{
					foreach(HtmlNode child in _childnodes)
					{
						if (child.Closed)
							continue;

						// create a fake closer node
						HtmlNode close = new HtmlNode(NodeType, _ownerdocument, -1);
						close._endnode = close;
						child.CloseNode(close);
					}
				}
			}

			if (!Closed)
			{
				_endnode = endnode;

				if (_ownerdocument._openednodes != null)
				{
					_ownerdocument._openednodes.Remove(_outerstartindex);
				}

				HtmlNode self = _ownerdocument._lastnodes[Name] as HtmlNode;
				if (self == this)
				{
					_ownerdocument._lastnodes.Remove(Name);
					_ownerdocument.UpdateLastParentNode();
				}

				if (endnode == this)
					return;

				// create an inner section
				_innerstartindex = _outerstartindex + _outerlength;
				_innerlength = endnode._outerstartindex - _innerstartindex;

				// update full length
				_outerlength = (endnode._outerstartindex + endnode._outerlength) - _outerstartindex;
			}
		}

		internal HtmlNode EndNode
		{
			get
			{
				return _endnode;
			}
		}

		internal string GetId()
		{
			HtmlAttribute att = Attributes["id"];
			if (att == null)
			{
				return null;
			}
			return att.Value;
		}

		internal void SetId(string id)
		{
			HtmlAttribute att = Attributes["id"];
			if (att == null)
			{
				att = _ownerdocument.CreateAttribute("id");
			}
			att.Value = id;
			_ownerdocument.SetIdForNode(this, att.Value);
			_outerchanged = true;
		}

		/// <summary>
		/// Creates a new XPathNavigator object for navigating this HTML node.
		/// </summary>
		/// <returns>An XPathNavigator object. The XPathNavigator is positioned on the node from which the method was called. It is not positioned on the root of the document.</returns>
		public XPathNavigator CreateNavigator()
		{
			return new HtmlNodeNavigator(_ownerdocument, this);
		}

		/// <summary>
		/// Selects the first XmlNode that matches the XPath expression.
		/// </summary>
		/// <param name="xpath">The XPath expression. May not be null.</param>
		/// <returns>The first HtmlNode that matches the XPath query or a null reference if no matching node was found.</returns>
		public HtmlNode SelectSingleNode(string xpath)
		{
			if (xpath == null)
			{
				throw new ArgumentNullException("xpath");
			}

			HtmlNodeNavigator nav = new HtmlNodeNavigator(_ownerdocument, this);
			XPathNodeIterator it = nav.Select(xpath);
			if (!it.MoveNext())
			{
				return null;
			}

			HtmlNodeNavigator node = (HtmlNodeNavigator)it.Current;
			return node.CurrentNode;
		}

		/// <summary>
		/// Selects a list of nodes matching the XPath expression.
		/// </summary>
		/// <param name="xpath">The XPath expression.</param>
		/// <returns>An HtmlNodeCollection containing a collection of nodes matching the XPath query, or null if no node matched the XPath expression.</returns>
		public HtmlNodeCollection SelectNodes(string xpath)
		{
			HtmlNodeCollection list = new HtmlNodeCollection(null);

			HtmlNodeNavigator nav = new HtmlNodeNavigator(_ownerdocument, this);
			XPathNodeIterator it = nav.Select(xpath);
			while (it.MoveNext())
			{
				HtmlNodeNavigator n = (HtmlNodeNavigator)it.Current;
				list.Add(n.CurrentNode);
			}
			if (list.Count == 0)
			{
				return null;
			}
			return list;
		}

		/// <summary>
		/// Gets or sets the value of the 'id' HTML attribute. The document must have been parsed using the OptionUseIdAttribute set to true.
		/// </summary>
		public string Id
		{
			get
			{
				if (_ownerdocument._nodesid == null)
				{
					throw new Exception(HtmlDocument.HtmlExceptionUseIdAttributeFalse);
				}
				return GetId();
			}
			set
			{
				if (_ownerdocument._nodesid == null)
				{
					throw new Exception(HtmlDocument.HtmlExceptionUseIdAttributeFalse);
				}
				
				if (value == null)
				{
					throw new ArgumentNullException("value");
				}
				SetId(value);
			}
		}

		/// <summary>
		/// Gets the line number of this node in the document.
		/// </summary>
		public int Line
		{
			get
			{
				return _line;
			}
		}

		/// <summary>
		/// Gets the column number of this node in the document.
		/// </summary>
		public int LinePosition
		{
			get
			{
				return _lineposition;
			}
		}

		/// <summary>
		/// Gets the stream position of this node in the document, relative to the start of the document.
		/// </summary>
		public int StreamPosition
		{
			get
			{
				return _streamposition;
			}
		}

		/// <summary>
		/// Gets a value indicating if this node has been closed or not.
		/// </summary>
		public bool Closed
		{
			get
			{
				return (_endnode != null);
			}
		}

		/// <summary>
		/// Gets or sets this node's name.
		/// </summary>
		public string Name
		{
			get
			{
				if (_name == null)
				{
					_name = _ownerdocument._text.Substring(_namestartindex, _namelength).ToLower();
				}
				return _name;
			}
			set
			{
				_name = value;
			}
		}

		/// <summary>
		/// Gets or Sets the text between the start and end tags of the object.
		/// </summary>
		public virtual string InnerText
		{
			get
			{
				if (_nodetype == HtmlNodeType.Text)
				{
					return ((HtmlTextNode)this).Text;
				}

				if (_nodetype == HtmlNodeType.Comment)
				{
					return ((HtmlCommentNode)this).Comment;
				}

				// note: right now, this method is *slow*, because we recompute everything.
				// it could be optimised like innerhtml
				if (!HasChildNodes)
				{
					return string.Empty;
				}

				string s = null;
				foreach(HtmlNode node in ChildNodes)
				{
					s += node.InnerText;
				}
				return s;
			}
		}

		/// <summary>
		/// Gets or Sets the HTML between the start and end tags of the object.
		/// </summary>
		public virtual string InnerHtml
		{
			get
			{
				if (_innerchanged)
				{
					_innerhtml = WriteContentTo();
					_innerchanged = false;
					return _innerhtml;
				}
				if (_innerhtml != null)
				{
					return _innerhtml;
				}

				if (_innerstartindex < 0)
				{
					return string.Empty;
				}

				return _ownerdocument._text.Substring(_innerstartindex, _innerlength);
			}
			set
			{
				HtmlDocument doc = new HtmlDocument();
				doc.LoadHtml(value);

				RemoveAllChildren();
				AppendChildren(doc.DocumentNode.ChildNodes);
			}
		}

		/// <summary>
		/// Gets or Sets the object and its content in HTML.
		/// </summary>
		public virtual string OuterHtml
		{
			get
			{
				if (_outerchanged)
				{
					_outerhtml = WriteTo();
					_outerchanged = false;
					return _outerhtml;
				}

				if (_outerhtml != null)
				{
					return _outerhtml;
				}

				if (_outerstartindex < 0)
				{
					return string.Empty;
				}

				return _ownerdocument._text.Substring(_outerstartindex, _outerlength);
			}
		}

		/// <summary>
		/// Creates a duplicate of the node
		/// </summary>
		/// <returns></returns>
		public HtmlNode Clone()
		{
			return CloneNode(true);
		}

		/// <summary>
		/// Creates a duplicate of the node and changes its name at the same time.
		/// </summary>
		/// <param name="newName">The new name of the cloned node. May not be null.</param>
		/// <returns>The cloned node.</returns>
		public HtmlNode CloneNode(string newName)
		{
			return CloneNode(newName, true);
		}

		/// <summary>
		/// Creates a duplicate of the node and changes its name at the same time.
		/// </summary>
		/// <param name="newName">The new name of the cloned node. May not be null.</param>
		/// <param name="deep">true to recursively clone the subtree under the specified node; false to clone only the node itself.</param>
		/// <returns>The cloned node.</returns>
		public HtmlNode CloneNode(string newName, bool deep)
		{
			if (newName == null)
			{
				throw new ArgumentNullException("newName");
			}

			HtmlNode node = CloneNode(deep);
			node._name = newName;
			return node;
		}

		/// <summary>
		/// Creates a duplicate of the node.
		/// </summary>
		/// <param name="deep">true to recursively clone the subtree under the specified node; false to clone only the node itself.</param>
		/// <returns>The cloned node.</returns>
		public HtmlNode CloneNode(bool deep)
		{
			HtmlNode node = _ownerdocument.CreateNode(_nodetype);
			node._name = Name;

			switch(_nodetype)
			{
				case HtmlNodeType.Comment:
					((HtmlCommentNode)node).Comment = ((HtmlCommentNode)this).Comment;
					return node;
		
				case HtmlNodeType.Text:
					((HtmlTextNode)node).Text = ((HtmlTextNode)this).Text;
					return node;
			}

			// attributes
			if (HasAttributes)
			{
				foreach(HtmlAttribute att in _attributes)
				{
					HtmlAttribute newatt = att.Clone();
					node.Attributes.Append(newatt);
				}
			}

			// closing attributes
			if (HasClosingAttributes)
			{
				node._endnode = _endnode.CloneNode(false);
				foreach(HtmlAttribute att in _endnode._attributes)
				{
					HtmlAttribute newatt = att.Clone();
					node._endnode._attributes.Append(newatt);
				}
			}
			if (!deep)
			{
				return node;
			}

			if (!HasChildNodes)
			{
				return node;
			}

			// child nodes
			foreach(HtmlNode child in _childnodes)
			{
				HtmlNode newchild = child.Clone();
				node.AppendChild(newchild);
			}
			return node;
		}

		/// <summary>
		/// Gets the HTML node immediately following this element.
		/// </summary>
		public HtmlNode NextSibling
		{
			get
			{
				return _nextnode;
			}
		}

		/// <summary>
		/// Gets the node immediately preceding this node.
		/// </summary>
		public HtmlNode PreviousSibling
		{
			get
			{
				return _prevnode;
			}
		}

		/// <summary>
		/// Removes all the children and/or attributes of the current node.
		/// </summary>
		public void RemoveAll()
		{
			RemoveAllChildren();

			if (HasAttributes)
			{
				_attributes.Clear();
			}

			if ((_endnode != null) && (_endnode != this))
			{
				if (_endnode._attributes != null)
				{
					_endnode._attributes.Clear();
				}
			}
			_outerchanged = true;
			_innerchanged = true;
		}

		/// <summary>
		/// Removes all the children of the current node.
		/// </summary>
		public void RemoveAllChildren()
		{
			if (!HasChildNodes)
			{
				return;
			}

			if (_ownerdocument.OptionUseIdAttribute)
			{
				// remove nodes from id list
				foreach(HtmlNode node in _childnodes)
				{
					_ownerdocument.SetIdForNode(null, node.GetId());
				}
			}
			_childnodes.Clear();
			_outerchanged = true;
			_innerchanged = true;
		}

		/// <summary>
		/// Removes the specified child node.
		/// </summary>
		/// <param name="oldChild">The node being removed. May not be null.</param>
		/// <returns>The node removed.</returns>
		public HtmlNode RemoveChild(HtmlNode oldChild)
		{
			if (oldChild == null)
			{
				throw new ArgumentNullException("oldChild");
			}

			int index = -1;

			if (_childnodes != null)
			{
				index = _childnodes[oldChild];
			}

			if (index == -1)
			{
				throw new ArgumentException(HtmlDocument.HtmlExceptionRefNotChild);
			}

			_childnodes.Remove(index);

			_ownerdocument.SetIdForNode(null, oldChild.GetId());
			_outerchanged = true;
			_innerchanged = true;
			return oldChild;
		}

		/// <summary>
		/// Removes the specified child node.
		/// </summary>
		/// <param name="oldChild">The node being removed. May not be null.</param>
		/// <param name="keepGrandChildren">true to keep grand children of the node, false otherwise.</param>
		/// <returns>The node removed.</returns>
		public HtmlNode RemoveChild(HtmlNode oldChild, bool keepGrandChildren)
		{
			if (oldChild == null)
			{
				throw new ArgumentNullException("oldChild");
			}

			if ((oldChild._childnodes != null) && keepGrandChildren)
			{
				// get prev sibling
				HtmlNode prev = oldChild.PreviousSibling;

				// reroute grand children to ourselves
				foreach(HtmlNode grandchild in oldChild._childnodes)
				{
					InsertAfter(grandchild, prev);
				}
			}
			RemoveChild(oldChild);
			_outerchanged = true;
			_innerchanged = true;
			return oldChild;
		}

		/// <summary>
		/// Replaces the child node oldChild with newChild node.
		/// </summary>
		/// <param name="newChild">The new node to put in the child list.</param>
		/// <param name="oldChild">The node being replaced in the list.</param>
		/// <returns>The node replaced.</returns>
		public HtmlNode ReplaceChild(HtmlNode newChild, HtmlNode oldChild)
		{
			if (newChild == null)
			{
				return RemoveChild(oldChild);
			}

			if (oldChild == null)
			{
				return AppendChild(newChild);
			}

			int index = -1;

			if (_childnodes != null)
			{
				index = _childnodes[oldChild];
			}

			if (index == -1)
			{
				throw new ArgumentException(HtmlDocument.HtmlExceptionRefNotChild);
			}

			_childnodes.Replace(index, newChild);

			_ownerdocument.SetIdForNode(null, oldChild.GetId());
			_ownerdocument.SetIdForNode(newChild, newChild.GetId());
			_outerchanged = true;
			_innerchanged = true;
			return newChild;
		}

		/// <summary>
		/// Inserts the specified node immediately before the specified reference node.
		/// </summary>
		/// <param name="newChild">The node to insert. May not be null.</param>
		/// <param name="refChild">The node that is the reference node. The newChild is placed before this node.</param>
		/// <returns>The node being inserted.</returns>
		public HtmlNode InsertBefore(HtmlNode newChild, HtmlNode refChild)
		{
			if (newChild == null)
			{
				throw new ArgumentNullException("newChild");
			}

			if (refChild == null)
			{
				return AppendChild(newChild);
			}

			if (newChild == refChild)
			{
				return newChild;
			}

			int index = -1;

			if (_childnodes != null)
			{
				index = _childnodes[refChild];
			}

			if (index == -1)
			{
				throw new ArgumentException(HtmlDocument.HtmlExceptionRefNotChild);
			}

			_childnodes.Insert(index, newChild);

			_ownerdocument.SetIdForNode(newChild, newChild.GetId());
			_outerchanged = true;
			_innerchanged = true;
			return newChild;
		}

		/// <summary>
		/// Inserts the specified node immediately after the specified reference node.
		/// </summary>
		/// <param name="newChild">The node to insert. May not be null.</param>
		/// <param name="refChild">The node that is the reference node. The newNode is placed after the refNode.</param>
		/// <returns>The node being inserted.</returns>
		public HtmlNode InsertAfter(HtmlNode newChild, HtmlNode refChild)
		{
			if (newChild == null)
			{
				throw new ArgumentNullException("newChild");
			}

			if (refChild == null)
			{
				return PrependChild(newChild);
			}

			if (newChild == refChild)
			{
				return newChild;
			}

			int index = -1;

			if (_childnodes != null)
			{
				index = _childnodes[refChild];
			}
			if (index == -1)
			{
				throw new ArgumentException(HtmlDocument.HtmlExceptionRefNotChild);
			}

			_childnodes.Insert(index + 1, newChild);

			_ownerdocument.SetIdForNode(newChild, newChild.GetId());
			_outerchanged = true;
			_innerchanged = true;
			return newChild;
		}

		/// <summary>
		/// Gets the first child of the node.
		/// </summary>
		public HtmlNode FirstChild
		{
			get
			{
				if (!HasChildNodes)
				{
					return null;
				}
				return _childnodes[0];
			}
		}

		/// <summary>
		/// Gets the last child of the node.
		/// </summary>
		public HtmlNode LastChild
		{
			get
			{
				if (!HasChildNodes)
				{
					return null;
				}
				return _childnodes[_childnodes.Count-1];
			}
		}

		/// <summary>
		/// Gets the type of this node.
		/// </summary>
		public HtmlNodeType NodeType
		{
			get
			{
				return _nodetype;
			}
		}

		/// <summary>
		/// Gets the parent of this node (for nodes that can have parents).
		/// </summary>
		public HtmlNode ParentNode
		{
			get
			{
				return _parentnode;
			}
		}

		/// <summary>
		/// Gets the HtmlDocument to which this node belongs.
		/// </summary>
		public HtmlDocument OwnerDocument
		{
			get
			{
				return _ownerdocument;
			}
		}

		/// <summary>
		/// Gets all the children of the node.
		/// </summary>
		public HtmlNodeCollection ChildNodes
		{
			get
			{
				if (_childnodes == null)
				{
					_childnodes = new HtmlNodeCollection(this);
				}
				return _childnodes;
			}
		}

		/// <summary>
		/// Adds the specified node to the beginning of the list of children of this node.
		/// </summary>
		/// <param name="newChild">The node to add. May not be null.</param>
		/// <returns>The node added.</returns>
		public HtmlNode PrependChild(HtmlNode newChild)
		{
			if (newChild == null)
			{
				throw new ArgumentNullException("newChild");
			}
			ChildNodes.Prepend(newChild);
			_ownerdocument.SetIdForNode(newChild, newChild.GetId());
			_outerchanged = true;
			_innerchanged = true;
			return newChild;
		}

		/// <summary>
		/// Adds the specified node list to the beginning of the list of children of this node.
		/// </summary>
		/// <param name="newChildren">The node list to add. May not be null.</param>
		public void PrependChildren(HtmlNodeCollection newChildren)
		{
			if (newChildren == null)
			{
				throw new ArgumentNullException("newChildren");
			}

			foreach(HtmlNode newChild in newChildren)
			{
				PrependChild(newChild);
			}
		}

		/// <summary>
		/// Adds the specified node to the end of the list of children of this node.
		/// </summary>
		/// <param name="newChild">The node to add. May not be null.</param>
		/// <returns>The node added.</returns>
		public HtmlNode AppendChild(HtmlNode newChild)
		{
			if (newChild == null)
			{
				throw new ArgumentNullException("newChild");
			}

			ChildNodes.Append(newChild);
			_ownerdocument.SetIdForNode(newChild, newChild.GetId());
			_outerchanged = true;
			_innerchanged = true;
			return newChild;
		}

		/// <summary>
		/// Adds the specified node to the end of the list of children of this node.
		/// </summary>
		/// <param name="newChildren">The node list to add. May not be null.</param>
		public void AppendChildren(HtmlNodeCollection newChildren)
		{
			if (newChildren == null)
				throw new ArgumentNullException("newChildrend");

			foreach(HtmlNode newChild in newChildren)
			{
				AppendChild(newChild);
			}
		}

		/// <summary>
		/// Gets a value indicating whether the current node has any attributes.
		/// </summary>
		public bool HasAttributes
		{
			get
			{
				if (_attributes == null)
				{
					return false;
				}

				if (_attributes.Count <= 0)
				{
					return false;
				}
				return true;
			}
		}

		/// <summary>
		/// Gets a value indicating whether the current node has any attributes on the closing tag.
		/// </summary>
		public bool HasClosingAttributes
		{
			get
			{
				if ((_endnode == null) || (_endnode == this))
				{
					return false;
				}

				if (_endnode._attributes == null)
				{
					return false;
				}

				if (_endnode._attributes.Count <= 0)
				{
					return false;
				}
				return true;
			}
		}

		/// <summary>
		/// Gets a value indicating whether this node has any child nodes.
		/// </summary>
		public bool HasChildNodes
		{
			get
			{
				if (_childnodes == null)
				{
					return false;
				}

				if (_childnodes.Count <= 0)
				{
					return false;
				}
				return true;
			}
		}

		/// <summary>
		/// Helper method to get the value of an attribute of this node. If the attribute is not found, the default value will be returned.
		/// </summary>
		/// <param name="name">The name of the attribute to get. May not be null.</param>
		/// <param name="def">The default value to return if not found.</param>
		/// <returns>The value of the attribute if found, the default value if not found.</returns>
		public string GetAttributeValue(string name, string def)
		{
			if (name == null)
			{
				throw new ArgumentNullException("name");
			}

			if (!HasAttributes)
			{
				return def;
			}
			HtmlAttribute att = Attributes[name];
			if (att == null)
			{
				return def;
			}
			return att.Value;
		}

		/// <summary>
		/// Helper method to get the value of an attribute of this node. If the attribute is not found, the default value will be returned.
		/// </summary>
		/// <param name="name">The name of the attribute to get. May not be null.</param>
		/// <param name="def">The default value to return if not found.</param>
		/// <returns>The value of the attribute if found, the default value if not found.</returns>
		public int GetAttributeValue(string name, int def)
		{
			if (name == null)
			{
				throw new ArgumentNullException("name");
			}

			if (!HasAttributes)
			{
				return def;
			}
			HtmlAttribute att = Attributes[name];
			if (att == null)
			{
				return def;
			}
			try
			{
				return Convert.ToInt32(att.Value);
			}
			catch
			{
				return def;
			}
		}

		/// <summary>
		/// Helper method to get the value of an attribute of this node. If the attribute is not found, the default value will be returned.
		/// </summary>
		/// <param name="name">The name of the attribute to get. May not be null.</param>
		/// <param name="def">The default value to return if not found.</param>
		/// <returns>The value of the attribute if found, the default value if not found.</returns>
		public bool GetAttributeValue(string name, bool def)
		{
			if (name == null)
			{
				throw new ArgumentNullException("name");
			}

			if (!HasAttributes)
			{
				return def;
			}
			HtmlAttribute att = Attributes[name];
			if (att == null)
			{
				return def;
			}
			try
			{
				return Convert.ToBoolean(att.Value);
			}
			catch
			{
				return def;
			}
		}

		/// <summary>
		/// Helper method to set the value of an attribute of this node. If the attribute is not found, it will be created automatically.
		/// </summary>
		/// <param name="name">The name of the attribute to set. May not be null.</param>
		/// <param name="value">The value for the attribute.</param>
		/// <returns>The corresponding attribute instance.</returns>
		public HtmlAttribute SetAttributeValue(string name, string value)
		{
			if (name == null)
			{
				throw new ArgumentNullException("name");
			}
			HtmlAttribute att = Attributes[name];
			if (att == null)
			{
				return Attributes.Append(_ownerdocument.CreateAttribute(name, value));
			}
			att.Value = value;
			return att;
		}

		/// <summary>
		/// Gets the collection of HTML attributes for this node. May not be null.
		/// </summary>
		public HtmlAttributeCollection Attributes
		{
			get
			{
				if (!HasAttributes)
				{
					_attributes = new HtmlAttributeCollection(this);
				}
				return _attributes;
			}
		}

		/// <summary>
		/// Gets the collection of HTML attributes for the closing tag. May not be null.
		/// </summary>
		public HtmlAttributeCollection ClosingAttributes
		{
			get
			{
				if (!HasClosingAttributes)
				{
					return new HtmlAttributeCollection(this);
				}
				return _endnode.Attributes;
			}
		}

		internal void WriteAttribute(TextWriter outText, HtmlAttribute att)
		{
			string name;

			if (_ownerdocument.OptionOutputAsXml)
			{
				if (_ownerdocument.OptionOutputUpperCase)
				{
					name = att.XmlName.ToUpper();
				}
				else
				{
					name = att.XmlName;
				}

				outText.Write(" " + name + "=\"" + HtmlDocument.HtmlEncode(att.XmlValue) + "\"");
			}
			else
			{
				if (_ownerdocument.OptionOutputUpperCase)
				{
					name = att.Name.ToUpper();
				}
				else
				{
					name = att.Name;
				}

				if (att.Name.Length >= 4)
				{
					if ((att.Name[0] == '<') && (att.Name[1] == '%') &&
						(att.Name[att.Name.Length-1] == '>') && (att.Name[att.Name.Length-2] == '%'))
					{
						outText.Write(" " + name);
						return;
					}
				}
				if (_ownerdocument.OptionOutputOptimizeAttributeValues)
				{
					if (att.Value.IndexOfAny(new Char[]{(char)10, (char)13, (char)9, ' '}) < 0)
					{
						outText.Write(" " + name + "=" + att.Value);
					}
					else
					{
						outText.Write(" " + name + "=\"" + att.Value + "\"");
					}
				}
				else
				{
					outText.Write(" " + name + "=\"" + att.Value + "\"");
				}
			}
		}

		internal static void WriteAttributes(XmlWriter writer, HtmlNode node)
		{
			if (!node.HasAttributes)
			{
				return;
			}
			// we use _hashitems to make sure attributes are written only once
			foreach(HtmlAttribute att in node.Attributes._hashitems.Values)
			{
				writer.WriteAttributeString(att.XmlName, att.Value);
			}
		}

		internal void WriteAttributes(TextWriter outText, bool closing)
		{
			if (_ownerdocument.OptionOutputAsXml)
			{
				if (_attributes == null)
				{
					return;
				}
				// we use _hashitems to make sure attributes are written only once
				foreach(HtmlAttribute att in _attributes._hashitems.Values)
				{
					WriteAttribute(outText, att);
				}
				return;
			}

			if (!closing)
			{
				if (_attributes != null)
				{

					foreach(HtmlAttribute att in _attributes)
					{
						WriteAttribute(outText, att);
					}
				}
				if (_ownerdocument.OptionAddDebuggingAttributes)
				{
					WriteAttribute(outText, _ownerdocument.CreateAttribute("_closed", Closed.ToString()));
					WriteAttribute(outText, _ownerdocument.CreateAttribute("_children", ChildNodes.Count.ToString()));

					int i = 0;
					foreach(HtmlNode n in ChildNodes)
					{
						WriteAttribute(outText, _ownerdocument.CreateAttribute("_child_" + i,
							n.Name));
						i++;
					}
				}
			}
			else
			{
				if (_endnode == null)
				{
					return;
				}

				if (_endnode._attributes == null)
				{
					return;
				}

				if (_endnode == this)
				{
					return;
				}

				foreach(HtmlAttribute att in _endnode._attributes)
				{
					WriteAttribute(outText, att);
				}
				if (_ownerdocument.OptionAddDebuggingAttributes)
				{
					WriteAttribute(outText, _ownerdocument.CreateAttribute("_closed", Closed.ToString()));
					WriteAttribute(outText, _ownerdocument.CreateAttribute("_children", ChildNodes.Count.ToString()));
				}
			}
		}

		internal static string GetXmlComment(HtmlCommentNode comment)
		{
			string s = comment.Comment;
			return s.Substring(4, s.Length-7).Replace("--", " - -");
		}

		/// <summary>
		/// Saves the current node to the specified TextWriter.
		/// </summary>
		/// <param name="outText">The TextWriter to which you want to save.</param>
		public void WriteTo(TextWriter outText)
		{
			string html;
			switch(_nodetype)
			{
				case HtmlNodeType.Comment:
					html = ((HtmlCommentNode)this).Comment;
					if (_ownerdocument.OptionOutputAsXml)
					{
						outText.Write("<!--" + GetXmlComment((HtmlCommentNode)this) + " -->");
					}
					else
					{
						outText.Write(html);
					}
					break;

				case HtmlNodeType.Document:
					if (_ownerdocument.OptionOutputAsXml)
					{
						outText.Write("<?xml version=\"1.0\" encoding=\"" + _ownerdocument.GetOutEncoding().BodyName + "\"?>");

						// check there is a root element
						if (_ownerdocument.DocumentNode.HasChildNodes)
						{
							int rootnodes = _ownerdocument.DocumentNode._childnodes.Count;
							if (rootnodes > 0)
							{
								HtmlNode xml = _ownerdocument.GetXmlDeclaration();
								if (xml != null)
								{
									rootnodes --;
								}

								if (rootnodes > 1)
								{
									if (_ownerdocument.OptionOutputUpperCase)
									{
										outText.Write("<SPAN>");
										WriteContentTo(outText);
										outText.Write("</SPAN>");
									}
									else
									{
										outText.Write("<span>");
										WriteContentTo(outText);
										outText.Write("</span>");
									}
									break;
								}
							}
						}
					}
					WriteContentTo(outText);
					break;

				case HtmlNodeType.Text:
					html = ((HtmlTextNode)this).Text;
					if (_ownerdocument.OptionOutputAsXml)
					{
						outText.Write(HtmlDocument.HtmlEncode(html));
					}
					else
					{
						outText.Write(html);
					}
					break;

				case HtmlNodeType.Element:
					string name;
					if (_ownerdocument.OptionOutputUpperCase)
					{
						name = Name.ToUpper();
					}
					else
					{
						name = Name;
					}

					if (_ownerdocument.OptionOutputAsXml)
					{
						if (name.Length > 0)
						{
							if (name[0] == '?')
							{
								// forget this one, it's been done at the document level
								break;
							}

							if (name.Trim().Length == 0)
							{
								break;
							}
							name = HtmlDocument.GetXmlName(name);
						}
						else
						{
							break;
						}
					}

					outText.Write("<" + name);
					WriteAttributes(outText, false);

					if (!HasChildNodes)
					{
						if (HtmlNode.IsEmptyElement(Name))
						{
							if ((_ownerdocument.OptionWriteEmptyNodes) || (_ownerdocument.OptionOutputAsXml))
							{
								outText.Write(" />");
							}
							else
							{
								if (Name.Length > 0)
								{
									if (Name[0] == '?')
									{
										outText.Write("?");
									}
								}

								outText.Write(">");
							}
						}
						else
						{
							outText.Write("></" + name + ">");
						}
					}
					else
					{
						outText.Write(">");
						bool cdata = false;
						if (_ownerdocument.OptionOutputAsXml)
						{
							if (HtmlNode.IsCDataElement(Name))
							{
								// this code and the following tries to output things as nicely as possible for old browsers.
								cdata = true;
								outText.Write("\r\n//<![CDATA[\r\n");
							}
						}

						if (cdata)
						{
							if (HasChildNodes)
							{
								// child must be a text
								ChildNodes[0].WriteTo(outText);
							}
							outText.Write("\r\n//]]>//\r\n");
						}
						else
						{
							WriteContentTo(outText);
						}

						outText.Write("</" + name);
						if (!_ownerdocument.OptionOutputAsXml)
						{
							WriteAttributes(outText, true);
						}
						outText.Write(">");
					}
					break;
			}
		}

		/// <summary>
		/// Saves the current node to the specified XmlWriter.
		/// </summary>
		/// <param name="writer">The XmlWriter to which you want to save.</param>
		public void WriteTo(XmlWriter writer)
		{
			string html;
			switch(_nodetype)
			{
				case HtmlNodeType.Comment:
					writer.WriteComment(GetXmlComment((HtmlCommentNode)this));
					break;

				case HtmlNodeType.Document:
					writer.WriteProcessingInstruction("xml", "version=\"1.0\" encoding=\"" + _ownerdocument.GetOutEncoding().BodyName + "\"");
					if (HasChildNodes)
					{
						foreach(HtmlNode subnode in ChildNodes)
						{
							subnode.WriteTo(writer);
						}
					}
					break;

				case HtmlNodeType.Text:
					html = ((HtmlTextNode)this).Text;
					writer.WriteString(html);
					break;

				case HtmlNodeType.Element:
					string name;
					if (_ownerdocument.OptionOutputUpperCase)
					{
						name = Name.ToUpper();
					}
					else
					{
						name = Name;
					}
					writer.WriteStartElement(name);
					WriteAttributes(writer, this);

					if (HasChildNodes)
					{
						foreach(HtmlNode subnode in ChildNodes)
						{
							subnode.WriteTo(writer);
						}
					}
					writer.WriteEndElement();
					break;
			}
		}

		/// <summary>
		/// Saves all the children of the node to the specified TextWriter.
		/// </summary>
		/// <param name="outText">The TextWriter to which you want to save.</param>
		public void WriteContentTo(TextWriter outText)
		{
			if (_childnodes == null)
			{
				return;
			}

			foreach(HtmlNode node in _childnodes)
			{
				node.WriteTo(outText);
			}
		}

		/// <summary>
		/// Saves the current node to a string.
		/// </summary>
		/// <returns>The saved string.</returns>
		public string WriteTo()
		{
			StringWriter sw = new StringWriter();
			WriteTo(sw);
			sw.Flush();
			return sw.ToString();
		}

		/// <summary>
		/// Saves all the children of the node to a string.
		/// </summary>
		/// <returns>The saved string.</returns>
		public string WriteContentTo()
		{
			StringWriter sw = new StringWriter();
			WriteContentTo(sw);
			sw.Flush();
			return sw.ToString();
		}
	}

}
