a575963da9
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
1996 lines
66 KiB
C#
1996 lines
66 KiB
C#
// HtmlAgilityPack V1.0 - Simon Mourier <simon underscore mourier at hotmail dot com>
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Xml;
|
|
using System.Xml.XPath;
|
|
|
|
namespace HtmlAgilityPack
|
|
{
|
|
/// <summary>
|
|
/// Represents an HTML node.
|
|
/// </summary>
|
|
[DebuggerDisplay("Name: {OriginalName}}")]
|
|
public class HtmlNode : IXPathNavigable
|
|
{
|
|
#region Fields
|
|
|
|
internal HtmlAttributeCollection _attributes;
|
|
internal HtmlNodeCollection _childnodes;
|
|
internal HtmlNode _endnode;
|
|
|
|
internal bool _innerchanged;
|
|
internal string _innerhtml;
|
|
internal int _innerlength;
|
|
internal int _innerstartindex;
|
|
internal int _line;
|
|
internal int _lineposition;
|
|
private string _name;
|
|
internal int _namelength;
|
|
internal int _namestartindex;
|
|
internal HtmlNode _nextnode;
|
|
internal HtmlNodeType _nodetype;
|
|
internal bool _outerchanged;
|
|
internal string _outerhtml;
|
|
internal int _outerlength;
|
|
internal int _outerstartindex;
|
|
internal HtmlDocument _ownerdocument;
|
|
internal HtmlNode _parentnode;
|
|
internal HtmlNode _prevnode;
|
|
internal HtmlNode _prevwithsamename;
|
|
internal bool _starttag;
|
|
internal int _streamposition;
|
|
|
|
#endregion
|
|
|
|
#region Static Members
|
|
|
|
/// <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;
|
|
|
|
#endregion
|
|
|
|
#region Constructors
|
|
|
|
/// <summary>
|
|
/// Initialize HtmlNode. Builds a list of all tags that have special allowances
|
|
/// </summary>
|
|
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>
|
|
/// Initializes HtmlNode, providing type, owner and where it exists in a collection
|
|
/// </summary>
|
|
/// <param name="type"></param>
|
|
/// <param name="ownerdocument"></param>
|
|
/// <param name="index"></param>
|
|
public 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)) return;
|
|
// innerhtml and outerhtml must be calculated
|
|
_outerchanged = true;
|
|
_innerchanged = true;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
/// <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;
|
|
}
|
|
internal set { _attributes = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets all the children of the node.
|
|
/// </summary>
|
|
public HtmlNodeCollection ChildNodes
|
|
{
|
|
get
|
|
{
|
|
if (_childnodes == null)
|
|
{
|
|
_childnodes = new HtmlNodeCollection(this);
|
|
}
|
|
return _childnodes;
|
|
}
|
|
internal set { _childnodes = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating if this node has been closed or not.
|
|
/// </summary>
|
|
public bool Closed
|
|
{
|
|
get { return (_endnode != null); }
|
|
}
|
|
|
|
/// <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 HtmlNode EndNode
|
|
{
|
|
get { return _endnode; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the first child of the node.
|
|
/// </summary>
|
|
public HtmlNode FirstChild
|
|
{
|
|
get
|
|
{
|
|
if (!HasChildNodes)
|
|
{
|
|
return null;
|
|
}
|
|
return _childnodes[0];
|
|
}
|
|
}
|
|
|
|
/// <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 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>
|
|
/// 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 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 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 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 the last child of the node.
|
|
/// </summary>
|
|
public HtmlNode LastChild
|
|
{
|
|
get
|
|
{
|
|
return !HasChildNodes ? null : _childnodes[_childnodes.Count - 1];
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the line number of this node in the document.
|
|
/// </summary>
|
|
public int Line
|
|
{
|
|
get { return _line; }
|
|
internal set { _line = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the column number of this node in the document.
|
|
/// </summary>
|
|
public int LinePosition
|
|
{
|
|
get { return _lineposition; }
|
|
internal set { _lineposition = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets this node's name.
|
|
/// </summary>
|
|
public string Name
|
|
{
|
|
get
|
|
{
|
|
if (_name == null)
|
|
{
|
|
Name = _ownerdocument._text.Substring(_namestartindex, _namelength);
|
|
}
|
|
return _name != null ? _name.ToLower() : string.Empty;
|
|
}
|
|
set { _name = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the HTML node immediately following this element.
|
|
/// </summary>
|
|
public HtmlNode NextSibling
|
|
{
|
|
get { return _nextnode; }
|
|
internal set { _nextnode = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the type of this node.
|
|
/// </summary>
|
|
public HtmlNodeType NodeType
|
|
{
|
|
get { return _nodetype; }
|
|
internal set { _nodetype = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// The original unaltered name of the tag
|
|
/// </summary>
|
|
public string OriginalName
|
|
{
|
|
get { return _name; }
|
|
}
|
|
|
|
/// <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>
|
|
/// Gets the <see cref="HtmlDocument"/> to which this node belongs.
|
|
/// </summary>
|
|
public HtmlDocument OwnerDocument
|
|
{
|
|
get { return _ownerdocument; }
|
|
internal set { _ownerdocument = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the parent of this node (for nodes that can have parents).
|
|
/// </summary>
|
|
public HtmlNode ParentNode
|
|
{
|
|
get { return _parentnode; }
|
|
internal set { _parentnode = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the node immediately preceding this node.
|
|
/// </summary>
|
|
public HtmlNode PreviousSibling
|
|
{
|
|
get { return _prevnode; }
|
|
internal set { _prevnode = value; }
|
|
}
|
|
|
|
/// <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 valid XPath string that points to this node
|
|
/// </summary>
|
|
public string XPath
|
|
{
|
|
get
|
|
{
|
|
string basePath = (ParentNode == null || ParentNode.NodeType == HtmlNodeType.Document)
|
|
? "/"
|
|
: ParentNode.XPath + "/";
|
|
return basePath + GetRelativeXpath();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IXPathNavigable Members
|
|
|
|
/// <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);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Public Methods
|
|
|
|
/// <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 <c>null</c>.</param>
|
|
/// <returns>true if the name is the name of an element node that can be kept overlapped, <c>false</c> 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>
|
|
/// 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>
|
|
/// 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 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 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>
|
|
/// 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>
|
|
/// Returns a collection of all ancestor nodes of this element.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public IEnumerable<HtmlNode> Ancestors()
|
|
{
|
|
HtmlNode node = ParentNode;
|
|
while (node.ParentNode != null)
|
|
{
|
|
yield return node.ParentNode;
|
|
node = node.ParentNode;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get Ancestors with matching name
|
|
/// </summary>
|
|
/// <param name="name"></param>
|
|
/// <returns></returns>
|
|
public IEnumerable<HtmlNode> Ancestors(string name)
|
|
{
|
|
for (HtmlNode n = ParentNode; n != null; n = n.ParentNode)
|
|
if (n.Name == name)
|
|
yield return n;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a collection of all ancestor nodes of this element.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public IEnumerable<HtmlNode> AncestorsAndSelf()
|
|
{
|
|
for (HtmlNode n = this; n != null; n = n.ParentNode)
|
|
yield return n;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets all anscestor nodes and the current node
|
|
/// </summary>
|
|
/// <param name="name"></param>
|
|
/// <returns></returns>
|
|
public IEnumerable<HtmlNode> AncestorsAndSelf(string name)
|
|
{
|
|
for (HtmlNode n = this; n != null; n = n.ParentNode)
|
|
if (n.Name == name)
|
|
yield return n;
|
|
}
|
|
|
|
/// <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 all Attributes with name
|
|
/// </summary>
|
|
/// <param name="name"></param>
|
|
/// <returns></returns>
|
|
public IEnumerable<HtmlAttribute> ChildAttributes(string name)
|
|
{
|
|
return Attributes.AttributesWithName(name);
|
|
}
|
|
|
|
/// <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 <c>null</c>.</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>
|
|
/// Creates a duplicate of the node and the subtree under it.
|
|
/// </summary>
|
|
/// <param name="node">The node to duplicate. May not be <c>null</c>.</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 <c>null</c>.</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));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates an XPathNavigator using the root of this document.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public XPathNavigator CreateRootNavigator()
|
|
{
|
|
return new HtmlNodeNavigator(_ownerdocument, _ownerdocument.DocumentNode);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets all Descendant nodes for this node and each of child nodes
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public IEnumerable<HtmlNode> DescendantNodes()
|
|
{
|
|
foreach (HtmlNode node in ChildNodes)
|
|
{
|
|
yield return node;
|
|
foreach (HtmlNode descendant in node.DescendantNodes())
|
|
yield return descendant;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a collection of all descendant nodes of this element, in document order
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public IEnumerable<HtmlNode> DescendantNodesAndSelf()
|
|
{
|
|
return DescendantsAndSelf();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets all Descendant nodes in enumerated list
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public IEnumerable<HtmlNode> Descendants()
|
|
{
|
|
foreach (HtmlNode node in DescendantNodes())
|
|
{
|
|
yield return node;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get all descendant nodes with matching name
|
|
/// </summary>
|
|
/// <param name="name"></param>
|
|
/// <returns></returns>
|
|
public IEnumerable<HtmlNode> Descendants(string name)
|
|
{
|
|
foreach (HtmlNode node in Descendants())
|
|
if (node.Name == name)
|
|
yield return node;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a collection of all descendant nodes of this element, in document order
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public IEnumerable<HtmlNode> DescendantsAndSelf()
|
|
{
|
|
yield return this;
|
|
foreach (HtmlNode n in DescendantNodes())
|
|
{
|
|
HtmlNode el = n;
|
|
if (el != null)
|
|
yield return el;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets all descendant nodes including this node
|
|
/// </summary>
|
|
/// <param name="name"></param>
|
|
/// <returns></returns>
|
|
public IEnumerable<HtmlNode> DescendantsAndSelf(string name)
|
|
{
|
|
yield return this;
|
|
foreach (HtmlNode node in Descendants())
|
|
if (node.Name == name)
|
|
yield return node;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets first generation child node matching name
|
|
/// </summary>
|
|
/// <param name="name"></param>
|
|
/// <returns></returns>
|
|
public HtmlNode Element(string name)
|
|
{
|
|
foreach (HtmlNode node in ChildNodes)
|
|
if (node.Name == name)
|
|
return node;
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets matching first generation child nodes matching name
|
|
/// </summary>
|
|
/// <param name="name"></param>
|
|
/// <returns></returns>
|
|
public IEnumerable<HtmlNode> Elements(string name)
|
|
{
|
|
foreach (HtmlNode node in ChildNodes)
|
|
if (node.Name == name)
|
|
yield return node;
|
|
}
|
|
|
|
/// <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 <c>null</c>.</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 <c>null</c>.</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 <c>null</c>.</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>
|
|
/// Inserts the specified node immediately after the specified reference node.
|
|
/// </summary>
|
|
/// <param name="newChild">The node to insert. May not be <c>null</c>.</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);
|
|
}
|
|
|
|
if (_childnodes != null) _childnodes.Insert(index + 1, newChild);
|
|
|
|
_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 <c>null</c>.</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);
|
|
}
|
|
|
|
if (_childnodes != null) _childnodes.Insert(index, newChild);
|
|
|
|
_ownerdocument.SetIdForNode(newChild, newChild.GetId());
|
|
_outerchanged = true;
|
|
_innerchanged = true;
|
|
return newChild;
|
|
}
|
|
|
|
/// <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 <c>null</c>.</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 <c>null</c>.</param>
|
|
public void PrependChildren(HtmlNodeCollection newChildren)
|
|
{
|
|
if (newChildren == null)
|
|
{
|
|
throw new ArgumentNullException("newChildren");
|
|
}
|
|
|
|
foreach (HtmlNode newChild in newChildren)
|
|
{
|
|
PrependChild(newChild);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes node from parent collection
|
|
/// </summary>
|
|
public void Remove()
|
|
{
|
|
if (ParentNode != null)
|
|
ParentNode.ChildNodes.Remove(this);
|
|
}
|
|
|
|
/// <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 <c>null</c>.</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);
|
|
}
|
|
|
|
if (_childnodes != null)
|
|
_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 <c>null</c>.</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);
|
|
}
|
|
|
|
if (_childnodes != null) _childnodes.Replace(index, newChild);
|
|
|
|
_ownerdocument.SetIdForNode(null, oldChild.GetId());
|
|
_ownerdocument.SetIdForNode(newChild, newChild.GetId());
|
|
_outerchanged = true;
|
|
_innerchanged = true;
|
|
return newChild;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Selects a list of nodes matching the <see cref="XPath"/> expression.
|
|
/// </summary>
|
|
/// <param name="xpath">The XPath expression.</param>
|
|
/// <returns>An <see cref="HtmlNodeCollection"/> containing a collection of nodes matching the <see cref="XPath"/> query, or <c>null</c> 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>
|
|
/// Selects the first XmlNode that matches the XPath expression.
|
|
/// </summary>
|
|
/// <param name="xpath">The XPath expression. May not be null.</param>
|
|
/// <returns>The first <see cref="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>
|
|
/// 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>
|
|
/// 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 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();
|
|
}
|
|
|
|
/// <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.OptionOutputOriginalCase)
|
|
name = OriginalName;
|
|
|
|
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 (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 (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)
|
|
{
|
|
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:
|
|
string html = ((HtmlTextNode) this).Text;
|
|
writer.WriteString(html);
|
|
break;
|
|
|
|
case HtmlNodeType.Element:
|
|
string name = _ownerdocument.OptionOutputUpperCase ? Name.ToUpper() : Name;
|
|
|
|
if (_ownerdocument.OptionOutputOriginalCase)
|
|
name = OriginalName;
|
|
|
|
writer.WriteStartElement(name);
|
|
WriteAttributes(writer, this);
|
|
|
|
if (HasChildNodes)
|
|
{
|
|
foreach (HtmlNode subnode in ChildNodes)
|
|
{
|
|
subnode.WriteTo(writer);
|
|
}
|
|
}
|
|
writer.WriteEndElement();
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Saves the current node to a string.
|
|
/// </summary>
|
|
/// <returns>The saved string.</returns>
|
|
public string WriteTo()
|
|
{
|
|
using (StringWriter sw = new StringWriter())
|
|
{
|
|
WriteTo(sw);
|
|
sw.Flush();
|
|
return sw.ToString();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Internal Methods
|
|
|
|
internal static string GetXmlComment(HtmlCommentNode comment)
|
|
{
|
|
string s = comment.Comment;
|
|
return s.Substring(4, s.Length - 7).Replace("--", " - -");
|
|
}
|
|
|
|
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 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 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;
|
|
}
|
|
|
|
internal void WriteAttribute(TextWriter outText, HtmlAttribute att)
|
|
{
|
|
string name;
|
|
string quote = att.QuoteType == AttributeValueQuote.DoubleQuote ? "\"" : "'";
|
|
if (_ownerdocument.OptionOutputAsXml)
|
|
{
|
|
if (_ownerdocument.OptionOutputUpperCase)
|
|
{
|
|
name = att.XmlName.ToUpper();
|
|
}
|
|
else
|
|
{
|
|
name = att.XmlName;
|
|
}
|
|
if (_ownerdocument.OptionOutputOriginalCase)
|
|
name = att.OriginalName;
|
|
|
|
outText.Write(" " + name + "=" + quote + HtmlDocument.HtmlEncode(att.XmlValue) + quote);
|
|
}
|
|
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 + "=" + quote + att.Value + quote);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
outText.Write(" " + name + "=" + quote + att.Value + quote);
|
|
}
|
|
}
|
|
}
|
|
|
|
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()));
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private Methods
|
|
|
|
private string GetRelativeXpath()
|
|
{
|
|
if (ParentNode == null)
|
|
return Name;
|
|
if (NodeType == HtmlNodeType.Document)
|
|
return string.Empty;
|
|
|
|
int i = 1;
|
|
foreach (HtmlNode node in ParentNode.ChildNodes)
|
|
{
|
|
if (node.Name != Name) continue;
|
|
|
|
if (node == this)
|
|
break;
|
|
|
|
i++;
|
|
}
|
|
return Name + "[" + i + "]";
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |