File: System\Xml\Dom\XmlNode.cs
Web Access
Project: src\src\libraries\System.Private.Xml\src\System.Private.Xml.csproj (System.Private.Xml)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Text;
using System.Xml.Schema;
using System.Xml.XPath;
using MS.Internal.Xml.XPath;
 
namespace System.Xml
{
    // Represents a single node in the document.
    [DebuggerDisplay("{debuggerDisplayProxy}")]
    public abstract class XmlNode : ICloneable, IEnumerable, IXPathNavigable
    {
        internal XmlNode? parentNode; //this pointer is reused to save the userdata information, need to prevent internal user access the pointer directly.
 
        internal XmlNode()
        {
        }
 
        internal XmlNode(XmlDocument doc)
        {
            if (doc == null)
                throw new ArgumentException(SR.Xdom_Node_Null_Doc);
 
            this.parentNode = doc;
        }
 
        public virtual XPathNavigator? CreateNavigator()
        {
            XmlDocument? thisAsDoc = this as XmlDocument;
            if (thisAsDoc != null)
            {
                return thisAsDoc.CreateNavigator(this);
            }
 
            XmlDocument? doc = OwnerDocument;
            Debug.Assert(doc != null);
            return doc.CreateNavigator(this);
        }
 
        // Selects the first node that matches the xpath expression
        public XmlNode? SelectSingleNode(string xpath)
        {
            if (CreateNavigator() is XPathNavigator navigator)
            {
                XPathNodeIterator nodeIterator = navigator.Select(xpath);
                if (nodeIterator.MoveNext())
                {
                    Debug.Assert(nodeIterator.Current != null);
                    return ((IHasXmlNode)nodeIterator.Current).GetNode();
                }
            }
 
            return null;
        }
 
        // Selects the first node that matches the xpath expression and given namespace context.
        public XmlNode? SelectSingleNode(string xpath, XmlNamespaceManager nsmgr)
        {
            XPathNavigator? xn = (this).CreateNavigator();
            //if the method is called on node types like DocType, Entity, XmlDeclaration,
            //the navigator returned is null. So just return null from here for those node types.
            if (xn == null)
                return null;
 
            XPathExpression exp = xn.Compile(xpath);
            exp.SetContext(nsmgr);
            return new XPathNodeList(xn.Select(exp))[0];
        }
 
        // Selects all nodes that match the xpath expression
        public XmlNodeList? SelectNodes(string xpath)
        {
            XPathNavigator? n = (this).CreateNavigator();
            //if the method is called on node types like DocType, Entity, XmlDeclaration,
            //the navigator returned is null. So just return null from here for those node types.
            if (n == null)
                return null;
 
            return new XPathNodeList(n.Select(xpath));
        }
 
        // Selects all nodes that match the xpath expression and given namespace context.
        public XmlNodeList? SelectNodes(string xpath, XmlNamespaceManager nsmgr)
        {
            XPathNavigator? xn = (this).CreateNavigator();
            //if the method is called on node types like DocType, Entity, XmlDeclaration,
            //the navigator returned is null. So just return null from here for those node types.
            if (xn == null)
                return null;
 
            XPathExpression exp = xn.Compile(xpath);
            exp.SetContext(nsmgr);
            return new XPathNodeList(xn.Select(exp));
        }
 
        // Gets the name of the node.
        public abstract string Name
        {
            get;
        }
 
        // Gets or sets the value of the node.
        public virtual string? Value
        {
            get { return null; }
            set { throw new InvalidOperationException(SR.Format(CultureInfo.InvariantCulture, SR.Xdom_Node_SetVal, NodeType.ToString())); }
        }
 
        // Gets the type of the current node.
        public abstract XmlNodeType NodeType
        {
            get;
        }
 
        // Gets the parent of this node (for nodes that can have parents).
        public virtual XmlNode? ParentNode
        {
            get
            {
                Debug.Assert(parentNode != null);
 
                if (parentNode.NodeType != XmlNodeType.Document)
                {
                    return parentNode;
                }
 
                // Linear lookup through the children of the document
                XmlLinkedNode? firstChild = parentNode.FirstChild as XmlLinkedNode;
                if (firstChild != null)
                {
                    XmlLinkedNode? node = firstChild;
                    do
                    {
                        if (node == this)
                        {
                            return parentNode;
                        }
 
                        node = node.next;
                    }
                    while (node != null
                           && node != firstChild);
                }
 
                return null;
            }
        }
 
        // Gets all children of this node.
        public virtual XmlNodeList ChildNodes
        {
            get { return new XmlChildNodes(this); }
        }
 
        // Gets the node immediately preceding this node.
        public virtual XmlNode? PreviousSibling
        {
            get { return null; }
        }
 
        // Gets the node immediately following this node.
        public virtual XmlNode? NextSibling
        {
            get { return null; }
        }
 
        // Gets a XmlAttributeCollection containing the attributes
        // of this node.
        public virtual XmlAttributeCollection? Attributes
        {
            get { return null; }
        }
 
        // Gets the XmlDocument that contains this node.
        public virtual XmlDocument? OwnerDocument
        {
            get
            {
                Debug.Assert(parentNode != null);
                if (parentNode.NodeType == XmlNodeType.Document)
                    return (XmlDocument)parentNode;
                return parentNode.OwnerDocument;
            }
        }
 
        // Gets the first child of this node.
        public virtual XmlNode? FirstChild
        {
            get
            {
                XmlLinkedNode? linkedNode = LastNode;
                if (linkedNode != null)
                    return linkedNode.next;
 
                return null;
            }
        }
 
        // Gets the last child of this node.
        public virtual XmlNode? LastChild
        {
            get { return LastNode; }
        }
 
        internal virtual bool IsContainer
        {
            get { return false; }
        }
 
        internal virtual XmlLinkedNode? LastNode
        {
            get { return null; }
            set { }
        }
 
        internal bool AncestorNode(XmlNode node)
        {
            XmlNode? n = this.ParentNode;
 
            while (n != null && n != this)
            {
                if (n == node)
                    return true;
                n = n.ParentNode;
            }
 
            return false;
        }
 
        //trace to the top to find out its parent node.
        internal bool IsConnected()
        {
            XmlNode? parent = ParentNode;
            while (parent != null && !(parent.NodeType == XmlNodeType.Document))
                parent = parent.ParentNode;
            return parent != null;
        }
 
        // Inserts the specified node immediately before the specified reference node.
        public virtual XmlNode? InsertBefore(XmlNode newChild, XmlNode? refChild)
        {
            if (this == newChild || AncestorNode(newChild))
                throw new ArgumentException(SR.Xdom_Node_Insert_Child);
 
            if (refChild == null)
                return AppendChild(newChild);
 
            if (!IsContainer)
                throw new InvalidOperationException(SR.Xdom_Node_Insert_Contain);
 
            if (refChild.ParentNode != this)
                throw new ArgumentException(SR.Xdom_Node_Insert_Path);
 
            if (newChild == refChild)
                return newChild;
 
            XmlDocument? childDoc = newChild.OwnerDocument;
            XmlDocument? thisDoc = OwnerDocument;
            if (childDoc != null && childDoc != thisDoc && childDoc != this)
                throw new ArgumentException(SR.Xdom_Node_Insert_Context);
 
            if (!CanInsertBefore(newChild, refChild))
                throw new InvalidOperationException(SR.Xdom_Node_Insert_Location);
 
            newChild.ParentNode?.RemoveChild(newChild);
 
            // special case for doc-fragment.
            if (newChild.NodeType == XmlNodeType.DocumentFragment)
            {
                XmlNode? first = newChild.FirstChild;
                XmlNode? node = first;
                if (node != null)
                {
                    newChild.RemoveChild(node);
                    InsertBefore(node, refChild);
                    // insert the rest of the children after this one.
                    InsertAfter(newChild, node);
                }
 
                return first;
            }
 
            if (!(newChild is XmlLinkedNode) || !IsValidChildType(newChild.NodeType))
                throw new InvalidOperationException(SR.Xdom_Node_Insert_TypeConflict);
 
            XmlLinkedNode newNode = (XmlLinkedNode)newChild;
            XmlLinkedNode refNode = (XmlLinkedNode)refChild;
 
            string? newChildValue = newChild.Value;
            XmlNodeChangedEventArgs? args = GetEventArgs(newChild, newChild.ParentNode, this, newChildValue, newChildValue, XmlNodeChangedAction.Insert);
 
            if (args != null)
                BeforeEvent(args);
 
            if (refNode == FirstChild)
            {
                newNode.next = refNode;
                LastNode!.next = newNode;
                newNode.SetParent(this);
 
                if (newNode.IsText)
                {
                    if (refNode.IsText)
                    {
                        NestTextNodes(newNode, refNode);
                    }
                }
            }
            else
            {
                XmlLinkedNode prevNode = (XmlLinkedNode)refNode.PreviousSibling!;
 
                newNode.next = refNode;
                prevNode.next = newNode;
                newNode.SetParent(this);
 
                if (prevNode.IsText)
                {
                    if (newNode.IsText)
                    {
                        NestTextNodes(prevNode, newNode);
                        if (refNode.IsText)
                        {
                            NestTextNodes(newNode, refNode);
                        }
                    }
                    else
                    {
                        if (refNode.IsText)
                        {
                            UnnestTextNodes(prevNode, refNode);
                        }
                    }
                }
                else
                {
                    if (newNode.IsText)
                    {
                        if (refNode.IsText)
                        {
                            NestTextNodes(newNode, refNode);
                        }
                    }
                }
            }
 
            if (args != null)
                AfterEvent(args);
 
            return newNode;
        }
 
        // Inserts the specified node immediately after the specified reference node.
        public virtual XmlNode? InsertAfter(XmlNode newChild, XmlNode? refChild)
        {
            if (this == newChild || AncestorNode(newChild))
                throw new ArgumentException(SR.Xdom_Node_Insert_Child);
 
            if (refChild == null)
                return PrependChild(newChild);
 
            if (!IsContainer)
                throw new InvalidOperationException(SR.Xdom_Node_Insert_Contain);
 
            if (refChild.ParentNode != this)
                throw new ArgumentException(SR.Xdom_Node_Insert_Path);
 
            if (newChild == refChild)
                return newChild;
 
            XmlDocument? childDoc = newChild.OwnerDocument;
            XmlDocument? thisDoc = OwnerDocument;
            if (childDoc != null && childDoc != thisDoc && childDoc != this)
                throw new ArgumentException(SR.Xdom_Node_Insert_Context);
 
            if (!CanInsertAfter(newChild, refChild))
                throw new InvalidOperationException(SR.Xdom_Node_Insert_Location);
 
            newChild.ParentNode?.RemoveChild(newChild);
 
            // special case for doc-fragment.
            if (newChild.NodeType == XmlNodeType.DocumentFragment)
            {
                XmlNode last = refChild;
                XmlNode? first = newChild.FirstChild;
                XmlNode? node = first;
                while (node != null)
                {
                    XmlNode? next = node.NextSibling;
                    newChild.RemoveChild(node);
                    InsertAfter(node, last);
                    last = node;
                    node = next;
                }
                return first;
            }
 
            if (!(newChild is XmlLinkedNode) || !IsValidChildType(newChild.NodeType))
                throw new InvalidOperationException(SR.Xdom_Node_Insert_TypeConflict);
 
            XmlLinkedNode newNode = (XmlLinkedNode)newChild;
            XmlLinkedNode refNode = (XmlLinkedNode)refChild;
 
            string? newChildValue = newChild.Value;
            XmlNodeChangedEventArgs? args = GetEventArgs(newChild, newChild.ParentNode, this, newChildValue, newChildValue, XmlNodeChangedAction.Insert);
 
            if (args != null)
                BeforeEvent(args);
 
            if (refNode == LastNode)
            {
                newNode.next = refNode.next;
                refNode.next = newNode;
                LastNode = newNode;
                newNode.SetParent(this);
 
                if (refNode.IsText)
                {
                    if (newNode.IsText)
                    {
                        NestTextNodes(refNode, newNode);
                    }
                }
            }
            else
            {
                XmlLinkedNode nextNode = refNode.next!;
 
                newNode.next = nextNode;
                refNode.next = newNode;
                newNode.SetParent(this);
 
                if (refNode.IsText)
                {
                    if (newNode.IsText)
                    {
                        NestTextNodes(refNode, newNode);
                        if (nextNode.IsText)
                        {
                            NestTextNodes(newNode, nextNode);
                        }
                    }
                    else
                    {
                        if (nextNode.IsText)
                        {
                            UnnestTextNodes(refNode, nextNode);
                        }
                    }
                }
                else
                {
                    if (newNode.IsText)
                    {
                        if (nextNode.IsText)
                        {
                            NestTextNodes(newNode, nextNode);
                        }
                    }
                }
            }
 
 
            if (args != null)
                AfterEvent(args);
 
            return newNode;
        }
 
        // Replaces the child node oldChild with newChild node.
        public virtual XmlNode ReplaceChild(XmlNode newChild, XmlNode oldChild)
        {
            XmlNode? nextNode = oldChild.NextSibling;
            RemoveChild(oldChild);
            InsertBefore(newChild, nextNode);
            return oldChild;
        }
 
        // Removes specified child node.
        public virtual XmlNode RemoveChild(XmlNode oldChild)
        {
            if (!IsContainer)
                throw new InvalidOperationException(SR.Xdom_Node_Remove_Contain);
 
            if (oldChild.ParentNode != this)
                throw new ArgumentException(SR.Xdom_Node_Remove_Child);
 
            XmlLinkedNode oldNode = (XmlLinkedNode)oldChild;
 
            string? oldNodeValue = oldNode.Value;
            XmlNodeChangedEventArgs? args = GetEventArgs(oldNode, this, null, oldNodeValue, oldNodeValue, XmlNodeChangedAction.Remove);
 
            if (args != null)
                BeforeEvent(args);
 
            XmlLinkedNode? lastNode = LastNode;
 
            if (oldNode == FirstChild)
            {
                if (oldNode == lastNode)
                {
                    LastNode = null;
                    oldNode.next = null;
                    oldNode.SetParent(null);
                }
                else
                {
                    XmlLinkedNode nextNode = oldNode.next!;
 
                    if (nextNode.IsText)
                    {
                        if (oldNode.IsText)
                        {
                            UnnestTextNodes(oldNode, nextNode);
                        }
                    }
 
                    lastNode!.next = nextNode;
                    oldNode.next = null;
                    oldNode.SetParent(null);
                }
            }
            else
            {
                if (oldNode == lastNode)
                {
                    XmlLinkedNode prevNode = (XmlLinkedNode)oldNode.PreviousSibling!;
                    prevNode.next = oldNode.next;
                    LastNode = prevNode;
                    oldNode.next = null;
                    oldNode.SetParent(null);
                }
                else
                {
                    XmlLinkedNode prevNode = (XmlLinkedNode)oldNode.PreviousSibling!;
                    XmlLinkedNode nextNode = oldNode.next!;
 
                    if (nextNode.IsText)
                    {
                        if (prevNode.IsText)
                        {
                            NestTextNodes(prevNode, nextNode);
                        }
                        else
                        {
                            if (oldNode.IsText)
                            {
                                UnnestTextNodes(oldNode, nextNode);
                            }
                        }
                    }
 
                    prevNode.next = nextNode;
                    oldNode.next = null;
                    oldNode.SetParent(null);
                }
            }
 
            if (args != null)
                AfterEvent(args);
 
            return oldChild;
        }
 
        // Adds the specified node to the beginning of the list of children of this node.
        public virtual XmlNode? PrependChild(XmlNode newChild)
        {
            return InsertBefore(newChild, FirstChild);
        }
 
        // Adds the specified node to the end of the list of children of this node.
        public virtual XmlNode? AppendChild(XmlNode newChild)
        {
            XmlDocument? thisDoc = OwnerDocument ?? this as XmlDocument;
            if (!IsContainer)
                throw new InvalidOperationException(SR.Xdom_Node_Insert_Contain);
 
            if (this == newChild || AncestorNode(newChild))
                throw new ArgumentException(SR.Xdom_Node_Insert_Child);
 
            newChild.ParentNode?.RemoveChild(newChild);
 
            XmlDocument? childDoc = newChild.OwnerDocument;
            if (childDoc != null && childDoc != thisDoc && childDoc != this)
                throw new ArgumentException(SR.Xdom_Node_Insert_Context);
 
            // special case for doc-fragment.
            if (newChild.NodeType == XmlNodeType.DocumentFragment)
            {
                XmlNode? first = newChild.FirstChild;
                XmlNode? node = first;
                while (node != null)
                {
                    XmlNode? next = node.NextSibling;
                    newChild.RemoveChild(node);
                    AppendChild(node);
                    node = next;
                }
 
                return first;
            }
 
            if (!(newChild is XmlLinkedNode) || !IsValidChildType(newChild.NodeType))
                throw new InvalidOperationException(SR.Xdom_Node_Insert_TypeConflict);
 
 
            if (!CanInsertAfter(newChild, LastChild))
                throw new InvalidOperationException(SR.Xdom_Node_Insert_Location);
 
            string? newChildValue = newChild.Value;
            XmlNodeChangedEventArgs? args = GetEventArgs(newChild, newChild.ParentNode, this, newChildValue, newChildValue, XmlNodeChangedAction.Insert);
 
            if (args != null)
                BeforeEvent(args);
 
            XmlLinkedNode? refNode = LastNode;
            XmlLinkedNode newNode = (XmlLinkedNode)newChild;
 
            if (refNode == null)
            {
                newNode.next = newNode;
                LastNode = newNode;
                newNode.SetParent(this);
            }
            else
            {
                newNode.next = refNode.next;
                refNode.next = newNode;
                LastNode = newNode;
                newNode.SetParent(this);
 
                if (refNode.IsText)
                {
                    if (newNode.IsText)
                    {
                        NestTextNodes(refNode, newNode);
                    }
                }
            }
 
            if (args != null)
                AfterEvent(args);
 
            return newNode;
        }
 
        //the function is provided only at Load time to speed up Load process
        internal virtual XmlNode AppendChildForLoad(XmlNode newChild, XmlDocument doc)
        {
            XmlNodeChangedEventArgs? args = doc.GetInsertEventArgsForLoad(newChild, this);
 
            if (args != null)
                doc.BeforeEvent(args);
 
            XmlLinkedNode? refNode = LastNode;
            XmlLinkedNode newNode = (XmlLinkedNode)newChild;
 
            if (refNode == null)
            {
                newNode.next = newNode;
                LastNode = newNode;
                newNode.SetParentForLoad(this);
            }
            else
            {
                newNode.next = refNode.next;
                refNode.next = newNode;
                LastNode = newNode;
                if (refNode.IsText
                    && newNode.IsText)
                {
                    NestTextNodes(refNode, newNode);
                }
                else
                {
                    newNode.SetParentForLoad(this);
                }
            }
 
            if (args != null)
                doc.AfterEvent(args);
 
            return newNode;
        }
 
        internal virtual bool IsValidChildType(XmlNodeType type)
        {
            return false;
        }
 
        internal virtual bool CanInsertBefore(XmlNode newChild, XmlNode? refChild)
        {
            return true;
        }
 
        internal virtual bool CanInsertAfter(XmlNode newChild, XmlNode? refChild)
        {
            return true;
        }
 
        // Gets a value indicating whether this node has any child nodes.
        public virtual bool HasChildNodes
        {
            get { return LastNode != null; }
        }
 
        // Creates a duplicate of this node.
        public abstract XmlNode CloneNode(bool deep);
 
        internal virtual void CopyChildren(XmlDocument doc, XmlNode container, bool deep)
        {
            for (XmlNode? child = container.FirstChild; child != null; child = child.NextSibling)
            {
                AppendChildForLoad(child.CloneNode(deep), doc);
            }
        }
 
        // DOM Level 2
 
        // Puts all XmlText nodes in the full depth of the sub-tree
        // underneath this XmlNode into a "normal" form where only
        // markup (e.g., tags, comments, processing instructions, CDATA sections,
        // and entity references) separates XmlText nodes, that is, there
        // are no adjacent XmlText nodes.
        public virtual void Normalize()
        {
            XmlNode? firstChildTextLikeNode = null;
            StringBuilder sb = StringBuilderCache.Acquire();
            for (XmlNode? crtChild = this.FirstChild; crtChild != null;)
            {
                XmlNode? nextChild = crtChild.NextSibling;
                switch (crtChild.NodeType)
                {
                    case XmlNodeType.Text:
                    case XmlNodeType.Whitespace:
                    case XmlNodeType.SignificantWhitespace:
                        {
                            sb.Append(crtChild.Value);
                            XmlNode? winner = NormalizeWinner(firstChildTextLikeNode, crtChild);
                            if (winner == firstChildTextLikeNode)
                            {
                                this.RemoveChild(crtChild);
                            }
                            else
                            {
                                if (firstChildTextLikeNode != null)
                                    this.RemoveChild(firstChildTextLikeNode);
                                firstChildTextLikeNode = crtChild;
                            }
 
                            break;
                        }
                    case XmlNodeType.Element:
                        {
                            crtChild.Normalize();
                            goto default;
                        }
                    default:
                        {
                            if (firstChildTextLikeNode != null)
                            {
                                firstChildTextLikeNode.Value = sb.ToString();
                                firstChildTextLikeNode = null;
                            }
 
                            sb.Clear();
                            break;
                        }
                }
                crtChild = nextChild;
            }
            if (firstChildTextLikeNode != null && sb.Length > 0)
                firstChildTextLikeNode.Value = sb.ToString();
 
            StringBuilderCache.Release(sb);
        }
 
        private static XmlNode? NormalizeWinner(XmlNode? firstNode, XmlNode secondNode)
        {
            //first node has the priority
            if (firstNode == null)
                return secondNode;
            Debug.Assert(firstNode.NodeType == XmlNodeType.Text
                        || firstNode.NodeType == XmlNodeType.SignificantWhitespace
                        || firstNode.NodeType == XmlNodeType.Whitespace
                        || secondNode.NodeType == XmlNodeType.Text
                        || secondNode.NodeType == XmlNodeType.SignificantWhitespace
                        || secondNode.NodeType == XmlNodeType.Whitespace);
            if (firstNode.NodeType == XmlNodeType.Text)
                return firstNode;
            if (secondNode.NodeType == XmlNodeType.Text)
                return secondNode;
            if (firstNode.NodeType == XmlNodeType.SignificantWhitespace)
                return firstNode;
            if (secondNode.NodeType == XmlNodeType.SignificantWhitespace)
                return secondNode;
            if (firstNode.NodeType == XmlNodeType.Whitespace)
                return firstNode;
            if (secondNode.NodeType == XmlNodeType.Whitespace)
                return secondNode;
            Debug.Assert(true, "shouldn't have fall through here.");
            return null;
        }
 
        // Test if the DOM implementation implements a specific feature.
        public virtual bool Supports(string feature, string version)
        {
            if (string.Equals("XML", feature, StringComparison.OrdinalIgnoreCase))
            {
                if (version == null || version == "1.0" || version == "2.0")
                    return true;
            }
            return false;
        }
 
        // Gets the namespace URI of this node.
        public virtual string NamespaceURI
        {
            get { return string.Empty; }
        }
 
        // Gets or sets the namespace prefix of this node.
        [AllowNull]
        public virtual string Prefix
        {
            get { return string.Empty; }
            set { }
        }
 
        // Gets the name of the node without the namespace prefix.
        public abstract string LocalName
        {
            get;
        }
 
        // Microsoft extensions
 
        // Gets a value indicating whether the node is read-only.
        public virtual bool IsReadOnly
        {
            get
            {
                return HasReadOnlyParent(this);
            }
        }
 
        internal static bool HasReadOnlyParent(XmlNode? n)
        {
            while (n != null)
            {
                switch (n.NodeType)
                {
                    case XmlNodeType.EntityReference:
                    case XmlNodeType.Entity:
                        return true;
 
                    case XmlNodeType.Attribute:
                        n = ((XmlAttribute)n).OwnerElement;
                        break;
 
                    default:
                        n = n.ParentNode;
                        break;
                }
            }
            return false;
        }
 
        // Creates a duplicate of this node.
        public virtual XmlNode Clone()
        {
            return this.CloneNode(true);
        }
 
        object ICloneable.Clone()
        {
            return this.CloneNode(true);
        }
 
        // Provides a simple ForEach-style iteration over the
        // collection of nodes in this XmlNamedNodeMap.
        IEnumerator IEnumerable.GetEnumerator()
        {
            return new XmlChildEnumerator(this);
        }
 
        public IEnumerator GetEnumerator()
        {
            return new XmlChildEnumerator(this);
        }
 
        private void AppendChildText(StringBuilder builder)
        {
            for (XmlNode? child = FirstChild; child != null; child = child.NextSibling)
            {
                if (child.FirstChild == null)
                {
                    if (child.NodeType == XmlNodeType.Text || child.NodeType == XmlNodeType.CDATA
                        || child.NodeType == XmlNodeType.Whitespace || child.NodeType == XmlNodeType.SignificantWhitespace)
                        builder.Append(child.InnerText);
                }
                else
                {
                    child.AppendChildText(builder);
                }
            }
        }
 
        // Gets or sets the concatenated values of the node and
        // all its children.
        public virtual string InnerText
        {
            get
            {
                XmlNode? fc = FirstChild;
                if (fc == null)
                {
                    return string.Empty;
                }
                if (fc.NextSibling == null)
                {
                    XmlNodeType nodeType = fc.NodeType;
                    switch (nodeType)
                    {
                        case XmlNodeType.Text:
                        case XmlNodeType.CDATA:
                        case XmlNodeType.Whitespace:
                        case XmlNodeType.SignificantWhitespace:
                            return fc.Value!;
                    }
                }
 
                StringBuilder builder = StringBuilderCache.Acquire();
                AppendChildText(builder);
                return StringBuilderCache.GetStringAndRelease(builder);
            }
 
            set
            {
                XmlNode? firstChild = FirstChild;
                if (firstChild != null  //there is one child
                    && firstChild.NextSibling == null // and exactly one
                    && firstChild.NodeType == XmlNodeType.Text)//which is a text node
                {
                    //this branch is for perf reason and event fired when TextNode.Value is changed
                    firstChild.Value = value;
                }
                else
                {
                    RemoveAll();
                    AppendChild(OwnerDocument!.CreateTextNode(value));
                }
            }
        }
 
        // Gets the markup representing this node and all its children.
        public virtual string OuterXml
        {
            get
            {
                StringWriter sw = new StringWriter(CultureInfo.InvariantCulture);
                XmlDOMTextWriter xw = new XmlDOMTextWriter(sw);
                try
                {
                    WriteTo(xw);
                }
                finally
                {
                    xw.Close();
                }
                return sw.ToString();
            }
        }
 
        // Gets or sets the markup representing just the children of this node.
        public virtual string InnerXml
        {
            get
            {
                StringWriter sw = new StringWriter(CultureInfo.InvariantCulture);
                XmlDOMTextWriter xw = new XmlDOMTextWriter(sw);
                try
                {
                    WriteContentTo(xw);
                }
                finally
                {
                    xw.Close();
                }
                return sw.ToString();
            }
 
            set
            {
                throw new InvalidOperationException(SR.Xdom_Set_InnerXml);
            }
        }
 
        public virtual IXmlSchemaInfo SchemaInfo
        {
            get
            {
                return XmlDocument.NotKnownSchemaInfo;
            }
        }
 
        public virtual string BaseURI
        {
            get
            {
                XmlNode? curNode = this.ParentNode; //save one while loop since if going to here, the nodetype of this node can't be document, entity and entityref
                while (curNode != null)
                {
                    XmlNodeType nt = curNode.NodeType;
                    //EntityReference's children come from the dtd where they are defined.
                    //we need to investigate the same thing for entity's children if they are defined in an external dtd file.
                    if (nt == XmlNodeType.EntityReference)
                        return ((XmlEntityReference)curNode).ChildBaseURI;
                    if (nt == XmlNodeType.Document
                        || nt == XmlNodeType.Entity
                        || nt == XmlNodeType.Attribute)
                        return curNode.BaseURI;
                    curNode = curNode.ParentNode;
                }
                return string.Empty;
            }
        }
 
        // Saves the current node to the specified XmlWriter.
        public abstract void WriteTo(XmlWriter w);
 
        // Saves all the children of the node to the specified XmlWriter.
        public abstract void WriteContentTo(XmlWriter w);
 
        // Removes all the children and/or attributes
        // of the current node.
        public virtual void RemoveAll()
        {
            XmlNode? child = FirstChild;
            XmlNode? sibling;
 
            while (child != null)
            {
                sibling = child.NextSibling;
                RemoveChild(child);
                child = sibling;
            }
        }
 
        internal XmlDocument Document
        {
            get
            {
                if (NodeType == XmlNodeType.Document)
                    return (XmlDocument)this;
                return OwnerDocument!;
            }
        }
 
        // Looks up the closest xmlns declaration for the given
        // prefix that is in scope for the current node and returns
        // the namespace URI in the declaration.
        public virtual string GetNamespaceOfPrefix(string prefix)
        {
            string? namespaceName = GetNamespaceOfPrefixStrict(prefix);
            return namespaceName ?? string.Empty;
        }
 
        internal string? GetNamespaceOfPrefixStrict(string prefix)
        {
            XmlDocument doc = Document;
            string? pref = prefix;
 
            if (doc != null)
            {
                pref = doc.NameTable.Get(pref);
                if (pref == null)
                    return null;
 
                XmlNode? node = this;
                while (node != null)
                {
                    if (node.NodeType == XmlNodeType.Element)
                    {
                        XmlElement elem = (XmlElement)node;
                        if (elem.HasAttributes)
                        {
                            XmlAttributeCollection attrs = elem.Attributes;
                            if (pref.Length == 0)
                            {
                                for (int iAttr = 0; iAttr < attrs.Count; iAttr++)
                                {
                                    XmlAttribute attr = attrs[iAttr];
                                    if (attr.Prefix.Length == 0)
                                    {
                                        if (Ref.Equal(attr.LocalName, doc.strXmlns))
                                        {
                                            return attr.Value; // found xmlns
                                        }
                                    }
                                }
                            }
                            else
                            {
                                for (int iAttr = 0; iAttr < attrs.Count; iAttr++)
                                {
                                    XmlAttribute attr = attrs[iAttr];
                                    if (Ref.Equal(attr.Prefix, doc.strXmlns))
                                    {
                                        if (Ref.Equal(attr.LocalName, pref))
                                        {
                                            return attr.Value; // found xmlns:prefix
                                        }
                                    }
                                    else if (Ref.Equal(attr.Prefix, pref))
                                    {
                                        return attr.NamespaceURI; // found prefix:attr
                                    }
                                }
                            }
                        }
                        if (Ref.Equal(node.Prefix, pref))
                        {
                            return node.NamespaceURI;
                        }
                        node = node.ParentNode;
                    }
                    else if (node.NodeType == XmlNodeType.Attribute)
                    {
                        node = ((XmlAttribute)node).OwnerElement;
                    }
                    else
                    {
                        node = node.ParentNode;
                    }
                }
                if (Ref.Equal(doc.strXml, pref))
                { // xmlns:xml
                    return doc.strReservedXml;
                }
                else if (Ref.Equal(doc.strXmlns, pref))
                { // xmlns:xmlns
                    return doc.strReservedXmlns;
                }
            }
 
            return null;
        }
 
        // Looks up the closest xmlns declaration for the given namespace
        // URI that is in scope for the current node and returns
        // the prefix defined in that declaration.
        public virtual string GetPrefixOfNamespace(string namespaceURI)
        {
            return GetPrefixOfNamespaceStrict(namespaceURI) ?? string.Empty;
        }
 
        internal string? GetPrefixOfNamespaceStrict(string namespaceURI)
        {
            XmlDocument doc = Document;
            if (doc != null)
            {
                namespaceURI = doc.NameTable.Add(namespaceURI);
 
                XmlNode? node = this;
                while (node != null)
                {
                    if (node.NodeType == XmlNodeType.Element)
                    {
                        XmlElement elem = (XmlElement)node;
                        if (elem.HasAttributes)
                        {
                            XmlAttributeCollection attrs = elem.Attributes;
                            for (int iAttr = 0; iAttr < attrs.Count; iAttr++)
                            {
                                XmlAttribute attr = attrs[iAttr];
                                if (attr.Prefix.Length == 0)
                                {
                                    if (Ref.Equal(attr.LocalName, doc.strXmlns))
                                    {
                                        if (attr.Value == namespaceURI)
                                        {
                                            return string.Empty; // found xmlns="namespaceURI"
                                        }
                                    }
                                }
                                else if (Ref.Equal(attr.Prefix, doc.strXmlns))
                                {
                                    if (attr.Value == namespaceURI)
                                    {
                                        return attr.LocalName; // found xmlns:prefix="namespaceURI"
                                    }
                                }
                                else if (Ref.Equal(attr.NamespaceURI, namespaceURI))
                                {
                                    return attr.Prefix; // found prefix:attr
                                                        // with prefix bound to namespaceURI
                                }
                            }
                        }
                        if (Ref.Equal(node.NamespaceURI, namespaceURI))
                        {
                            return node.Prefix;
                        }
                        node = node.ParentNode;
                    }
                    else if (node.NodeType == XmlNodeType.Attribute)
                    {
                        node = ((XmlAttribute)node).OwnerElement;
                    }
                    else
                    {
                        node = node.ParentNode;
                    }
                }
                if (Ref.Equal(doc.strReservedXml, namespaceURI))
                { // xmlns:xml
                    return doc.strXml;
                }
                else if (Ref.Equal(doc.strReservedXmlns, namespaceURI))
                { // xmlns:xmlns
                    return doc.strXmlns;
                }
            }
 
            return null;
        }
 
        // Retrieves the first child element with the specified name.
        public virtual XmlElement? this[string name]
        {
            get
            {
                for (XmlNode? n = FirstChild; n != null; n = n.NextSibling)
                {
                    if (n.NodeType == XmlNodeType.Element && n.Name == name)
                        return (XmlElement)n;
                }
 
                return null;
            }
        }
 
        // Retrieves the first child element with the specified LocalName and
        // NamespaceURI.
        public virtual XmlElement? this[string localname, string ns]
        {
            get
            {
                for (XmlNode? n = FirstChild; n != null; n = n.NextSibling)
                {
                    if (n.NodeType == XmlNodeType.Element && n.LocalName == localname && n.NamespaceURI == ns)
                        return (XmlElement)n;
                }
 
                return null;
            }
        }
 
        internal virtual void SetParent(XmlNode? node)
        {
            if (node == null)
            {
                this.parentNode = OwnerDocument;
            }
            else
            {
                this.parentNode = node;
            }
        }
 
        internal virtual void SetParentForLoad(XmlNode node)
        {
            this.parentNode = node;
        }
 
        internal static void SplitName(string name, out string prefix, out string localName)
        {
            int colonPos = name.IndexOf(':'); // ordinal compare
            if (-1 == colonPos || 0 == colonPos || name.Length - 1 == colonPos)
            {
                prefix = string.Empty;
                localName = name;
            }
            else
            {
                prefix = name.Substring(0, colonPos);
                localName = name.Substring(colonPos + 1);
            }
        }
 
        internal virtual XmlNode? FindChild(XmlNodeType type)
        {
            for (XmlNode? child = FirstChild; child != null; child = child.NextSibling)
            {
                if (child.NodeType == type)
                {
                    return child;
                }
            }
 
            return null;
        }
 
        internal virtual XmlNodeChangedEventArgs? GetEventArgs(XmlNode node, XmlNode? oldParent, XmlNode? newParent, string? oldValue, string? newValue, XmlNodeChangedAction action)
        {
            XmlDocument? doc = OwnerDocument;
            if (doc != null)
            {
                if (!doc.IsLoading)
                {
                    if (((newParent != null && newParent.IsReadOnly) || (oldParent != null && oldParent.IsReadOnly)))
                        throw new InvalidOperationException(SR.Xdom_Node_Modify_ReadOnly);
                }
 
                return doc.GetEventArgs(node, oldParent, newParent, oldValue, newValue, action);
            }
 
            return null;
        }
 
        internal virtual void BeforeEvent(XmlNodeChangedEventArgs args)
        {
            if (args != null)
                OwnerDocument!.BeforeEvent(args);
        }
 
        internal virtual void AfterEvent(XmlNodeChangedEventArgs args)
        {
            if (args != null)
                OwnerDocument!.AfterEvent(args);
        }
 
        internal virtual XmlSpace XmlSpace
        {
            get
            {
                XmlNode? node = this;
                XmlElement? elem;
                do
                {
                    elem = node as XmlElement;
                    if (elem != null && elem.HasAttribute("xml:space"))
                    {
                        switch (elem.GetAttribute("xml:space").AsSpan().Trim(XmlConvert.WhitespaceChars))
                        {
                            case "default":
                                return XmlSpace.Default;
                            case "preserve":
                                return XmlSpace.Preserve;
                            default:
                                //should we throw exception if value is otherwise?
                                break;
                        }
                    }
 
                    node = node.ParentNode;
                } while (node != null);
 
                return XmlSpace.None;
            }
        }
 
        internal virtual string XmlLang
        {
            get
            {
                XmlNode? node = this;
                XmlElement? elem;
                do
                {
                    elem = node as XmlElement;
                    if (elem != null)
                    {
                        if (elem.HasAttribute("xml:lang"))
                            return elem.GetAttribute("xml:lang");
                    }
 
                    node = node.ParentNode;
                } while (node != null);
 
                return string.Empty;
            }
        }
 
        internal virtual XPathNodeType XPNodeType
        {
            get
            {
                return (XPathNodeType)(-1);
            }
        }
 
        internal virtual string XPLocalName
        {
            get
            {
                return string.Empty;
            }
        }
 
        internal virtual string GetXPAttribute(string localName, string namespaceURI)
        {
            return string.Empty;
        }
 
        internal virtual bool IsText
        {
            get
            {
                return false;
            }
        }
 
        public virtual XmlNode? PreviousText
        {
            get
            {
                return null;
            }
        }
 
        internal static void NestTextNodes(XmlNode prevNode, XmlNode nextNode)
        {
            Debug.Assert(prevNode.IsText);
            Debug.Assert(nextNode.IsText);
 
            nextNode.parentNode = prevNode;
        }
 
        internal static void UnnestTextNodes(XmlNode prevNode, XmlNode nextNode)
        {
            Debug.Assert(prevNode.IsText);
            Debug.Assert(nextNode.IsText);
 
            nextNode.parentNode = prevNode.ParentNode;
        }
 
        private object debuggerDisplayProxy { get { return new DebuggerDisplayXmlNodeProxy(this); } }
 
        [DebuggerDisplay("{ToString()}")]
        internal readonly struct DebuggerDisplayXmlNodeProxy
        {
            private readonly XmlNode _node;
 
            public DebuggerDisplayXmlNodeProxy(XmlNode node)
            {
                _node = node;
            }
 
            public override string ToString()
            {
                XmlNodeType nodeType = _node.NodeType;
                string result = nodeType.ToString();
                switch (nodeType)
                {
                    case XmlNodeType.Element:
                    case XmlNodeType.EntityReference:
                        result += $", Name=\"{_node.Name}\"";
                        break;
                    case XmlNodeType.Attribute:
                    case XmlNodeType.ProcessingInstruction:
                        result += $", Name=\"{_node.Name}\", Value=\"{XmlConvert.EscapeValueForDebuggerDisplay(_node.Value!)}\"";
                        break;
                    case XmlNodeType.Text:
                    case XmlNodeType.CDATA:
                    case XmlNodeType.Comment:
                    case XmlNodeType.Whitespace:
                    case XmlNodeType.SignificantWhitespace:
                    case XmlNodeType.XmlDeclaration:
                        result += $", Value=\"{XmlConvert.EscapeValueForDebuggerDisplay(_node.Value!)}\"";
                        break;
                    case XmlNodeType.DocumentType:
                        XmlDocumentType documentType = (XmlDocumentType)_node;
                        result += $", Name=\"{documentType.Name}\", SYSTEM=\"{documentType.SystemId}\", PUBLIC=\"{documentType.PublicId}\", Value=\"{XmlConvert.EscapeValueForDebuggerDisplay(documentType.InternalSubset!)}\"";
                        break;
                    default:
                        break;
                }
                return result;
            }
        }
    }
}