Node
public abstract class Node : EventTarget, INode, IEventTarget, IMarkupFormattable, IEquatable<INode>
Represents a node in the generated tree.
using AngleSharp.Html.Dom;
using AngleSharp.Text;
using System;
using System.IO;
using System.Text;
namespace AngleSharp.Dom
{
public abstract class Node : EventTarget, INode, IEventTarget, IMarkupFormattable, IEquatable<INode>
{
private readonly NodeType _type;
private readonly string _name;
private readonly NodeFlags _flags;
private Url _baseUri;
private Node _parent;
private NodeList _children;
private Document _owner;
public NodeFlags Flags => _flags;
public bool HasChildNodes => _children.Length != 0;
public string BaseUri => BaseUrl?.Href ?? string.Empty;
public Url BaseUrl {
get {
if (_baseUri != null)
return _baseUri;
if (_parent != null) {
foreach (Node item in this.Ancestors<Node>()) {
if (item._baseUri != null)
return item._baseUri;
}
}
Document owner = Owner;
if (owner != null)
return owner._baseUri ?? owner.DocumentUrl;
if (_type == NodeType.Document) {
owner = (Document)this;
return owner.DocumentUrl;
}
return null;
}
set {
_baseUri = value;
}
}
public NodeType NodeType => _type;
public virtual string NodeValue {
get {
return null;
}
set {
}
}
public virtual string TextContent {
get {
return null;
}
set {
}
}
INode INode.PreviousSibling {
get {
return PreviousSibling;
}
}
INode INode.NextSibling {
get {
return NextSibling;
}
}
INode INode.FirstChild {
get {
return FirstChild;
}
}
INode INode.LastChild {
get {
return LastChild;
}
}
IDocument INode.Owner {
get {
return Owner;
}
}
INode INode.Parent {
get {
return _parent;
}
}
public IElement ParentElement => _parent as IElement;
INodeList INode.ChildNodes {
get {
return _children;
}
}
public string NodeName => _name;
internal Node PreviousSibling {
get {
if (_parent != null) {
int length = _parent._children.Length;
for (int i = 1; i < length; i++) {
if (_parent._children[i] == this)
return _parent._children[i - 1];
}
}
return null;
}
}
internal Node NextSibling {
get {
if (_parent != null) {
int num = _parent._children.Length - 1;
for (int i = 0; i < num; i++) {
if (_parent._children[i] == this)
return _parent._children[i + 1];
}
}
return null;
}
}
internal Node FirstChild {
get {
if (_children.Length <= 0)
return null;
return _children[0];
}
}
internal Node LastChild {
get {
if (_children.Length <= 0)
return null;
return _children[_children.Length - 1];
}
}
internal NodeList ChildNodes {
get {
return _children;
}
set {
_children = value;
}
}
internal Node Parent {
get {
return _parent;
}
set {
_parent = value;
}
}
internal Document Owner {
get {
if (_type == NodeType.Document)
return null;
return _owner;
}
set {
foreach (Node item in this.DescendentsAndSelf<Node>()) {
Document owner = item.Owner;
if (owner != value) {
item._owner = value;
if (owner != null)
NodeIsAdopted(owner);
}
}
}
}
public Node(Document owner, string name, NodeType type = NodeType.Element, NodeFlags flags = NodeFlags.None)
{
_owner = owner;
_name = (name ?? string.Empty);
_type = type;
_children = (this.IsEndPoint() ? NodeList.Empty : new NodeList());
_flags = flags;
}
internal void ReplaceAll(Node node, bool suppressObservers)
{
Document owner = Owner;
if (node != null)
owner.AdoptNode(node);
NodeList nodeList = new NodeList();
NodeList nodeList2 = new NodeList();
nodeList.AddRange(_children);
if (node != null) {
if (node.NodeType == NodeType.DocumentFragment)
nodeList2.AddRange(node._children);
else
nodeList2.Add(node);
}
for (int i = 0; i < nodeList.Length; i++) {
RemoveChild(nodeList[i], true);
}
for (int j = 0; j < nodeList2.Length; j++) {
InsertBefore(nodeList2[j], null, true);
}
if (!suppressObservers)
owner.QueueMutation(MutationRecord.ChildList(this, nodeList2, nodeList, null, null));
}
internal INode InsertBefore(Node newElement, Node referenceElement, bool suppressObservers)
{
Document owner = Owner;
int count = (newElement.NodeType != NodeType.DocumentFragment) ? 1 : newElement.ChildNodes.Length;
if (referenceElement != null && owner != null) {
int childIndex = referenceElement.Index();
owner.ForEachRange(delegate(Range m) {
if (m.Head == this)
return m.Start > childIndex;
return false;
}, delegate(Range m) {
m.StartWith(this, m.Start + count);
});
owner.ForEachRange(delegate(Range m) {
if (m.Tail == this)
return m.End > childIndex;
return false;
}, delegate(Range m) {
m.EndWith(this, m.End + count);
});
}
if (newElement.NodeType == NodeType.Document || newElement.Contains(this))
throw new DomException(DomError.HierarchyRequest);
NodeList nodeList = new NodeList();
int num = _children.Index(referenceElement);
if (num == -1)
num = _children.Length;
if (newElement._type == NodeType.DocumentFragment) {
int num2 = num;
int i = num;
while (newElement.HasChildNodes) {
Node node = newElement.ChildNodes[0];
newElement.RemoveChild(node, true);
InsertNode(num2, node);
num2++;
}
for (; i < num2; i++) {
Node node2 = _children[i];
nodeList.Add(node2);
NodeIsInserted(node2);
}
} else {
nodeList.Add(newElement);
InsertNode(num, newElement);
NodeIsInserted(newElement);
}
if (!suppressObservers)
owner?.QueueMutation(MutationRecord.ChildList(this, nodeList, null, (num > 0) ? _children[num - 1] : null, referenceElement));
return newElement;
}
internal void RemoveChild(Node node, bool suppressObservers)
{
Document owner = Owner;
int index = _children.Index(node);
if (owner != null) {
owner.ForEachRange((Range m) => m.Head.IsInclusiveDescendantOf(node), delegate(Range m) {
m.StartWith(this, index);
});
owner.ForEachRange((Range m) => m.Tail.IsInclusiveDescendantOf(node), delegate(Range m) {
m.EndWith(this, index);
});
owner.ForEachRange(delegate(Range m) {
if (m.Head == this)
return m.Start > index;
return false;
}, delegate(Range m) {
m.StartWith(this, m.Start - 1);
});
owner.ForEachRange(delegate(Range m) {
if (m.Tail == this)
return m.End > index;
return false;
}, delegate(Range m) {
m.EndWith(this, m.End - 1);
});
}
Node node2 = (index > 0) ? _children[index - 1] : null;
if (!suppressObservers && owner != null) {
NodeList removedNodes = new NodeList {
node
};
owner.QueueMutation(MutationRecord.ChildList(this, null, removedNodes, node2, node.NextSibling));
owner.AddTransientObserver(node);
}
RemoveNode(index, node);
NodeIsRemoved(node, node2);
}
internal INode ReplaceChild(Node node, Node child, bool suppressObservers)
{
if (this.IsEndPoint() || node.IsHostIncludingInclusiveAncestor(this))
throw new DomException(DomError.HierarchyRequest);
if (child.Parent != this)
throw new DomException(DomError.NotFound);
if (node.IsInsertable()) {
Node nextSibling = child.NextSibling;
Document owner = Owner;
NodeList nodeList = new NodeList();
NodeList nodeList2 = new NodeList();
IDocument document = this as IDocument;
if (document != null && IsChangeForbidden(node, document, child))
throw new DomException(DomError.HierarchyRequest);
if (nextSibling == node)
nextSibling = node.NextSibling;
owner?.AdoptNode(node);
RemoveChild(child, true);
InsertBefore(node, nextSibling, true);
nodeList2.Add(child);
if (node._type == NodeType.DocumentFragment)
nodeList.AddRange(node._children);
else
nodeList.Add(node);
if (!suppressObservers)
owner?.QueueMutation(MutationRecord.ChildList(this, nodeList, nodeList2, child.PreviousSibling, nextSibling));
return child;
}
throw new DomException(DomError.HierarchyRequest);
}
public abstract Node Clone(Document newOwner, bool deep);
public void AppendText(string s)
{
TextNode textNode = LastChild as TextNode;
if (textNode != null)
textNode.Append(s);
else
AddNode(new TextNode(Owner, s));
}
public void InsertText(int index, string s)
{
if (index > 0 && index <= _children.Length) {
IText text = _children[index - 1] as IText;
if (text != null) {
text.Append(s);
return;
}
}
if (index >= 0 && index < _children.Length) {
IText text2 = _children[index] as IText;
if (text2 != null) {
text2.Insert(0, s);
return;
}
}
InsertNode(index, new TextNode(Owner, s));
}
public void InsertNode(int index, Node node)
{
node.Parent = this;
_children.Insert(index, node);
}
public void AddNode(Node node)
{
node.Parent = this;
_children.Add(node);
}
public void RemoveNode(int index, Node node)
{
node.Parent = null;
_children.RemoveAt(index);
}
public void ToHtml(TextWriter writer, IMarkupFormatter formatter)
{
IElement parentElement = ParentElement;
foreach (INode item in this.GetDescendantsAndSelf()) {
IComment comment = item as IComment;
if (comment != null)
writer.Write(formatter.Comment(comment));
else {
ICharacterData characterData = item as ICharacterData;
if (characterData != null) {
INode parent = characterData.Parent;
if (parent != null && parent.Flags.HasFlag(NodeFlags.LiteralText))
writer.Write(formatter.LiteralText(characterData));
else
writer.Write(formatter.Text(characterData));
} else {
IDocumentType documentType = item as IDocumentType;
if (documentType != null)
writer.Write(formatter.Doctype(documentType));
else {
IProcessingInstruction processingInstruction = item as IProcessingInstruction;
if (processingInstruction != null)
writer.Write(formatter.Processing(processingInstruction));
else {
IElement element = item as IElement;
if (element != null) {
NodeFlags flags = element.Flags;
bool flag = flags.HasFlag(NodeFlags.SelfClosing);
writer.Write(formatter.OpenTag(element, flag));
if (!flag && flags.HasFlag(NodeFlags.LineTolerance)) {
IText text = element.FirstChild as IText;
if (text != null && text.Data.Has('\n', 0))
writer.Write('\n');
}
(element as IHtmlTemplateElement)?.Content.ToHtml(writer, formatter);
if (!item.HasChildNodes)
writer.Write(formatter.CloseTag(element, flag));
}
}
}
}
}
if (!item.HasChildNodes && item.NextSibling == null) {
for (IElement element2 = item.ParentElement; element2 != parentElement; element2 = ((element2.NextSibling == null) ? element2.ParentElement : parentElement)) {
writer.Write(formatter.CloseTag(element2, element2.Flags.HasFlag(NodeFlags.SelfClosing)));
}
}
}
}
public INode AppendChild(INode child)
{
return this.PreInsert(child, null);
}
public INode InsertChild(int index, INode child)
{
Node child2 = (index < _children.Length) ? _children[index] : null;
return this.PreInsert(child, child2);
}
public INode InsertBefore(INode newElement, INode referenceElement)
{
return this.PreInsert(newElement, referenceElement);
}
public INode ReplaceChild(INode newChild, INode oldChild)
{
return ReplaceChild(newChild as Node, oldChild as Node, false);
}
public INode RemoveChild(INode child)
{
return this.PreRemove(child);
}
public INode Clone(bool deep = true)
{
return Clone(Owner, deep);
}
public DocumentPositions CompareDocumentPosition(INode otherNode)
{
if (this == otherNode)
return DocumentPositions.Same;
if (Owner != otherNode.Owner) {
DocumentPositions documentPositions = (otherNode.GetHashCode() > GetHashCode()) ? DocumentPositions.Following : DocumentPositions.Preceding;
return DocumentPositions.Disconnected | DocumentPositions.ImplementationSpecific | documentPositions;
}
if (otherNode.IsAncestorOf(this))
return DocumentPositions.Preceding | DocumentPositions.Contains;
if (otherNode.IsDescendantOf(this))
return DocumentPositions.Following | DocumentPositions.ContainedBy;
if (otherNode.IsPreceding(this))
return DocumentPositions.Preceding;
return DocumentPositions.Following;
}
public bool Contains(INode otherNode)
{
return this.IsInclusiveAncestorOf(otherNode);
}
public void Normalize()
{
for (int i = 0; i < _children.Length; i++) {
Node node = _children[i];
TextNode text = node as TextNode;
if (text != null) {
int length = text.Length;
if (length == 0) {
RemoveChild(text, false);
i--;
} else {
StringBuilder stringBuilder = StringBuilderPool.Obtain();
TextNode sibling = text;
int end = i;
Document owner = Owner;
while ((sibling = (sibling.NextSibling as TextNode)) != null) {
stringBuilder.Append(sibling.Data);
end++;
owner.ForEachRange((Range m) => m.Head == sibling, delegate(Range m) {
m.StartWith(text, length);
});
owner.ForEachRange((Range m) => m.Tail == sibling, delegate(Range m) {
m.EndWith(text, length);
});
owner.ForEachRange(delegate(Range m) {
if (m.Head == sibling.Parent)
return m.Start == end;
return false;
}, delegate(Range m) {
m.StartWith(text, length);
});
owner.ForEachRange(delegate(Range m) {
if (m.Tail == sibling.Parent)
return m.End == end;
return false;
}, delegate(Range m) {
m.EndWith(text, length);
});
length += sibling.Length;
}
text.Replace(text.Length, 0, stringBuilder.ToPool());
for (int num = end; num > i; num--) {
RemoveChild(_children[num], false);
}
}
} else if (_children[i].HasChildNodes) {
_children[i].Normalize();
}
}
}
public string LookupNamespaceUri(string prefix)
{
if (string.IsNullOrEmpty(prefix))
prefix = null;
return LocateNamespace(prefix);
}
public string LookupPrefix(string namespaceUri)
{
if (string.IsNullOrEmpty(namespaceUri))
return null;
return LocatePrefix(namespaceUri);
}
public bool IsDefaultNamespace(string namespaceUri)
{
if (string.IsNullOrEmpty(namespaceUri))
namespaceUri = null;
string other = LocateNamespace(null);
return namespaceUri.Is(other);
}
public virtual bool Equals(INode otherNode)
{
if (BaseUri.Is(otherNode.BaseUri) && NodeName.Is(otherNode.NodeName) && ChildNodes.Length == otherNode.ChildNodes.Length) {
for (int i = 0; i < _children.Length; i++) {
if (!_children[i].Equals(otherNode.ChildNodes[i]))
return false;
}
return true;
}
return false;
}
private static bool IsChangeForbidden(Node node, IDocument parent, INode child)
{
switch (node._type) {
case NodeType.DocumentType:
if (parent.Doctype == child)
return child.IsPrecededByElement();
return true;
case NodeType.Element:
if (parent.DocumentElement == child)
return child.IsFollowedByDoctype();
return true;
case NodeType.DocumentFragment: {
int elementCount = node.GetElementCount();
if (elementCount <= 1 && !node.HasTextNodes()) {
if (elementCount == 1) {
if (parent.DocumentElement == child)
return child.IsFollowedByDoctype();
return true;
}
return false;
}
return true;
}
default:
return false;
}
}
protected static void GetPrefixAndLocalName(string qualifiedName, ref string namespaceUri, out string prefix, out string localName)
{
if (!qualifiedName.IsXmlName())
throw new DomException(DomError.InvalidCharacter);
if (!qualifiedName.IsQualifiedName())
throw new DomException(DomError.Namespace);
if (string.IsNullOrEmpty(namespaceUri))
namespaceUri = null;
int num = qualifiedName.IndexOf(':');
if (num > 0) {
prefix = qualifiedName.Substring(0, num);
localName = qualifiedName.Substring(num + 1);
} else {
prefix = null;
localName = qualifiedName;
}
if (IsNamespaceError(prefix, namespaceUri, qualifiedName))
throw new DomException(DomError.Namespace);
}
protected static bool IsNamespaceError(string prefix, string namespaceUri, string qualifiedName)
{
if ((prefix == null || namespaceUri != null) && (!prefix.Is(NamespaceNames.XmlPrefix) || namespaceUri.Is(NamespaceNames.XmlUri)) && ((!qualifiedName.Is(NamespaceNames.XmlNsPrefix) && !prefix.Is(NamespaceNames.XmlNsPrefix)) || namespaceUri.Is(NamespaceNames.XmlNsUri))) {
if (namespaceUri.Is(NamespaceNames.XmlNsUri)) {
if (!qualifiedName.Is(NamespaceNames.XmlNsPrefix))
return !prefix.Is(NamespaceNames.XmlNsPrefix);
return false;
}
return false;
}
return true;
}
protected virtual string LocateNamespace(string prefix)
{
return _parent?.LocateNamespace(prefix);
}
protected virtual string LocatePrefix(string namespaceUri)
{
return _parent?.LocatePrefix(namespaceUri);
}
protected virtual void NodeIsAdopted(Document oldDocument)
{
}
protected virtual void NodeIsInserted(Node newNode)
{
newNode.OnParentChanged();
}
protected virtual void NodeIsRemoved(Node removedNode, Node oldPreviousSibling)
{
removedNode.OnParentChanged();
}
protected virtual void OnParentChanged()
{
}
protected void CloneNode(Node target, Document owner, bool deep)
{
target._baseUri = _baseUri;
if (deep) {
foreach (INode child in _children) {
Node node = child as Node;
if (node != null) {
Node node2 = node.Clone(owner, true);
target.AddNode(node2);
}
}
}
}
}
}