File: System\Xml\RegionIterator.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.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text;
 
#pragma warning disable 618 // ignore obsolete warning about XmlDataDocument
 
namespace System.Xml
{
    internal abstract class BaseRegionIterator : BaseTreeIterator
    {
        internal BaseRegionIterator(DataSetMapper mapper) : base(mapper) { }
    }
 
    // Iterates over non-attribute nodes
    internal sealed class RegionIterator : BaseRegionIterator
    {
        private readonly XmlBoundElement _rowElement;
        private XmlNode _currentNode;
 
        internal RegionIterator(XmlBoundElement rowElement) : base(((XmlDataDocument)(rowElement.OwnerDocument)).Mapper)
        {
            Debug.Assert(rowElement != null && rowElement.Row != null);
            _rowElement = rowElement;
            _currentNode = rowElement;
        }
 
        internal override XmlNode? CurrentNode => _currentNode;
 
        [MemberNotNullWhen(true, nameof(CurrentNode))]
        internal override bool Next()
        {
            XmlNode? nextNode;
            ElementState oldState = _rowElement.ElementState;
 
            // We do not want to cause any foliation w/ this iterator or use this iterator once the region was defoliated
            Debug.Assert(oldState != ElementState.None);
 
            // Try to move to the first child
            nextNode = _currentNode.FirstChild;
 
            // No children, try next sibling
            if (nextNode != null)
            {
                _currentNode = nextNode;
                Debug.Assert(CurrentNode != null);
                // If we have been defoliated, we should have stayed that way
                Debug.Assert((oldState == ElementState.Defoliated) ? (_rowElement.ElementState == ElementState.Defoliated) : true);
                // Rollback foliation
                _rowElement.ElementState = oldState;
                return true;
            }
 
            return NextRight();
        }
 
        [MemberNotNullWhen(true, nameof(CurrentNode))]
        internal override bool NextRight()
        {
            // Make sure we do not get past the rowElement if we call NextRight on a just initialized iterator and rowElement has no children
            if (_currentNode == _rowElement)
            {
                _currentNode = null!;
                return false;
            }
 
            ElementState oldState = _rowElement.ElementState;
 
            // We do not want to cause any foliation w/ this iterator or use this iterator once the region was defoliated
            Debug.Assert(oldState != ElementState.None);
 
            XmlNode? nextNode = _currentNode.NextSibling;
 
            if (nextNode != null)
            {
                _currentNode = nextNode;
                // If we have been defoliated, we should have stayed that way
                Debug.Assert((oldState == ElementState.Defoliated) ? (_rowElement.ElementState == ElementState.Defoliated) : true);
                // Rollback foliation
                _rowElement.ElementState = oldState;
                Debug.Assert(CurrentNode != null);
                return true;
            }
 
            // No next sibling, try the first sibling of from the parent chain
            nextNode = _currentNode;
            while (nextNode != _rowElement && nextNode!.NextSibling == null)
            {
                nextNode = nextNode.ParentNode;
            }
 
            if (nextNode == _rowElement)
            {
                _currentNode = null!;
                // If we have been defoliated, we should have stayed that way
                Debug.Assert((oldState == ElementState.Defoliated) ? (_rowElement.ElementState == ElementState.Defoliated) : true);
 
                // Rollback foliation
                _rowElement.ElementState = oldState;
                return false;
            }
 
            Debug.Assert(nextNode.NextSibling != null);
            _currentNode = nextNode.NextSibling;
 
            // If we have been defoliated, we should have stayed that way
            Debug.Assert((oldState == ElementState.Defoliated) ? (_rowElement.ElementState == ElementState.Defoliated) : true);
 
            // Rollback foliation
            _rowElement.ElementState = oldState;
            Debug.Assert(CurrentNode != null);
            return true;
        }
 
        // Get the initial text value for the current node. You should be positioned on the node (element) for
        // which to get the initial text value, not on the text node.
        [MemberNotNullWhen(true, nameof(CurrentNode))]
        internal bool NextInitialTextLikeNodes(out string value)
        {
            Debug.Assert(CurrentNode != null);
            Debug.Assert(CurrentNode.NodeType == XmlNodeType.Element);
#if DEBUG
            // It's not OK to try to read the initial text value for sub-regions, because we do not know how to revert their initial state
            if (CurrentNode.NodeType == XmlNodeType.Element && DataSetMapper.GetTableSchemaForElement((XmlElement)(CurrentNode)) != null)
            {
                if (CurrentNode != _rowElement)
                {
                    Debug.Fail("Reading the initial text value for sub-regions.");
                }
            }
#endif
 
            ElementState oldState = _rowElement.ElementState;
 
            // We do not want to cause any foliation w/ this iterator or use this iterator once the region was defoliated
            Debug.Assert(oldState != ElementState.None);
 
            XmlNode? n = CurrentNode.FirstChild;
            value = GetInitialTextFromNodes(ref n);
            if (n == null)
            {
                // If we have been defoliated, we should have stayed that way
                Debug.Assert((oldState == ElementState.Defoliated) ? (_rowElement.ElementState == ElementState.Defoliated) : true);
 
                // Rollback eventual foliation
                _rowElement.ElementState = oldState;
                return NextRight();
            }
            Debug.Assert(!XmlDataDocument.Helpers.IsTextLikeNode(n));
            _currentNode = n;
 
            // If we have been defoliated, we should have stayed that way
            Debug.Assert((oldState == ElementState.Defoliated) ? (_rowElement.ElementState == ElementState.Defoliated) : true);
 
            // Rollback eventual foliation
            _rowElement.ElementState = oldState;
            return true;
        }
 
        private static string GetInitialTextFromNodes(ref XmlNode? n)
        {
            string? value = null;
 
            if (n != null)
            {
                // don't consider whitespace
                while (n.NodeType == XmlNodeType.Whitespace)
                {
                    n = n.NextSibling;
                    if (n == null)
                    {
                        return string.Empty;
                    }
                }
 
                if (XmlDataDocument.Helpers.IsTextLikeNode(n) && (n.NextSibling == null || !XmlDataDocument.Helpers.IsTextLikeNode(n.NextSibling)))
                {
                    // don't use string builder if only one text node exists
                    value = n.Value;
                    n = n.NextSibling;
                }
                else
                {
                    StringBuilder sb = new StringBuilder();
                    while (n != null && XmlDataDocument.Helpers.IsTextLikeNode(n))
                    {
                        // Ignore non-significant whitespace nodes
                        if (n.NodeType != XmlNodeType.Whitespace)
                        {
                            sb.Append(n.Value);
                        }
                        n = n.NextSibling;
                    }
                    value = sb.ToString();
                }
            }
 
            return value ?? string.Empty;
        }
    }
}