File: System\Xml\XmlBoundElement.cs
Web Access
Project: src\src\libraries\System.Data.Common\src\System.Data.Common.csproj (System.Data.Common)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Data;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
 
#pragma warning disable 618 // ignore obsolete warning about XmlDataDocument
 
namespace System.Xml
{
    internal enum ElementState
    {
        None,
        Defoliated,
        WeakFoliation,
        StrongFoliation,
        Foliating,
        Defoliating,
    }
 
    [RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
    [RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
    internal sealed class XmlBoundElement : XmlElement
    {
        private DataRow? _row;
        private ElementState _state;
 
        internal XmlBoundElement(string prefix, string localName, string namespaceURI, XmlDocument doc) : base(prefix, localName, namespaceURI, doc)
        {
            _state = ElementState.None;
        }
 
        public override XmlAttributeCollection Attributes
        {
            get
            {
                AutoFoliate();
                return base.Attributes;
            }
        }
 
        public override bool HasAttributes => Attributes.Count > 0;
 
        public override XmlNode? FirstChild
        {
            get
            {
                AutoFoliate();
                return base.FirstChild;
            }
        }
 
        internal XmlNode? SafeFirstChild => base.FirstChild;
 
        public override XmlNode? LastChild
        {
            get
            {
                AutoFoliate();
                return base.LastChild;
            }
        }
 
        public override XmlNode? PreviousSibling
        {
            get
            {
                XmlNode? prev = base.PreviousSibling;
                if (prev == null)
                {
                    XmlBoundElement? parent = ParentNode as XmlBoundElement;
                    if (parent != null)
                    {
                        parent.AutoFoliate();
                        return base.PreviousSibling;
                    }
                }
                return prev;
            }
        }
 
        internal XmlNode? SafePreviousSibling => base.PreviousSibling;
 
        public override XmlNode? NextSibling
        {
            get
            {
                XmlNode? next = base.NextSibling;
                if (next == null)
                {
                    XmlBoundElement? parent = ParentNode as XmlBoundElement;
                    if (parent != null)
                    {
                        parent.AutoFoliate();
                        return base.NextSibling;
                    }
                }
                return next;
            }
        }
 
        internal XmlNode? SafeNextSibling => base.NextSibling;
 
        public override bool HasChildNodes
        {
            get
            {
                AutoFoliate();
                return base.HasChildNodes;
            }
        }
 
        public override XmlNode? InsertBefore(XmlNode newChild, XmlNode? refChild)
        {
            AutoFoliate();
            return base.InsertBefore(newChild, refChild);
        }
 
        public override XmlNode? InsertAfter(XmlNode newChild, XmlNode? refChild)
        {
            AutoFoliate();
            return base.InsertAfter(newChild, refChild);
        }
 
        public override XmlNode ReplaceChild(XmlNode newChild, XmlNode oldChild)
        {
            AutoFoliate();
            return base.ReplaceChild(newChild, oldChild);
        }
 
        public override XmlNode? AppendChild(XmlNode newChild)
        {
            AutoFoliate();
            return base.AppendChild(newChild);
        }
 
        internal void RemoveAllChildren()
        {
            XmlNode? child = FirstChild;
            XmlNode? sibling;
 
            while (child != null)
            {
                sibling = child.NextSibling;
                RemoveChild(child);
                child = sibling;
            }
        }
 
        public override string InnerXml
        {
            get { return base.InnerXml; }
            set
            {
                RemoveAllChildren();
 
                XmlDataDocument doc = (XmlDataDocument)OwnerDocument;
 
                bool bOrigIgnoreXmlEvents = doc.IgnoreXmlEvents;
                bool bOrigIgnoreDataSetEvents = doc.IgnoreDataSetEvents;
 
                doc.IgnoreXmlEvents = true;
                doc.IgnoreDataSetEvents = true;
 
                base.InnerXml = value;
 
                doc.SyncTree(this);
 
                doc.IgnoreDataSetEvents = bOrigIgnoreDataSetEvents;
                doc.IgnoreXmlEvents = bOrigIgnoreXmlEvents;
            }
        }
 
        internal DataRow? Row
        {
            get { return _row; }
            set { _row = value; }
        }
 
        internal bool IsFoliated
        {
            get
            {
                while (_state == ElementState.Foliating || _state == ElementState.Defoliating)
                {
                    Thread.Sleep(0);
                }
 
                //has to be sure that we are either foliated or defoliated when ask for IsFoliated.
                return _state != ElementState.Defoliated;
            }
        }
 
        internal ElementState ElementState
        {
            get { return _state; }
            set { _state = value; }
        }
 
        internal void Foliate(ElementState newState)
        {
            XmlDataDocument doc = (XmlDataDocument)OwnerDocument;
            doc?.Foliate(this, newState);
        }
 
        // Foliate the node as a side effect of user calling functions on this node (like NextSibling) OR as a side effect of DataDocNav using nodes to do editing
        private void AutoFoliate()
        {
            XmlDataDocument doc = (XmlDataDocument)OwnerDocument;
            doc?.Foliate(this, doc.AutoFoliationState);
        }
 
        public override XmlNode CloneNode(bool deep)
        {
            XmlDataDocument doc = (XmlDataDocument)(OwnerDocument);
            ElementState oldAutoFoliationState = doc.AutoFoliationState;
            doc.AutoFoliationState = ElementState.WeakFoliation;
            XmlElement element;
            try
            {
                Foliate(ElementState.WeakFoliation);
                element = (XmlElement)(base.CloneNode(deep));
 
                // Clone should create a XmlBoundElement node
                Debug.Assert(element is XmlBoundElement);
            }
            finally
            {
                doc.AutoFoliationState = oldAutoFoliationState;
            }
 
            return element;
        }
 
        public override void WriteContentTo(XmlWriter w)
        {
            DataPointer dp = new DataPointer((XmlDataDocument)OwnerDocument, this);
            try
            {
                dp.AddPointer();
                WriteBoundElementContentTo(dp, w);
            }
            finally
            {
                dp.SetNoLongerUse();
            }
        }
 
        public override void WriteTo(XmlWriter w)
        {
            DataPointer dp = new DataPointer((XmlDataDocument)OwnerDocument, this);
            try
            {
                dp.AddPointer();
                WriteRootBoundElementTo(dp, w);
            }
            finally
            {
                dp.SetNoLongerUse();
            }
        }
 
        private void WriteRootBoundElementTo(DataPointer dp, XmlWriter w)
        {
            Debug.Assert(dp.NodeType == XmlNodeType.Element);
            XmlDataDocument doc = (XmlDataDocument)OwnerDocument;
            w.WriteStartElement(dp.Prefix, dp.LocalName, dp.NamespaceURI);
            int cAttr = dp.AttributeCount;
            bool bHasXSI = false;
            if (cAttr > 0)
            {
                for (int iAttr = 0; iAttr < cAttr; iAttr++)
                {
                    dp.MoveToAttribute(iAttr);
                    if (dp.Prefix == "xmlns" && dp.LocalName == XmlDataDocument.XSI)
                    {
                        bHasXSI = true;
                    }
 
                    WriteTo(dp, w);
                    dp.MoveToOwnerElement();
                }
            }
 
            if (!bHasXSI && doc._bLoadFromDataSet && doc._bHasXSINIL)
            {
                w.WriteAttributeString("xmlns", "xsi", "http://www.w3.org/2000/xmlns/", Keywords.XSINS);
            }
 
            WriteBoundElementContentTo(dp, w);
 
            // Force long end tag when the elem is not empty, even if there are no children.
            if (dp.IsEmptyElement)
            {
                w.WriteEndElement();
            }
            else
            {
                w.WriteFullEndElement();
            }
        }
 
        private static void WriteBoundElementTo(DataPointer dp, XmlWriter w)
        {
            Debug.Assert(dp.NodeType == XmlNodeType.Element);
            w.WriteStartElement(dp.Prefix, dp.LocalName, dp.NamespaceURI);
            int cAttr = dp.AttributeCount;
            if (cAttr > 0)
            {
                for (int iAttr = 0; iAttr < cAttr; iAttr++)
                {
                    dp.MoveToAttribute(iAttr);
                    WriteTo(dp, w);
                    dp.MoveToOwnerElement();
                }
            }
 
            WriteBoundElementContentTo(dp, w);
 
            // Force long end tag when the elem is not empty, even if there are no children.
            if (dp.IsEmptyElement)
            {
                w.WriteEndElement();
            }
            else
            {
                w.WriteFullEndElement();
            }
        }
 
        private static void WriteBoundElementContentTo(DataPointer dp, XmlWriter w)
        {
            if (!dp.IsEmptyElement && dp.MoveToFirstChild())
            {
                do
                {
                    WriteTo(dp, w);
                }
                while (dp.MoveToNextSibling());
 
                dp.MoveToParent();
            }
        }
 
        private static void WriteTo(DataPointer dp, XmlWriter w)
        {
            switch (dp.NodeType)
            {
                case XmlNodeType.Attribute:
                    if (!dp.IsDefault)
                    {
                        w.WriteStartAttribute(dp.Prefix, dp.LocalName, dp.NamespaceURI);
 
                        if (dp.MoveToFirstChild())
                        {
                            do
                            {
                                WriteTo(dp, w);
                            }
                            while (dp.MoveToNextSibling());
 
                            dp.MoveToParent();
                        }
 
                        w.WriteEndAttribute();
                    }
                    break;
 
                case XmlNodeType.Element:
                    WriteBoundElementTo(dp, w);
                    break;
 
                case XmlNodeType.Text:
                    w.WriteString(dp.Value);
                    break;
 
                default:
                    Debug.Assert(((IXmlDataVirtualNode)dp).IsOnColumn(null));
                    dp.GetNode()?.WriteTo(w);
                    break;
            }
        }
 
        public override XmlNodeList GetElementsByTagName(string name)
        {
            // Retrieving nodes from the returned nodelist may cause foliation which causes new nodes to be created,
            // so the System.Xml iterator will throw if this happens during iteration. To avoid this, foliate everything
            // before iteration, so iteration will not cause foliation (and as a result of this, creation of new nodes).
            XmlNodeList tempNodeList = base.GetElementsByTagName(name);
 
            _ = tempNodeList.Count;
            return tempNodeList;
        }
    }
}