File: System\Xml\DataPointer.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;
 
#pragma warning disable 0618 // ignore obsolete warning about XmlDataDocument
 
namespace System.Xml
{
    internal sealed class DataPointer : IXmlDataVirtualNode
    {
        private XmlDataDocument _doc;
        private XmlNode _node;
        private DataColumn? _column;
        private bool _fOnValue;
        private bool _bNeedFoliate;
        private bool _isInUse;
 
        internal DataPointer(XmlDataDocument doc, XmlNode node)
        {
            _doc = doc;
            _node = node;
            _column = null;
            _fOnValue = false;
            _bNeedFoliate = false;
            _isInUse = true;
            AssertValid();
        }
 
        internal DataPointer(DataPointer pointer)
        {
            _doc = pointer._doc;
            _node = pointer._node;
            _column = pointer._column;
            _fOnValue = pointer._fOnValue;
            _bNeedFoliate = false;
            _isInUse = true;
            AssertValid();
        }
 
        internal void AddPointer() => _doc.AddPointer(this);
 
        // Returns the row element of the region that the pointer points into
        private XmlBoundElement? GetRowElement()
        {
            XmlBoundElement? rowElem;
            if (_column != null)
            {
                rowElem = _node as XmlBoundElement;
                Debug.Assert(rowElem != null);
                Debug.Assert(rowElem.Row != null);
                return rowElem;
            }
 
            DataSetMapper.GetRegion(_node, out rowElem);
            return rowElem;
        }
 
        private DataRow? Row
        {
            get
            {
                XmlBoundElement? rowElem = GetRowElement();
                if (rowElem == null)
                {
                    return null;
                }
 
                Debug.Assert(rowElem.Row != null);
                return rowElem.Row;
            }
        }
 
        private static bool IsFoliated(XmlNode node) =>
            node != null && node is XmlBoundElement ?
                ((XmlBoundElement)node).IsFoliated : true;
 
        internal void MoveTo(DataPointer pointer)
        {
            AssertValid();
 
            // You should not move outside of this document
            Debug.Assert(_node == _doc || _node.OwnerDocument == _doc);
 
            _doc = pointer._doc;
            _node = pointer._node;
            _column = pointer._column;
            _fOnValue = pointer._fOnValue;
            AssertValid();
        }
        private void MoveTo(XmlNode node)
        {
            // You should not move outside of this document
            Debug.Assert(node == _doc || node.OwnerDocument == _doc);
 
            _node = node;
            _column = null;
            _fOnValue = false;
            AssertValid();
        }
 
        private void MoveTo(XmlNode node, DataColumn? column, bool fOnValue)
        {
            // You should not move outside of this document
            Debug.Assert(node == _doc || node.OwnerDocument == _doc);
 
            _node = node;
            _column = column;
            _fOnValue = fOnValue;
            AssertValid();
        }
 
        private DataColumn? NextColumn(DataRow row, DataColumn? col, bool fAttribute, bool fNulls)
        {
            if (row.RowState == DataRowState.Deleted)
            {
                return null;
            }
 
            DataTable table = row.Table;
            DataColumnCollection columns = table.Columns;
            int iColumn = (col != null) ? col.Ordinal + 1 : 0;
            int cColumns = columns.Count;
            DataRowVersion rowVersion = (row.RowState == DataRowState.Detached) ? DataRowVersion.Proposed : DataRowVersion.Current;
 
            for (; iColumn < cColumns; iColumn++)
            {
                DataColumn c = columns[iColumn];
                if (!_doc.IsNotMapped(c) && (c.ColumnMapping == MappingType.Attribute) == fAttribute && (fNulls || !Convert.IsDBNull(row[c, rowVersion])))
                {
                    return c;
                }
            }
 
            return null;
        }
 
        private DataColumn? NthColumn(DataRow row, bool fAttribute, int iColumn, bool fNulls)
        {
            DataColumn? c = null;
            while ((c = NextColumn(row, c, fAttribute, fNulls)) != null)
            {
                if (iColumn == 0)
                {
                    return c;
                }
 
                iColumn = checked((iColumn - 1));
            }
            return null;
        }
 
        private int ColumnCount(DataRow row, bool fAttribute, bool fNulls)
        {
            DataColumn? c = null;
            int count = 0;
            while ((c = NextColumn(row, c, fAttribute, fNulls)) != null)
            {
                count++;
            }
            return count;
        }
 
        internal bool MoveToFirstChild()
        {
            RealFoliate();
            AssertValid();
            if (_node == null)
            {
                return false;
            }
 
            if (_column != null)
            {
                if (_fOnValue)
                {
                    return false;
                }
 
                _fOnValue = true;
                return true;
            }
            else if (!IsFoliated(_node))
            {
                // find virtual column elements first
                DataColumn? c = NextColumn(Row!, null, false, false);
                if (c != null)
                {
                    MoveTo(_node, c, _doc.IsTextOnly(c));
                    return true;
                }
            }
 
            // look for anything
            XmlNode? n = _doc.SafeFirstChild(_node);
            if (n != null)
            {
                MoveTo(n);
                return true;
            }
 
            return false;
        }
 
        internal bool MoveToNextSibling()
        {
            RealFoliate();
            AssertValid();
            if (_node != null)
            {
                if (_column != null)
                {
                    if (_fOnValue && !_doc.IsTextOnly(_column))
                    {
                        return false;
                    }
 
                    DataColumn? c = NextColumn(Row!, _column, false, false);
                    if (c != null)
                    {
                        MoveTo(_node, c, false);
                        return true;
                    }
 
                    XmlNode? n = _doc.SafeFirstChild(_node);
                    if (n != null)
                    {
                        MoveTo(n);
                        return true;
                    }
                }
                else
                {
                    XmlNode? n = _doc.SafeNextSibling(_node);
                    if (n != null)
                    {
                        MoveTo(n);
                        return true;
                    }
                }
            }
 
            return false;
        }
 
        internal bool MoveToParent()
        {
            RealFoliate();
            AssertValid();
            if (_node != null)
            {
                if (_column != null)
                {
                    if (_fOnValue && !_doc.IsTextOnly(_column))
                    {
                        MoveTo(_node, _column, false);
                        return true;
                    }
 
                    if (_column.ColumnMapping != MappingType.Attribute)
                    {
                        MoveTo(_node, null, false);
                        return true;
                    }
                }
                else
                {
                    XmlNode? n = _node.ParentNode;
                    if (n != null)
                    {
                        MoveTo(n);
                        return true;
                    }
                }
            }
            return false;
        }
 
        internal bool MoveToOwnerElement()
        {
            RealFoliate();
            AssertValid();
            if (_node != null)
            {
                if (_column != null)
                {
                    if (_fOnValue || _doc.IsTextOnly(_column) || _column.ColumnMapping != MappingType.Attribute)
                    {
                        return false;
                    }
 
                    MoveTo(_node, null, false);
                    return true;
                }
                else if (_node.NodeType == XmlNodeType.Attribute)
                {
                    XmlNode? n = ((XmlAttribute)_node).OwnerElement;
                    if (n != null)
                    {
                        MoveTo(n, null, false);
                        return true;
                    }
                }
            }
 
            return false;
        }
 
 
        internal int AttributeCount
        {
            get
            {
                RealFoliate();
                AssertValid();
                if (_node != null)
                {
                    if (_column == null && _node.NodeType == XmlNodeType.Element)
                    {
                        if (!IsFoliated(_node))
                        {
                            return ColumnCount(Row!, true, false);
                        }
                        else
                        {
                            return _node.Attributes!.Count;
                        }
                    }
                }
                return 0;
            }
        }
 
        internal bool MoveToAttribute(int i)
        {
            RealFoliate();
            AssertValid();
            if (i < 0)
            {
                return false;
            }
 
            if (_node != null)
            {
                if ((_column == null || _column.ColumnMapping == MappingType.Attribute) && _node.NodeType == XmlNodeType.Element)
                {
                    if (!IsFoliated(_node))
                    {
                        DataColumn? c = NthColumn(Row!, true, i, false);
                        if (c != null)
                        {
                            MoveTo(_node, c, false);
                            return true;
                        }
                    }
                    else
                    {
                        XmlNode? n = _node.Attributes!.Item(i);
                        if (n != null)
                        {
                            MoveTo(n, null, false);
                            return true;
                        }
                    }
                }
            }
            return false;
        }
 
        internal XmlNodeType NodeType
        {
            get
            {
                RealFoliate();
                AssertValid();
                if (_node == null)
                {
                    return XmlNodeType.None;
                }
                else if (_column == null)
                {
                    return _node.NodeType;
                }
                else if (_fOnValue)
                {
                    return XmlNodeType.Text;
                }
                else if (_column.ColumnMapping == MappingType.Attribute)
                {
                    return XmlNodeType.Attribute;
                }
                else
                {
                    return XmlNodeType.Element;
                }
            }
        }
 
        internal string LocalName
        {
            get
            {
                RealFoliate();
                AssertValid();
                if (_node == null)
                {
                    return string.Empty;
                }
                else if (_column == null)
                {
                    string name = _node.LocalName;
                    Debug.Assert(name != null);
                    if (IsLocalNameEmpty(_node.NodeType))
                    {
                        return string.Empty;
                    }
 
                    return name;
                }
                else if (_fOnValue)
                {
                    return string.Empty;
                }
                else
                {
                    return _doc.NameTable.Add(_column.EncodedColumnName);
                }
            }
        }
 
        internal string NamespaceURI
        {
            get
            {
                RealFoliate();
                AssertValid();
                if (_node == null)
                {
                    return string.Empty;
                }
                else if (_column == null)
                {
                    return _node.NamespaceURI;
                }
                else if (_fOnValue)
                {
                    return string.Empty;
                }
                else
                {
                    return _doc.NameTable.Add(_column.Namespace);
                }
            }
        }
 
        internal string Name
        {
            get
            {
                RealFoliate();
                AssertValid();
                if (_node == null)
                {
                    return string.Empty;
                }
                else if (_column == null)
                {
                    string name = _node.Name;
                    //Again it could be String.Empty at null position
                    Debug.Assert(name != null);
                    if (IsLocalNameEmpty(_node.NodeType))
                    {
                        return string.Empty;
                    }
                    return name;
                }
                else
                {
                    string prefix = Prefix;
                    string lname = LocalName;
                    if (prefix != null && prefix.Length > 0)
                    {
                        if (lname != null && lname.Length > 0)
                        {
                            return _doc.NameTable.Add(prefix + ":" + lname);
                        }
                        else
                        {
                            return prefix;
                        }
                    }
                    else
                    {
                        return lname;
                    }
                }
            }
        }
 
        private static bool IsLocalNameEmpty(XmlNodeType nt)
        {
            switch (nt)
            {
                case XmlNodeType.None:
                case XmlNodeType.Text:
                case XmlNodeType.CDATA:
                case XmlNodeType.Comment:
                case XmlNodeType.Document:
                case XmlNodeType.DocumentFragment:
                case XmlNodeType.Whitespace:
                case XmlNodeType.SignificantWhitespace:
                case XmlNodeType.EndElement:
                case XmlNodeType.EndEntity:
                    return true;
                case XmlNodeType.Element:
                case XmlNodeType.Attribute:
                case XmlNodeType.EntityReference:
                case XmlNodeType.Entity:
                case XmlNodeType.ProcessingInstruction:
                case XmlNodeType.DocumentType:
                case XmlNodeType.Notation:
                case XmlNodeType.XmlDeclaration:
                    return false;
                default:
                    return true;
            }
        }
 
        internal string Prefix
        {
            get
            {
                RealFoliate();
                AssertValid();
                if (_node == null)
                {
                    return string.Empty;
                }
                else if (_column == null)
                {
                    return _node.Prefix;
                }
                else
                {
                    return string.Empty;
                }
            }
        }
 
        internal string? Value
        {
            [RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
            [RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
            get
            {
                RealFoliate();
                AssertValid();
                if (_node == null)
                {
                    return null;
                }
                else if (_column == null)
                {
                    return _node.Value;
                }
                else if (_column.ColumnMapping == MappingType.Attribute || _fOnValue)
                {
                    DataRow row = Row!;
                    DataRowVersion rowVersion = (row.RowState == DataRowState.Detached) ? DataRowVersion.Proposed : DataRowVersion.Current;
                    object value = row[_column, rowVersion];
                    if (!Convert.IsDBNull(value))
                    {
                        return _column.ConvertObjectToXml(value);
                    }
                    return null;
                }
                else
                {
                    // column element has no value
                    return null;
                }
            }
        }
 
        bool IXmlDataVirtualNode.IsOnNode(XmlNode nodeToCheck)
        {
            RealFoliate();
            return nodeToCheck == _node;
        }
 
        bool IXmlDataVirtualNode.IsOnColumn(DataColumn? col)
        {
            RealFoliate();
            return col == _column;
        }
 
        internal XmlNode GetNode() => _node;
 
        internal bool IsEmptyElement
        {
            get
            {
                RealFoliate();
                AssertValid();
                if (_node != null && _column == null)
                {
                    if (_node.NodeType == XmlNodeType.Element)
                    {
                        return ((XmlElement)_node).IsEmpty;
                    }
                }
                return false;
            }
        }
 
        internal bool IsDefault
        {
            get
            {
                RealFoliate();
                AssertValid();
                if (_node != null && _column == null && _node.NodeType == XmlNodeType.Attribute)
                {
                    return !((XmlAttribute)_node).Specified;
                }
 
                return false;
            }
        }
 
        void IXmlDataVirtualNode.OnFoliated(XmlNode foliatedNode)
        {
            // update the pointer if the element node has been foliated
            if (_node == foliatedNode)
            {
                // if already on this node, nothing to do!
                if (_column == null)
                {
                    return;
                }
                _bNeedFoliate = true;
            }
        }
 
        internal void RealFoliate()
        {
            if (!_bNeedFoliate)
            {
                return;
            }
 
            XmlNode? n;
 
            if (_doc.IsTextOnly(_column!))
            {
                n = _node.FirstChild;
            }
            else
            {
                if (_column!.ColumnMapping == MappingType.Attribute)
                {
                    n = _node.Attributes!.GetNamedItem(_column.EncodedColumnName, _column.Namespace);
                }
                else
                {
                    for (n = _node.FirstChild; n != null; n = n.NextSibling)
                    {
                        if (n.LocalName == _column.EncodedColumnName && n.NamespaceURI == _column.Namespace)
                            break;
                    }
                }
 
                if (n != null && _fOnValue)
                {
                    n = n.FirstChild;
                }
            }
 
            if (n == null)
            {
                throw new InvalidOperationException(SR.DataDom_Foliation);
            }
 
            // Cannot use MoveTo( n ); b/c the initial state for MoveTo is invalid (region is foliated but this is not)
            _node = n;
            _column = null;
            _fOnValue = false;
            AssertValid();
 
            _bNeedFoliate = false;
        }
 
        //for the 6 properties below, only when the this.column == null that the nodetype could be XmlDeclaration node
        internal string? PublicId
        {
            get
            {
                XmlNodeType nt = NodeType;
                switch (nt)
                {
                    case XmlNodeType.DocumentType:
                        {
                            Debug.Assert(_column == null);
                            return ((XmlDocumentType)(_node)).PublicId;
                        }
                    case XmlNodeType.Entity:
                        {
                            Debug.Assert(_column == null);
                            return ((XmlEntity)(_node)).PublicId;
                        }
                    case XmlNodeType.Notation:
                        {
                            Debug.Assert(_column == null);
                            return ((XmlNotation)(_node)).PublicId;
                        }
                }
                return null;
            }
        }
 
        internal string? SystemId
        {
            get
            {
                XmlNodeType nt = NodeType;
                switch (nt)
                {
                    case XmlNodeType.DocumentType:
                        {
                            Debug.Assert(_column == null);
                            return ((XmlDocumentType)(_node)).SystemId;
                        }
                    case XmlNodeType.Entity:
                        {
                            Debug.Assert(_column == null);
                            return ((XmlEntity)(_node)).SystemId;
                        }
                    case XmlNodeType.Notation:
                        {
                            Debug.Assert(_column == null);
                            return ((XmlNotation)(_node)).SystemId;
                        }
                }
                return null;
            }
        }
 
        internal string? InternalSubset
        {
            get
            {
                if (NodeType == XmlNodeType.DocumentType)
                {
                    Debug.Assert(_column == null);
                    return ((XmlDocumentType)(_node)).InternalSubset;
                }
                return null;
            }
        }
 
        internal XmlDeclaration? Declaration
        {
            get
            {
                XmlNode? child = _doc.SafeFirstChild(_doc);
                if (child != null && child.NodeType == XmlNodeType.XmlDeclaration)
                    return (XmlDeclaration)child;
                return null;
            }
        }
 
        internal string? Encoding
        {
            get
            {
                if (NodeType == XmlNodeType.XmlDeclaration)
                {
                    Debug.Assert(_column == null);
                    return ((XmlDeclaration)(_node)).Encoding;
                }
                else if (NodeType == XmlNodeType.Document)
                {
                    XmlDeclaration? dec = Declaration;
                    if (dec != null)
                    {
                        return dec.Encoding;
                    }
                }
                return null;
            }
        }
 
        internal string? Standalone
        {
            get
            {
                if (NodeType == XmlNodeType.XmlDeclaration)
                {
                    Debug.Assert(_column == null);
                    return ((XmlDeclaration)(_node)).Standalone;
                }
                else if (NodeType == XmlNodeType.Document)
                {
                    XmlDeclaration? dec = Declaration;
                    if (dec != null)
                    {
                        return dec.Standalone;
                    }
                }
                return null;
            }
        }
 
        internal string? Version
        {
            get
            {
                if (NodeType == XmlNodeType.XmlDeclaration)
                {
                    Debug.Assert(_column == null);
                    return ((XmlDeclaration)(_node)).Version;
                }
                else if (NodeType == XmlNodeType.Document)
                {
                    XmlDeclaration? dec = Declaration;
                    if (dec != null)
                        return dec.Version;
                }
                return null;
            }
        }
 
        [Conditional("DEBUG")]
        private void AssertValid()
        {
            // This pointer must be int the document list
            if (_column != null)
            {
                // We must be on a de-foliated region
                XmlBoundElement? rowElem = _node as XmlBoundElement;
                Debug.Assert(rowElem != null);
 
                DataRow? row = rowElem.Row;
                Debug.Assert(row != null);
 
                ElementState state = rowElem.ElementState;
                Debug.Assert(state == ElementState.Defoliated, "Region is accessed using column, but it's state is FOLIATED");
 
                // We cannot be on a column for which the value is DBNull
                DataRowVersion rowVersion = (row.RowState == DataRowState.Detached) ? DataRowVersion.Proposed : DataRowVersion.Current;
                Debug.Assert(!Convert.IsDBNull(row[_column, rowVersion]));
 
                // If we are on the Text column, we should always have fOnValue == true
                Debug.Assert((_column.ColumnMapping == MappingType.SimpleContent) ? _fOnValue : true);
            }
        }
 
        bool IXmlDataVirtualNode.IsInUse() => _isInUse;
 
        internal void SetNoLongerUse()
        {
            _node = null!;
            _column = null;
            _fOnValue = false;
            _bNeedFoliate = false;
            _isInUse = false;
        }
    }
}