Node
public abstract class Node : EventTarget, INode, IEventTarget, IMarkupFormattable, IEquatable<INode>, IConstructableNode
Represents a node in the generated tree.
using AngleSharp.Common;
using AngleSharp.Html.Construction;
using AngleSharp.Html.Dom;
using AngleSharp.Text;
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
namespace AngleSharp.Dom
{
[System.Runtime.CompilerServices.NullableContext(1)]
[System.Runtime.CompilerServices.Nullable(0)]
public abstract class Node : EventTarget, INode, IEventTarget, IMarkupFormattable, IEquatable<INode>, IConstructableNode
{
private readonly NodeType _type;
private readonly string _name;
private readonly NodeFlags _flags;
[System.Runtime.CompilerServices.Nullable(2)]
private Url _baseUri;
[System.Runtime.CompilerServices.Nullable(2)]
private Node _parent;
private NodeList _children;
[System.Runtime.CompilerServices.Nullable(2)]
private Document _owner;
public bool HasChildNodes => _children.Length != 0;
public string BaseUri => BaseUrl?.Href ?? string.Empty;
[System.Runtime.CompilerServices.Nullable(2)]
public Url BaseUrl {
[System.Runtime.CompilerServices.NullableContext(2)]
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;
}
[System.Runtime.CompilerServices.NullableContext(2)]
set {
_baseUri = value;
}
}
public NodeType NodeType => _type;
public NodeFlags Flags => _flags;
public virtual string NodeValue {
get {
return null;
}
set {
}
}
public virtual string TextContent {
get {
return null;
}
set {
}
}
[System.Runtime.CompilerServices.Nullable(2)]
INode INode.PreviousSibling {
[System.Runtime.CompilerServices.NullableContext(2)]
get {
return PreviousSibling;
}
}
[System.Runtime.CompilerServices.Nullable(2)]
INode INode.NextSibling {
[System.Runtime.CompilerServices.NullableContext(2)]
get {
return NextSibling;
}
}
[System.Runtime.CompilerServices.Nullable(2)]
INode INode.FirstChild {
[System.Runtime.CompilerServices.NullableContext(2)]
get {
return FirstChild;
}
}
[System.Runtime.CompilerServices.Nullable(2)]
INode INode.LastChild {
[System.Runtime.CompilerServices.NullableContext(2)]
get {
return LastChild;
}
}
IDocument INode.Owner {
get {
return Owner;
}
}
[System.Runtime.CompilerServices.Nullable(2)]
INode INode.Parent {
[System.Runtime.CompilerServices.NullableContext(2)]
get {
return _parent;
}
}
[System.Runtime.CompilerServices.Nullable(2)]
public IElement ParentElement {
[System.Runtime.CompilerServices.NullableContext(2)]
get {
return _parent as IElement;
}
}
INodeList INode.ChildNodes {
get {
return _children;
}
}
public string NodeName => _name;
[System.Runtime.CompilerServices.Nullable(2)]
internal Node PreviousSibling {
[System.Runtime.CompilerServices.NullableContext(2)]
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;
}
}
[System.Runtime.CompilerServices.Nullable(2)]
internal Node NextSibling {
[System.Runtime.CompilerServices.NullableContext(2)]
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;
}
}
[System.Runtime.CompilerServices.Nullable(2)]
internal Node FirstChild {
[System.Runtime.CompilerServices.NullableContext(2)]
get {
if (_children.Length <= 0)
return null;
return _children[0];
}
}
[System.Runtime.CompilerServices.Nullable(2)]
internal Node LastChild {
[System.Runtime.CompilerServices.NullableContext(2)]
get {
if (_children.Length <= 0)
return null;
return _children[_children.Length - 1];
}
}
internal NodeList ChildNodes {
get {
return _children;
}
set {
_children = value;
}
}
[System.Runtime.CompilerServices.Nullable(2)]
internal Node Parent {
[System.Runtime.CompilerServices.NullableContext(2)]
get {
return _parent;
}
[System.Runtime.CompilerServices.NullableContext(2)]
set {
_parent = value;
}
}
internal Document Owner {
get {
if (_type == NodeType.Document)
return null;
return _owner;
}
set {
if (Owner != value) {
foreach (Node item in this.DescendantsAndSelf<Node>()) {
Document owner = item.Owner;
item._owner = value;
if (owner != null)
NodeIsAdopted(owner);
}
}
}
}
StringOrMemory IConstructableNode.NodeName {
get {
return NodeName;
}
}
NodeFlags IConstructableNode.Flags {
get {
return _flags;
}
}
[System.Runtime.CompilerServices.Nullable(2)]
IConstructableNode IConstructableNode.Parent {
[System.Runtime.CompilerServices.NullableContext(2)]
get {
return Parent;
}
[System.Runtime.CompilerServices.NullableContext(2)]
set {
if (value != null)
Parent = (Node)value;
}
}
IConstructableNodeList IConstructableNode.ChildNodes {
get {
return ChildNodes;
}
}
[System.Runtime.CompilerServices.Nullable(0)]
private static ReadOnlySpan<char> WhiteSpace {
[System.Runtime.CompilerServices.NullableContext(0)]
get {
return " \t\r\n".AsSpan();
}
}
public Node([System.Runtime.CompilerServices.Nullable(2)] 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;
}
[System.Runtime.CompilerServices.NullableContext(2)]
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));
ReplacedAll();
}
internal INode InsertBefore(Node newElement, [System.Runtime.CompilerServices.Nullable(2)] Node referenceElement, bool suppressObservers)
{
Document owner = Owner;
if (referenceElement != null && owner != null) {
int index = referenceElement.Index();
foreach (IPreInsert attachedReference in owner.GetAttachedReferences<IPreInsert>()) {
attachedReference.PreInsert(this, newElement, index);
}
}
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 num = _children.Index(node);
if (owner != null) {
foreach (IPreRemove attachedReference in owner.GetAttachedReferences<IPreRemove>()) {
attachedReference.PreRemove(this, node, num);
}
}
Node node2 = (num > 0) ? _children[num - 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(num, node);
NodeIsRemoved(node, node2);
}
internal Node 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);
}
protected virtual void ReplacedAll()
{
}
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 {
IProcessingInstruction processingInstruction = item as IProcessingInstruction;
if (processingInstruction != null)
writer.Write(formatter.Processing(processingInstruction));
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 {
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, [System.Runtime.CompilerServices.Nullable(2)] INode referenceElement)
{
return this.PreInsert(newElement, referenceElement);
}
public INode ReplaceChild(INode newChild, INode oldChild)
{
return ReplaceChild((Node)newChild, (Node)oldChild, 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++) {
TextNode textNode = _children[i] as TextNode;
if (textNode != null) {
int num = textNode.Length;
if (num == 0) {
RemoveChild(textNode, false);
i--;
} else {
StringBuilder stringBuilder = StringBuilderPool.Obtain();
TextNode textNode2 = textNode;
int num2 = i;
Document owner = Owner;
while ((textNode2 = (textNode2.NextSibling as TextNode)) != null) {
stringBuilder.Append(textNode2.Data);
num2++;
foreach (Range attachedReference in owner.GetAttachedReferences<Range>()) {
if (attachedReference.Head == textNode2)
attachedReference.StartWith(textNode, num);
if (attachedReference.Tail == textNode2)
attachedReference.EndWith(textNode, num);
if (attachedReference.Head == textNode2.Parent && attachedReference.Start == num2)
attachedReference.StartWith(textNode, num);
if (attachedReference.Tail == textNode2.Parent && attachedReference.End == num2)
attachedReference.EndWith(textNode, num);
}
num += textNode2.Length;
}
textNode.Replace(textNode.Length, 0, stringBuilder.ToPool());
for (int num3 = num2; num3 > i; num3--) {
RemoveChild(_children[num3], false);
}
}
} else if (_children[i].HasChildNodes) {
_children[i].Normalize();
}
}
}
[System.Runtime.CompilerServices.NullableContext(2)]
public string LookupNamespaceUri(string prefix)
{
if (string.IsNullOrEmpty(prefix))
prefix = null;
return LocateNamespace(prefix);
}
[System.Runtime.CompilerServices.NullableContext(2)]
public string LookupPrefix(string namespaceUri)
{
if (string.IsNullOrEmpty(namespaceUri))
return null;
return LocatePrefix(namespaceUri);
}
[System.Runtime.CompilerServices.NullableContext(2)]
public bool IsDefaultNamespace(string namespaceUri)
{
if (string.IsNullOrEmpty(namespaceUri))
namespaceUri = null;
string other = LocateNamespace(null);
return namespaceUri.Is(other);
}
[System.Runtime.CompilerServices.NullableContext(2)]
public virtual bool Equals(INode otherNode)
{
if (BaseUri.Is(otherNode?.BaseUri) && NodeName.Is(otherNode?.NodeName)) {
int length = ChildNodes.Length;
int? nullable = otherNode?.ChildNodes.Length;
if ((length == nullable.GetValueOrDefault()) & nullable.HasValue) {
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, Node 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, [System.Runtime.CompilerServices.Nullable(2)] ref string namespaceUri, [System.Runtime.CompilerServices.Nullable(2)] 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);
}
[System.Runtime.CompilerServices.NullableContext(2)]
protected static bool IsNamespaceError(string prefix, string namespaceUri, [System.Runtime.CompilerServices.Nullable(1)] 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;
}
[return: System.Runtime.CompilerServices.Nullable(2)]
protected virtual string LocateNamespace(string prefix)
{
return _parent?.LocateNamespace(prefix);
}
[return: System.Runtime.CompilerServices.Nullable(2)]
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, [System.Runtime.CompilerServices.Nullable(2)] Node oldPreviousSibling)
{
removedNode.OnParentChanged();
}
protected virtual void OnParentChanged()
{
}
protected void CloneNode(Node target, Document owner, bool deep)
{
target._baseUri = _baseUri;
if (deep) {
foreach (Node child in _children) {
if (child != null) {
Node node = child;
Node node2 = node.Clone(owner, true);
target.AddNode(node2);
}
}
}
}
void IConstructableNode.RemoveFromParent()
{
Parent?.RemoveChild(this);
}
void IConstructableNode.RemoveChild(IConstructableNode childNode)
{
RemoveChild((Node)childNode, false);
}
void IConstructableNode.RemoveNode(int idx, IConstructableNode childNode)
{
RemoveNode(idx, (Node)childNode);
}
void IConstructableNode.InsertNode(int idx, IConstructableNode childNode)
{
InsertNode(idx, (Node)childNode);
}
void IConstructableNode.AddNode(IConstructableNode node)
{
AddNode((Node)node);
}
void IConstructableNode.AppendText(StringOrMemory text, bool emitWhiteSpaceOnly)
{
if (emitWhiteSpaceOnly || text.Memory.Span.Trim(WhiteSpace).Length != 0)
AppendText(text.ToString());
}
void IConstructableNode.InsertText(int idx, StringOrMemory text, bool emitWhiteSpaceOnly)
{
if (emitWhiteSpaceOnly || text.Memory.Span.Trim(WhiteSpace).Length != 0)
InsertText(idx, text.ToString());
}
}
}