File: System\Xml\Xsl\XsltOld\ReaderOutput.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.Collections;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Text;
 
namespace System.Xml.Xsl.XsltOld
{
    internal sealed class ReaderOutput : XmlReader, IRecordOutput
    {
        private Processor _processor;
        private readonly XmlNameTable _nameTable;
 
        // Main node + Fields Collection
        private RecordBuilder? _builder;
        private BuilderInfo _mainNode;
        private ArrayList? _attributeList;
        private int _attributeCount;
        private BuilderInfo? _attributeValue;
 
        // OutputScopeManager
        private OutputScopeManager? _manager;
 
        // Current position in the list
        private int _currentIndex;
        private BuilderInfo _currentInfo;
 
        // Reader state
        private ReadState _state = ReadState.Initial;
        private bool _haveRecord;
 
        // Static default record
        private static readonly BuilderInfo s_DefaultInfo = new BuilderInfo();
 
        private readonly XmlEncoder _encoder = new XmlEncoder();
 
        internal ReaderOutput(Processor processor)
        {
            Debug.Assert(processor != null);
            Debug.Assert(processor.NameTable != null);
 
            _processor = processor;
            _nameTable = processor.NameTable;
 
            Reset();
        }
 
        // XmlReader abstract methods implementation
        public override XmlNodeType NodeType
        {
            get
            {
                CheckCurrentInfo();
                return _currentInfo.NodeType;
            }
        }
 
        public override string Name
        {
            get
            {
                CheckCurrentInfo();
                string prefix = Prefix;
                string localName = LocalName;
 
                if (prefix != null && prefix.Length > 0)
                {
                    if (localName.Length > 0)
                    {
                        return _nameTable.Add($"{prefix}:{localName}");
                    }
                    else
                    {
                        return prefix;
                    }
                }
                else
                {
                    return localName;
                }
            }
        }
 
        public override string LocalName
        {
            get
            {
                CheckCurrentInfo();
                return _currentInfo.LocalName;
            }
        }
 
        public override string NamespaceURI
        {
            get
            {
                CheckCurrentInfo();
                return _currentInfo.NamespaceURI;
            }
        }
 
        public override string Prefix
        {
            get
            {
                CheckCurrentInfo();
                return _currentInfo.Prefix;
            }
        }
 
        public override bool HasValue
        {
            get
            {
                return XmlReader.HasValueInternal(NodeType);
            }
        }
 
        public override string Value
        {
            get
            {
                CheckCurrentInfo();
                return _currentInfo.Value;
            }
        }
 
        public override int Depth
        {
            get
            {
                CheckCurrentInfo();
                return _currentInfo.Depth;
            }
        }
 
        public override string BaseURI
        {
            get
            {
                return string.Empty;
            }
        }
 
        public override bool IsEmptyElement
        {
            get
            {
                CheckCurrentInfo();
                return _currentInfo.IsEmptyTag;
            }
        }
 
        public override char QuoteChar
        {
            get { return XmlEncoder.QuoteChar; }
        }
 
        public override bool IsDefault
        {
            get { return false; }
        }
 
        public override XmlSpace XmlSpace
        {
            get { return _manager != null ? _manager.XmlSpace : XmlSpace.None; }
        }
 
        public override string XmlLang
        {
            get { return _manager != null ? _manager.XmlLang : string.Empty; }
        }
 
        // Attribute Accessors
 
        public override int AttributeCount
        {
            get { return _attributeCount; }
        }
 
        public override string? GetAttribute(string name)
        {
            int ordinal;
            if (FindAttribute(name, out ordinal))
            {
                Debug.Assert(ordinal >= 0);
                return ((BuilderInfo)_attributeList![ordinal]!).Value;
            }
            else
            {
                Debug.Assert(ordinal == -1);
                return null;
            }
        }
 
        public override string? GetAttribute(string localName, string? namespaceURI)
        {
            int ordinal;
            if (FindAttribute(localName, namespaceURI, out ordinal))
            {
                Debug.Assert(ordinal >= 0);
                return ((BuilderInfo)_attributeList![ordinal]!).Value;
            }
            else
            {
                Debug.Assert(ordinal == -1);
                return null;
            }
        }
 
        public override string GetAttribute(int i)
        {
            BuilderInfo attribute = GetBuilderInfo(i);
            return attribute.Value;
        }
 
        public override string this[int i]
        {
            get { return GetAttribute(i); }
        }
 
        public override string? this[string name, string? namespaceURI]
        {
            get { return GetAttribute(name, namespaceURI); }
        }
 
        public override bool MoveToAttribute(string name)
        {
            int ordinal;
            if (FindAttribute(name, out ordinal))
            {
                Debug.Assert(ordinal >= 0);
                SetAttribute(ordinal);
                return true;
            }
            else
            {
                Debug.Assert(ordinal == -1);
                return false;
            }
        }
 
        public override bool MoveToAttribute(string localName, string? namespaceURI)
        {
            int ordinal;
            if (FindAttribute(localName, namespaceURI, out ordinal))
            {
                Debug.Assert(ordinal >= 0);
                SetAttribute(ordinal);
                return true;
            }
            else
            {
                Debug.Assert(ordinal == -1);
                return false;
            }
        }
 
        public override void MoveToAttribute(int i)
        {
            ArgumentOutOfRangeException.ThrowIfNegative(i);
            ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(i, _attributeCount);
            SetAttribute(i);
        }
 
        public override bool MoveToFirstAttribute()
        {
            if (_attributeCount <= 0)
            {
                Debug.Assert(_attributeCount == 0);
                return false;
            }
            else
            {
                SetAttribute(0);
                return true;
            }
        }
 
        public override bool MoveToNextAttribute()
        {
            if (_currentIndex + 1 < _attributeCount)
            {
                SetAttribute(_currentIndex + 1);
                return true;
            }
            return false;
        }
 
        public override bool MoveToElement()
        {
            if (NodeType == XmlNodeType.Attribute || _currentInfo == _attributeValue)
            {
                SetMainNode();
                return true;
            }
            return false;
        }
 
        // Moving through the Stream
 
        public override bool Read()
        {
            Debug.Assert(_processor != null || _state == ReadState.Closed);
 
            if (_state != ReadState.Interactive)
            {
                if (_state == ReadState.Initial)
                {
                    _state = ReadState.Interactive;
                }
                else
                {
                    return false;
                }
            }
 
            while (true)
            { // while -- to ignore empty whitespace nodes.
                if (_haveRecord)
                {
                    _processor!.ResetOutput();
                    _haveRecord = false;
                }
 
                _processor!.Execute();
 
                if (_haveRecord)
                {
                    CheckCurrentInfo();
                    // check text nodes on whitespace;
                    switch (NodeType)
                    {
                        case XmlNodeType.Text:
                            if (XmlCharType.IsOnlyWhitespace(Value))
                            {
                                _currentInfo.NodeType = XmlNodeType.Whitespace;
                                goto case XmlNodeType.Whitespace;
                            }
                            Debug.Assert(Value.Length != 0, "It whould be Whitespace in this case");
                            break;
                        case XmlNodeType.Whitespace:
                            if (Value.Length == 0)
                            {
                                continue;                          // ignoring emty text nodes
                            }
                            if (XmlSpace == XmlSpace.Preserve)
                            {
                                _currentInfo.NodeType = XmlNodeType.SignificantWhitespace;
                            }
                            break;
                    }
                }
                else
                {
                    Debug.Assert(_processor.ExecutionDone);
                    _state = ReadState.EndOfFile;
                    Reset();
                }
 
                return _haveRecord;
            }
        }
 
        public override bool EOF
        {
            get { return _state == ReadState.EndOfFile; }
        }
 
        public override void Close()
        {
            _processor = null!;
            _state = ReadState.Closed;
            Reset();
        }
 
        public override ReadState ReadState
        {
            get { return _state; }
        }
 
        // Whole Content Read Methods
        public override string ReadString()
        {
            string result = string.Empty;
 
            if (NodeType == XmlNodeType.Element || NodeType == XmlNodeType.Attribute || _currentInfo == _attributeValue)
            {
                if (_mainNode.IsEmptyTag)
                {
                    return result;
                }
                if (!Read())
                {
                    throw new InvalidOperationException(SR.Xml_InvalidOperation);
                }
            }
 
            StringBuilder? sb = null;
            bool first = true;
 
            while (true)
            {
                switch (NodeType)
                {
                    case XmlNodeType.Text:
                    case XmlNodeType.Whitespace:
                    case XmlNodeType.SignificantWhitespace:
                        //              case XmlNodeType.CharacterEntity:
                        if (first)
                        {
                            result = this.Value;
                            first = false;
                        }
                        else
                        {
                            sb ??= new StringBuilder(result);
                            sb.Append(this.Value);
                        }
                        if (!Read())
                            throw new InvalidOperationException(SR.Xml_InvalidOperation);
                        break;
                    default:
                        return (sb == null) ? result : sb.ToString();
                }
            }
        }
 
        public override string ReadInnerXml()
        {
            if (ReadState == ReadState.Interactive)
            {
                if (NodeType == XmlNodeType.Element && !IsEmptyElement)
                {
                    StringOutput output = new StringOutput(_processor);
                    output.OmitXmlDecl();
                    int depth = Depth;
 
                    Read();                 // skeep  begin Element
                    while (depth < Depth)
                    { // process content
                        Debug.Assert(_builder != null);
                        output.RecordDone(_builder);
                        Read();
                    }
                    Debug.Assert(NodeType == XmlNodeType.EndElement);
                    Read();                 // skeep end element
 
                    output.TheEnd();
                    return output.Result!;
                }
                else if (NodeType == XmlNodeType.Attribute)
                {
                    return _encoder.AttributeInnerXml(Value);
                }
                else
                {
                    Read();
                }
            }
            return string.Empty;
        }
 
        public override string ReadOuterXml()
        {
            if (ReadState == ReadState.Interactive)
            {
                if (NodeType == XmlNodeType.Element)
                {
                    StringOutput output = new StringOutput(_processor);
                    output.OmitXmlDecl();
                    bool emptyElement = IsEmptyElement;
                    int depth = Depth;
                    // process current record
                    output.RecordDone(_builder!);
                    Read();
                    // process internal elements & text nodes
                    while (depth < Depth)
                    {
                        Debug.Assert(_builder != null);
                        output.RecordDone(_builder);
                        Read();
                    }
                    // process end element
                    if (!emptyElement)
                    {
                        output.RecordDone(_builder!);
                        Read();
                    }
 
                    output.TheEnd();
                    return output.Result!;
                }
                else if (NodeType == XmlNodeType.Attribute)
                {
                    return _encoder.AttributeOuterXml(Name, Value);
                }
                else
                {
                    Read();
                }
            }
            return string.Empty;
        }
 
        //
        // Nametable and Namespace Helpers
        //
 
        public override XmlNameTable NameTable
        {
            get
            {
                Debug.Assert(_nameTable != null);
                return _nameTable;
            }
        }
 
        public override string? LookupNamespace(string prefix)
        {
            string? atomizedPrefix = _nameTable.Get(prefix);
 
            if (_manager != null && atomizedPrefix != null)
            {
                return _manager.ResolveNamespace(atomizedPrefix);
            }
            return null;
        }
 
        public override void ResolveEntity()
        {
            Debug.Assert(NodeType != XmlNodeType.EntityReference);
 
            if (NodeType != XmlNodeType.EntityReference)
            {
                throw new InvalidOperationException(SR.Xml_InvalidOperation);
            }
        }
 
        public override bool ReadAttributeValue()
        {
            if (ReadState != ReadState.Interactive || NodeType != XmlNodeType.Attribute)
            {
                return false;
            }
 
            if (_attributeValue == null)
            {
                _attributeValue = new BuilderInfo();
                _attributeValue.NodeType = XmlNodeType.Text;
            }
            if (_currentInfo == _attributeValue)
            {
                return false;
            }
 
            _attributeValue.Value = _currentInfo.Value;
            _attributeValue.Depth = _currentInfo.Depth + 1;
            _currentInfo = _attributeValue;
 
            return true;
        }
 
        //
        // RecordOutput interface method implementation
        //
        [MemberNotNull(nameof(_builder))]
        [MemberNotNull(nameof(_attributeList))]
        public Processor.OutputResult RecordDone(RecordBuilder record)
        {
            _builder = record;
            _mainNode = record.MainNode;
            _attributeList = record.AttributeList;
            _attributeCount = record.AttributeCount;
            _manager = record.Manager;
 
            _haveRecord = true;
            SetMainNode();
 
            return Processor.OutputResult.Interrupt;
        }
 
        public void TheEnd()
        {
            // nothing here, was taken care of by RecordBuilder
        }
 
        //
        // Implementation internals
        //
 
        private void SetMainNode()
        {
            _currentIndex = -1;
            _currentInfo = _mainNode;
        }
 
        private void SetAttribute(int attrib)
        {
            Debug.Assert(0 <= attrib && attrib < _attributeCount);
            Debug.Assert(0 <= attrib && attrib < _attributeList!.Count);
            Debug.Assert(_attributeList[attrib] is BuilderInfo);
 
            _currentIndex = attrib;
            _currentInfo = (BuilderInfo)_attributeList[attrib]!;
        }
 
        private BuilderInfo GetBuilderInfo(int attrib)
        {
            ArgumentOutOfRangeException.ThrowIfNegative(attrib);
            ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(attrib, _attributeCount);
 
            Debug.Assert(_attributeList![attrib] is BuilderInfo);
 
            return (BuilderInfo)_attributeList[attrib]!;
        }
 
        private bool FindAttribute(string? localName, string? namespaceURI, out int attrIndex)
        {
            namespaceURI ??= string.Empty;
            localName ??= string.Empty;
 
            for (int index = 0; index < _attributeCount; index++)
            {
                Debug.Assert(_attributeList![index] is BuilderInfo);
 
                BuilderInfo attribute = (BuilderInfo)_attributeList[index]!;
                if (attribute.NamespaceURI == namespaceURI && attribute.LocalName == localName)
                {
                    attrIndex = index;
                    return true;
                }
            }
 
            attrIndex = -1;
            return false;
        }
 
        private bool FindAttribute(string? name, out int attrIndex)
        {
            name ??= string.Empty;
 
            for (int index = 0; index < _attributeCount; index++)
            {
                Debug.Assert(_attributeList![index] is BuilderInfo);
 
                BuilderInfo attribute = (BuilderInfo)_attributeList[index]!;
                if (attribute.Name == name)
                {
                    attrIndex = index;
                    return true;
                }
            }
 
            attrIndex = -1;
            return false;
        }
 
        [MemberNotNull(nameof(_currentInfo))]
        [MemberNotNull(nameof(_mainNode))]
        private void Reset()
        {
            _currentIndex = -1;
            _currentInfo = s_DefaultInfo;
            _mainNode = s_DefaultInfo;
            _manager = null;
        }
 
        [System.Diagnostics.Conditional("DEBUG")]
        private void CheckCurrentInfo()
        {
            Debug.Assert(_currentInfo != null);
            Debug.Assert(_attributeCount == 0 || _attributeList != null);
            Debug.Assert((_currentIndex == -1) == (_currentInfo == _mainNode));
            Debug.Assert((_currentIndex == -1) || (_currentInfo == _attributeValue || _attributeList![_currentIndex] is BuilderInfo && _attributeList[_currentIndex] == _currentInfo));
        }
 
        private sealed class XmlEncoder
        {
            private StringBuilder? _buffer;
            private XmlTextEncoder? _encoder;
 
            [MemberNotNull(nameof(_buffer))]
            [MemberNotNull(nameof(_encoder))]
            private void Init()
            {
                _buffer = new StringBuilder();
                _encoder = new XmlTextEncoder(new StringWriter(_buffer, CultureInfo.InvariantCulture));
            }
 
            public string AttributeInnerXml(string value)
            {
                if (_encoder == null) Init();
                _buffer!.Length = 0;       // clean buffer
                _encoder.StartAttribute(/*save:*/false);
                _encoder.Write(value);
                _encoder.EndAttribute();
                return _buffer.ToString();
            }
 
            public string AttributeOuterXml(string name, string value)
            {
                if (_encoder == null) Init();
                _buffer!.Length = 0;       // clean buffer
                _buffer.Append(name);
                _buffer.Append('=');
                _buffer.Append(QuoteChar);
                _encoder.StartAttribute(/*save:*/false);
                _encoder.Write(value);
                _encoder.EndAttribute();
                _buffer.Append(QuoteChar);
                return _buffer.ToString();
            }
 
            public const char QuoteChar = '"';
        }
    }
}