Node
Represents a node in the generated tree.
using AngleSharp.Dom.Collections;
using AngleSharp.Extensions;
using AngleSharp.Html;
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
namespace AngleSharp.Dom
{
internal abstract class Node : EventTarget, INode, IEventTarget, IMarkupFormattable, IEquatable<INode>
{
private static readonly ConditionalWeakTable<Node, Document> Owners = new ConditionalWeakTable<Node, Document>();
private readonly NodeType _type;
private readonly string _name;
private readonly NodeFlags _flags;
private Url _baseUri;
private Node _parent;
private NodeList _children;
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)
return _parent.BaseUrl;
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 NodeFlags Flags => _flags;
internal NodeList ChildNodes {
get {
return _children;
}
set {
_children = value;
}
}
internal Node Parent {
get {
return _parent;
}
set {
_parent = value;
}
}
internal Document Owner {
get {
Document value = null;
if (_type != NodeType.Document)
Owners.TryGetValue(this, out value);
return value;
}
set {
Document owner = Owner;
if (owner != value) {
Owners.Remove(this);
Owners.Add(this, value);
for (int i = 0; i < _children.Length; i++) {
_children[i].Owner = value;
}
if (owner != null)
NodeIsAdopted(owner);
}
}
}
internal Node(Document owner, string name, NodeType type = NodeType.Element, NodeFlags flags = NodeFlags.None)
{
Owners.Add(this, owner);
_name = (name ?? string.Empty);
_type = type;
_children = this.CreateChildren();
_flags = flags;
}
internal void AppendText(string s)
{
TextNode textNode = LastChild as TextNode;
if (textNode == null)
AddNode(new TextNode(Owner, s));
else
textNode.Append(s);
}
internal void InsertText(int index, string s)
{
if (index > 0 && index <= _children.Length && _children[index - 1].NodeType == NodeType.Text)
((IText)_children[index - 1]).Append(s);
else if (index >= 0 && index < _children.Length && _children[index].NodeType == NodeType.Text) {
((IText)_children[index]).Insert(0, s);
} else {
TextNode node = new TextNode(Owner, s);
InsertNode(index, node);
}
}
public virtual void ToHtml(TextWriter writer, IMarkupFormatter formatter)
{
writer.Write(TextContent);
}
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 abstract INode Clone(bool deep = true);
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++) {
TextNode text = _children[i] as TextNode;
if (text != null) {
int length = text.Length;
if (length == 0) {
RemoveChild(text, false);
i--;
} else {
StringBuilder stringBuilder = Pool.NewStringBuilder();
TextNode sibling = text;
int end = i;
Document owner = Owner;
while ((sibling = (sibling.NextSibling as TextNode)) != null) {
stringBuilder.Append(sibling.Data);
end++;
owner.ForEachRange((AngleSharp.Dom.Collections.Range m) => m.Head == sibling, delegate(AngleSharp.Dom.Collections.Range m) {
m.StartWith(text, length);
});
owner.ForEachRange((AngleSharp.Dom.Collections.Range m) => m.Tail == sibling, delegate(AngleSharp.Dom.Collections.Range m) {
m.EndWith(text, length);
});
owner.ForEachRange(delegate(AngleSharp.Dom.Collections.Range m) {
if (m.Head == sibling.Parent)
return m.Start == end;
return false;
}, delegate(AngleSharp.Dom.Collections.Range m) {
m.StartWith(text, length);
});
owner.ForEachRange(delegate(AngleSharp.Dom.Collections.Range m) {
if (m.Tail == sibling.Parent)
return m.End == end;
return false;
}, delegate(AngleSharp.Dom.Collections.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;
}
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);
}
internal void ChangeOwner(Document document)
{
Document owner = Owner;
_parent?.RemoveChild(this, false);
Owner = document;
NodeIsAdopted(owner);
}
internal void InsertNode(int index, Node node)
{
node.Parent = this;
_children.Insert(index, node);
}
internal void AddNode(Node node)
{
node.Parent = this;
_children.Add(node);
}
internal void RemoveNode(int index, Node node)
{
node.Parent = null;
_children.RemoveAt(index);
}
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(AngleSharp.Dom.Collections.Range m) {
if (m.Head == this)
return m.Start > childIndex;
return false;
}, delegate(AngleSharp.Dom.Collections.Range m) {
m.StartWith(this, m.Start + count);
});
owner.ForEachRange(delegate(AngleSharp.Dom.Collections.Range m) {
if (m.Tail == this)
return m.End > childIndex;
return false;
}, delegate(AngleSharp.Dom.Collections.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 != null)
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((AngleSharp.Dom.Collections.Range m) => m.Head.IsInclusiveDescendantOf(node), delegate(AngleSharp.Dom.Collections.Range m) {
m.StartWith(this, index);
});
owner.ForEachRange((AngleSharp.Dom.Collections.Range m) => m.Tail.IsInclusiveDescendantOf(node), delegate(AngleSharp.Dom.Collections.Range m) {
m.EndWith(this, index);
});
owner.ForEachRange(delegate(AngleSharp.Dom.Collections.Range m) {
if (m.Head == this)
return m.Start > index;
return false;
}, delegate(AngleSharp.Dom.Collections.Range m) {
m.StartWith(this, m.Start - 1);
});
owner.ForEachRange(delegate(AngleSharp.Dom.Collections.Range m) {
if (m.Tail == this)
return m.End > index;
return false;
}, delegate(AngleSharp.Dom.Collections.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()) {
IDocument document = _parent as IDocument;
Node nextSibling = child.NextSibling;
Document owner = Owner;
NodeList nodeList = new NodeList();
NodeList nodeList2 = new NodeList();
if (document != null) {
bool flag = false;
switch (node._type) {
case NodeType.DocumentType:
flag = (document.Doctype != child || child.IsPrecededByElement());
break;
case NodeType.Element:
flag = (document.DocumentElement != child || child.IsFollowedByDoctype());
break;
case NodeType.DocumentFragment: {
int elementCount = node.GetElementCount();
flag = (elementCount > 1 || node.HasTextNodes() || (elementCount == 1 && (document.DocumentElement != child || child.IsFollowedByDoctype())));
break;
}
}
if (flag)
throw new DomException(DomError.HierarchyRequest);
}
if (nextSibling == node)
nextSibling = node.NextSibling;
if (owner != null)
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 != null)
owner.QueueMutation(MutationRecord.ChildList(this, nodeList, nodeList2, child.PreviousSibling, nextSibling));
return child;
}
throw new DomException(DomError.HierarchyRequest);
}
internal virtual void NodeIsAdopted(Document oldDocument)
{
}
internal virtual void NodeIsInserted(Node newNode)
{
newNode.OnParentChanged();
}
internal virtual void NodeIsRemoved(Node removedNode, Node oldPreviousSibling)
{
removedNode.OnParentChanged();
}
protected virtual void OnParentChanged()
{
}
protected void CloneNode(Node target, bool deep)
{
target._baseUri = _baseUri;
if (deep) {
foreach (INode child in _children) {
target.AddNode((Node)child.Clone(true));
}
}
}
}
}