File: System\Xml\Core\XmlTextReaderImplHelpers.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.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Runtime.Versioning;
using System.Security;
using System.Text;
using System.Xml.Schema;
 
namespace System.Xml
{
    internal sealed partial class XmlTextReaderImpl
    {
        //
        // ParsingState
        //
        // Parsing state (aka. scanner data) - holds parsing buffer and entity input data information
        private struct ParsingState
        {
            // character buffer
            internal char[] chars;
            internal int charPos;
            internal int charsUsed;
            internal Encoding? encoding;
            internal bool appendMode;
 
            // input stream & byte buffer
            internal Stream? stream;
            internal Decoder? decoder;
            internal byte[]? bytes;
            internal int bytePos;
            internal int bytesUsed;
 
            // input text reader
            internal TextReader? textReader;
 
            // current line number & position
            internal int lineNo;
            internal int lineStartPos;
 
            // base uri of the current entity
            internal string baseUriStr;
            internal Uri? baseUri;
 
            // eof flag of the entity
            internal bool isEof;
            internal bool isStreamEof;
 
            // entity type & id
            internal IDtdEntityInfo? entity;
            internal int entityId;
 
            // normalization
            internal bool eolNormalized;
 
            // EndEntity reporting
            internal bool entityResolvedManually;
 
            internal void Clear()
            {
                chars = null!;
                charPos = 0;
                charsUsed = 0;
                encoding = null;
                stream = null;
                decoder = null;
                bytes = null;
                bytePos = 0;
                bytesUsed = 0;
                textReader = null;
                lineNo = 1;
                lineStartPos = -1;
                baseUriStr = string.Empty;
                baseUri = null;
                isEof = false;
                isStreamEof = false;
                eolNormalized = true;
                entityResolvedManually = false;
            }
 
            internal void Close(bool closeInput)
            {
                if (closeInput)
                {
                    if (stream != null)
                    {
                        stream.Dispose();
                    }
                    else
                    {
                        textReader?.Dispose();
                    }
                }
            }
 
            internal int LineNo
            {
                get
                {
                    return lineNo;
                }
            }
 
            internal int LinePos
            {
                get
                {
                    return charPos - lineStartPos;
                }
            }
        }
 
        //
        // XmlContext
        //
        private sealed class XmlContext
        {
            internal XmlSpace xmlSpace;
            internal string xmlLang;
            internal string defaultNamespace;
            internal XmlContext? previousContext;
 
            internal XmlContext()
            {
                xmlSpace = XmlSpace.None;
                xmlLang = string.Empty;
                defaultNamespace = string.Empty;
                previousContext = null;
            }
 
            internal XmlContext(XmlContext previousContext)
            {
                this.xmlSpace = previousContext.xmlSpace;
                this.xmlLang = previousContext.xmlLang;
                this.defaultNamespace = previousContext.defaultNamespace;
                this.previousContext = previousContext;
            }
        }
 
        //
        // NoNamespaceManager
        //
        private sealed class NoNamespaceManager : XmlNamespaceManager
        {
            public NoNamespaceManager() : base() { }
            public override string DefaultNamespace { get { return string.Empty; } }
            public override void PushScope() { }
            public override bool PopScope() { return false; }
            public override void AddNamespace(string prefix, string uri) { }
            public override void RemoveNamespace(string prefix, string uri) { }
            public override IEnumerator GetEnumerator() { return null!; }
            public override IDictionary<string, string> GetNamespacesInScope(XmlNamespaceScope scope) { return null!; }
            public override string LookupNamespace(string? prefix) { return string.Empty; }
            public override string? LookupPrefix(string uri) { return null; }
            public override bool HasNamespace(string prefix) { return false; }
        }
 
        //
        // DtdParserProxy: IDtdParserAdapter proxy for XmlTextReaderImpl
        //
        internal sealed partial class DtdParserProxy : IDtdParserAdapterV1
        {
            // Fields
            private readonly XmlTextReaderImpl _reader;
 
            // Constructors
            internal DtdParserProxy(XmlTextReaderImpl reader)
            {
                _reader = reader;
            }
 
            // IDtdParserAdapter proxies
            XmlNameTable IDtdParserAdapter.NameTable
            {
                get { return _reader.DtdParserProxy_NameTable; }
            }
 
            IXmlNamespaceResolver? IDtdParserAdapter.NamespaceResolver
            {
                get { return _reader.DtdParserProxy_NamespaceResolver; }
            }
 
            Uri? IDtdParserAdapter.BaseUri
            {
                // SxS: DtdParserProxy_BaseUri property on the reader may expose machine scope resources. This property
                // is just returning the value of the other property, so it may expose machine scope resource as well.
                get { return _reader.DtdParserProxy_BaseUri; }
            }
 
            bool IDtdParserAdapter.IsEof
            {
                get { return _reader.DtdParserProxy_IsEof; }
            }
 
            char[] IDtdParserAdapter.ParsingBuffer
            {
                get { return _reader.DtdParserProxy_ParsingBuffer; }
            }
 
            int IDtdParserAdapter.ParsingBufferLength
            {
                get { return _reader.DtdParserProxy_ParsingBufferLength; }
            }
 
            int IDtdParserAdapter.CurrentPosition
            {
                get { return _reader.DtdParserProxy_CurrentPosition; }
                set { _reader.DtdParserProxy_CurrentPosition = value; }
            }
 
            int IDtdParserAdapter.EntityStackLength
            {
                get { return _reader.DtdParserProxy_EntityStackLength; }
            }
 
            bool IDtdParserAdapter.IsEntityEolNormalized
            {
                get { return _reader.DtdParserProxy_IsEntityEolNormalized; }
            }
 
            void IDtdParserAdapter.OnNewLine(int pos)
            {
                _reader.DtdParserProxy_OnNewLine(pos);
            }
 
            int IDtdParserAdapter.LineNo
            {
                get { return _reader.DtdParserProxy_LineNo; }
            }
 
            int IDtdParserAdapter.LineStartPosition
            {
                get { return _reader.DtdParserProxy_LineStartPosition; }
            }
 
            int IDtdParserAdapter.ReadData()
            {
                return _reader.DtdParserProxy_ReadData();
            }
 
            int IDtdParserAdapter.ParseNumericCharRef(StringBuilder? internalSubsetBuilder)
            {
                return _reader.DtdParserProxy_ParseNumericCharRef(internalSubsetBuilder);
            }
 
            int IDtdParserAdapter.ParseNamedCharRef(bool expand, StringBuilder? internalSubsetBuilder)
            {
                return _reader.DtdParserProxy_ParseNamedCharRef(expand, internalSubsetBuilder);
            }
 
            void IDtdParserAdapter.ParsePI(StringBuilder? sb)
            {
                _reader.DtdParserProxy_ParsePI(sb);
            }
 
            void IDtdParserAdapter.ParseComment(StringBuilder? sb)
            {
                _reader.DtdParserProxy_ParseComment(sb);
            }
 
            bool IDtdParserAdapter.PushEntity(IDtdEntityInfo entity, out int entityId)
            {
                return _reader.DtdParserProxy_PushEntity(entity, out entityId);
            }
 
            bool IDtdParserAdapter.PopEntity(out IDtdEntityInfo? oldEntity, out int newEntityId)
            {
                return _reader.DtdParserProxy_PopEntity(out oldEntity, out newEntityId);
            }
 
            bool IDtdParserAdapter.PushExternalSubset(string? systemId, string? publicId)
            {
                return _reader.DtdParserProxy_PushExternalSubset(systemId, publicId);
            }
 
            void IDtdParserAdapter.PushInternalDtd(string baseUri, string internalDtd)
            {
                Debug.Assert(internalDtd != null);
                _reader.DtdParserProxy_PushInternalDtd(baseUri, internalDtd);
            }
 
            [DoesNotReturn]
            void IDtdParserAdapter.Throw(Exception e)
            {
                _reader.DtdParserProxy_Throw(e);
            }
 
            void IDtdParserAdapter.OnSystemId(string systemId, LineInfo keywordLineInfo, LineInfo systemLiteralLineInfo)
            {
                _reader.DtdParserProxy_OnSystemId(systemId, keywordLineInfo, systemLiteralLineInfo);
            }
 
            void IDtdParserAdapter.OnPublicId(string publicId, LineInfo keywordLineInfo, LineInfo publicLiteralLineInfo)
            {
                _reader.DtdParserProxy_OnPublicId(publicId, keywordLineInfo, publicLiteralLineInfo);
            }
 
            bool IDtdParserAdapterWithValidation.DtdValidation
            {
                get { return _reader.DtdParserProxy_DtdValidation; }
            }
 
            IValidationEventHandling? IDtdParserAdapterWithValidation.ValidationEventHandling
            {
                get { return _reader.DtdParserProxy_ValidationEventHandling; }
            }
 
            bool IDtdParserAdapterV1.Normalization
            {
                get { return _reader.DtdParserProxy_Normalization; }
            }
 
            bool IDtdParserAdapterV1.Namespaces
            {
                get { return _reader.DtdParserProxy_Namespaces; }
            }
 
            bool IDtdParserAdapterV1.V1CompatibilityMode
            {
                get { return _reader.DtdParserProxy_V1CompatibilityMode; }
            }
        }
 
        //
        // NodeData
        //
        private sealed class NodeData : IComparable
        {
            // static instance with no data - is used when XmlTextReader is closed
            private static volatile NodeData? s_None;
 
            // NOTE: Do not use this property for reference comparison. It may not be unique.
            internal static NodeData None =>
                // no locking; s_None is immutable so it's not a problem that it may get initialized more than once
                s_None ??= new NodeData();
 
            // type
            internal XmlNodeType type;
 
            // name
            internal string localName = null!;
            internal string prefix = null!;
            internal string? ns;
            internal string? nameWPrefix;
 
            // value:
            // value == null -> the value is kept in the 'chars' buffer starting at valueStartPos and valueLength long
            private string? _value;
            private char[]? _chars;
            private int _valueStartPos;
            private int _valueLength;
 
            // main line info
            internal LineInfo lineInfo;
 
            // second line info
            internal LineInfo lineInfo2;
 
            // quote char for attributes
            internal char quoteChar;
 
            // depth
            internal int depth;
 
            // empty element / default attribute
            private bool _isEmptyOrDefault;
 
            // entity id
            internal int entityId;
 
            // helper members
            internal bool xmlContextPushed;
 
            // attribute value chunks
            internal NodeData? nextAttrValueChunk;
 
            // type info
            internal object? schemaType;
            internal object? typedValue;
 
            internal NodeData()
            {
                Clear(XmlNodeType.None);
                xmlContextPushed = false;
            }
 
            internal int LineNo
            {
                get
                {
                    return lineInfo.lineNo;
                }
            }
 
            internal int LinePos
            {
                get
                {
                    return lineInfo.linePos;
                }
            }
 
            internal bool IsEmptyElement
            {
                get
                {
                    return type == XmlNodeType.Element && _isEmptyOrDefault;
                }
                set
                {
                    Debug.Assert(type == XmlNodeType.Element);
                    _isEmptyOrDefault = value;
                }
            }
 
            internal bool IsDefaultAttribute
            {
                get
                {
                    return type == XmlNodeType.Attribute && _isEmptyOrDefault;
                }
                set
                {
                    Debug.Assert(type == XmlNodeType.Attribute);
                    _isEmptyOrDefault = value;
                }
            }
 
            internal bool ValueBuffered
            {
                get
                {
                    return _value == null;
                }
            }
 
            internal string StringValue
            {
                get
                {
                    Debug.Assert(_valueStartPos >= 0 || _value != null, "Value not ready.");
 
                    if (_value == null)
                    {
                        Debug.Assert(_chars != null);
                        _value = new string(_chars, _valueStartPos, _valueLength);
                    }
                    return _value;
                }
            }
 
            internal void TrimSpacesInValue()
            {
                if (ValueBuffered)
                {
                    Debug.Assert(_chars != null);
                    XmlTextReaderImpl.StripSpaces(_chars, _valueStartPos, ref _valueLength);
                }
                else
                {
                    Debug.Assert(_value != null);
                    _value = XmlTextReaderImpl.StripSpaces(_value);
                }
            }
 
            [MemberNotNull(nameof(_value))]
            [MemberNotNull(nameof(nameWPrefix))]
            [MemberNotNull(nameof(localName))]
            [MemberNotNull(nameof(prefix))]
            [MemberNotNull(nameof(ns))]
            internal void Clear(XmlNodeType type)
            {
                this.type = type;
                ClearName();
                _value = string.Empty;
                _valueStartPos = -1;
                schemaType = null;
                typedValue = null;
            }
 
            [MemberNotNull(nameof(localName))]
            [MemberNotNull(nameof(prefix))]
            [MemberNotNull(nameof(ns))]
            [MemberNotNull(nameof(nameWPrefix))]
            internal void ClearName()
            {
                localName = string.Empty;
                prefix = string.Empty;
                ns = string.Empty;
                nameWPrefix = string.Empty;
            }
 
            internal void SetLineInfo(int lineNo, int linePos)
            {
                lineInfo.Set(lineNo, linePos);
            }
 
            internal void SetLineInfo2(int lineNo, int linePos)
            {
                lineInfo2.Set(lineNo, linePos);
            }
 
            internal void SetValueNode(XmlNodeType type, string value)
            {
                Debug.Assert(value != null);
 
                this.type = type;
                ClearName();
                _value = value;
                _valueStartPos = -1;
            }
 
            internal void SetValueNode(XmlNodeType type, char[] chars, int startPos, int len)
            {
                this.type = type;
                ClearName();
 
                _value = null;
                _chars = chars;
                _valueStartPos = startPos;
                _valueLength = len;
            }
 
            internal void SetNamedNode(XmlNodeType type, string localName)
            {
                SetNamedNode(type, localName, string.Empty, localName);
            }
 
            internal void SetNamedNode(XmlNodeType type, string localName, string prefix, string? nameWPrefix)
            {
                Debug.Assert(localName != null);
                Debug.Assert(localName.Length > 0);
 
                this.type = type;
                this.localName = localName;
                this.prefix = prefix;
                this.nameWPrefix = nameWPrefix;
                this.ns = string.Empty;
                _value = string.Empty;
                _valueStartPos = -1;
            }
 
            internal void SetValue(string value)
            {
                _valueStartPos = -1;
                _value = value;
            }
 
            internal void SetValue(char[] chars, int startPos, int len)
            {
                _value = null;
                _chars = chars;
                _valueStartPos = startPos;
                _valueLength = len;
            }
 
            internal void OnBufferInvalidated()
            {
                if (_value == null)
                {
                    Debug.Assert(_valueStartPos != -1);
                    Debug.Assert(_chars != null);
                    _value = new string(_chars, _valueStartPos, _valueLength);
                }
                _valueStartPos = -1;
            }
 
            internal void CopyTo(int valueOffset, StringBuilder sb)
            {
                if (_value == null)
                {
                    Debug.Assert(_valueStartPos != -1);
                    Debug.Assert(_chars != null);
                    sb.Append(_chars, _valueStartPos + valueOffset, _valueLength - valueOffset);
                }
                else
                {
                    if (valueOffset <= 0)
                    {
                        sb.Append(_value);
                    }
                    else
                    {
                        sb.Append(_value, valueOffset, _value.Length - valueOffset);
                    }
                }
            }
 
            internal int CopyTo(int valueOffset, char[] buffer, int offset, int length)
            {
                if (_value == null)
                {
                    Debug.Assert(_valueStartPos != -1);
                    Debug.Assert(_chars != null);
                    int copyCount = _valueLength - valueOffset;
                    if (copyCount > length)
                    {
                        copyCount = length;
                    }
                    XmlTextReaderImpl.BlockCopyChars(_chars, _valueStartPos + valueOffset, buffer, offset, copyCount);
                    return copyCount;
                }
                else
                {
                    int copyCount = _value.Length - valueOffset;
                    if (copyCount > length)
                    {
                        copyCount = length;
                    }
                    _value.CopyTo(valueOffset, buffer, offset, copyCount);
                    return copyCount;
                }
            }
 
            internal int CopyToBinary(IncrementalReadDecoder decoder, int valueOffset)
            {
                if (_value == null)
                {
                    Debug.Assert(_valueStartPos != -1);
                    Debug.Assert(_chars != null);
                    return decoder.Decode(_chars, _valueStartPos + valueOffset, _valueLength - valueOffset);
                }
                else
                {
                    return decoder.Decode(_value, valueOffset, _value.Length - valueOffset);
                }
            }
 
            internal void AdjustLineInfo(int valueOffset, bool isNormalized, ref LineInfo lineInfo)
            {
                if (valueOffset == 0)
                {
                    return;
                }
                if (_valueStartPos != -1)
                {
                    Debug.Assert(_chars != null);
                    XmlTextReaderImpl.AdjustLineInfo(_chars, _valueStartPos, _valueStartPos + valueOffset, isNormalized, ref lineInfo);
                }
                else
                {
                    Debug.Assert(_value != null);
                    XmlTextReaderImpl.AdjustLineInfo(_value, 0, valueOffset, isNormalized, ref lineInfo);
                }
            }
 
            // This should be inlined by JIT compiler
            internal string GetNameWPrefix(XmlNameTable nt)
            {
                if (nameWPrefix != null)
                {
                    return nameWPrefix;
                }
                else
                {
                    return CreateNameWPrefix(nt);
                }
            }
 
            internal string CreateNameWPrefix(XmlNameTable nt)
            {
                Debug.Assert(nameWPrefix == null);
                if (prefix.Length == 0)
                {
                    nameWPrefix = localName;
                }
                else
                {
                    nameWPrefix = nt.Add($"{prefix}:{localName}");
                }
                return nameWPrefix;
            }
 
            int IComparable.CompareTo(object? obj)
            {
                NodeData? other = obj as NodeData;
                if (other != null)
                {
                    if (Ref.Equal(localName, other.localName))
                    {
                        if (Ref.Equal(ns, other.ns))
                        {
                            return 0;
                        }
                        else
                        {
                            return string.CompareOrdinal(ns, other.ns);
                        }
                    }
                    else
                    {
                        return string.CompareOrdinal(localName, other.localName);
                    }
                }
                else
                {
                    Debug.Fail("We should never get to this point.");
                    // 'other' is null, 'this' is not null. Always return 1, like "".CompareTo(null).
                    return 1;
                }
            }
        }
 
        //
        // DtdDefaultAttributeInfoToNodeDataComparer
        //
        // Compares IDtdDefaultAttributeInfo to NodeData
        private sealed class DtdDefaultAttributeInfoToNodeDataComparer : IComparer<object>
        {
            private static readonly IComparer<object> s_instance = new DtdDefaultAttributeInfoToNodeDataComparer();
 
            internal static IComparer<object> Instance
            {
                get { return s_instance; }
            }
 
            public int Compare(object? x, object? y)
            {
                Debug.Assert(x == null || x is NodeData || x is IDtdDefaultAttributeInfo);
                Debug.Assert(y == null || y is NodeData || y is IDtdDefaultAttributeInfo);
 
                string localName, localName2;
                string prefix, prefix2;
 
                if (x == null)
                {
                    return y == null ? 0 : -1;
                }
                else if (y == null)
                {
                    return 1;
                }
 
                NodeData? nodeData = x as NodeData;
                if (nodeData != null)
                {
                    localName = nodeData.localName;
                    prefix = nodeData.prefix;
                }
                else
                {
                    if (x is IDtdDefaultAttributeInfo attrDef)
                    {
                        localName = attrDef.LocalName;
                        prefix = attrDef.Prefix;
                    }
                    else
                    {
                        throw new XmlException(SR.Xml_DefaultException, string.Empty);
                    }
                }
 
                nodeData = y as NodeData;
                if (nodeData != null)
                {
                    localName2 = nodeData.localName;
                    prefix2 = nodeData.prefix;
                }
                else
                {
                    if (y is IDtdDefaultAttributeInfo attrDef)
                    {
                        localName2 = attrDef.LocalName;
                        prefix2 = attrDef.Prefix;
                    }
                    else
                    {
                        throw new XmlException(SR.Xml_DefaultException, string.Empty);
                    }
                }
 
                // string.Compare does reference euqality first for us, so we don't have to do it here
                int result = string.Compare(localName, localName2, StringComparison.Ordinal);
                if (result != 0)
                {
                    return result;
                }
 
                return string.Compare(prefix, prefix2, StringComparison.Ordinal);
            }
        }
 
        //
        // OnDefaultAttributeUse delegate
        //
        internal delegate void OnDefaultAttributeUseDelegate(IDtdDefaultAttributeInfo defaultAttribute, XmlTextReaderImpl coreReader);
    }
}