File: System\Xml\Xsl\XsltOld\RecordBuilder.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.Text;
using System.Xml.XPath;
 
namespace System.Xml.Xsl.XsltOld
{
    internal sealed class RecordBuilder
    {
        private int _outputState;
        private RecordBuilder? _next;
 
        private readonly IRecordOutput _output;
 
        // Atomization:
        private readonly XmlNameTable _nameTable;
        private readonly OutKeywords _atoms;
 
        // Namespace manager for output
        private readonly OutputScopeManager _scopeManager;
 
        // Main node + Fields Collection
        private readonly BuilderInfo _mainNode = new BuilderInfo();
        private readonly ArrayList _attributeList = new ArrayList();
        private int _attributeCount;
        private readonly ArrayList _namespaceList = new ArrayList();
        private int _namespaceCount;
        private readonly BuilderInfo _dummy = new BuilderInfo();
 
        // Current position in the list
        private BuilderInfo? _currentInfo;
        // Builder state
        private bool _popScope;
        private int _recordState;
        private int _recordDepth;
 
        private const int NoRecord = 0;      // No part of a new record was generated (old record was cleared out)
        private const int SomeRecord = 1;      // Record was generated partially        (can be eventually record)
        private const int HaveRecord = 2;      // Record was fully generated
 
        private const char s_Minus = '-';
        private const char s_Space = ' ';
        private const string s_SpaceMinus = " -";
        private const char s_Question = '?';
        private const char s_Greater = '>';
        private const string s_SpaceGreater = " >";
 
        private const string PrefixFormat = "xp_{0}";
 
        internal RecordBuilder(IRecordOutput output, XmlNameTable? nameTable)
        {
            Debug.Assert(output != null);
            _output = output;
            _nameTable = nameTable ?? new NameTable();
            _atoms = new OutKeywords(_nameTable);
            _scopeManager = new OutputScopeManager(_nameTable, _atoms);
        }
 
        //
        // Internal properties
        //
 
        internal int OutputState
        {
            get { return _outputState; }
            set { _outputState = value; }
        }
 
        internal RecordBuilder? Next
        {
            get { return _next; }
            set { _next = value; }
        }
 
        internal IRecordOutput Output
        {
            get { return _output; }
        }
 
        internal BuilderInfo MainNode
        {
            get { return _mainNode; }
        }
 
        internal ArrayList AttributeList
        {
            get { return _attributeList; }
        }
 
        internal int AttributeCount
        {
            get { return _attributeCount; }
        }
 
        internal OutputScopeManager Manager
        {
            get { return _scopeManager; }
        }
 
        private void ValueAppend(string? s, bool disableOutputEscaping)
        {
            _currentInfo!.ValueAppend(s, disableOutputEscaping);
        }
 
        private bool CanOutput(int state)
        {
            Debug.Assert(_recordState != HaveRecord);
 
            // If we have no record cached or the next event doesn't start new record, we are OK
 
            if (_recordState == NoRecord || (state & StateMachine.BeginRecord) == 0)
            {
                return true;
            }
            else
            {
                _recordState = HaveRecord;
                FinalizeRecord();
                SetEmptyFlag(state);
                return _output.RecordDone(this) == Processor.OutputResult.Continue;
            }
        }
 
        internal Processor.OutputResult BeginEvent(int state, XPathNodeType nodeType, string? prefix, string? name, string? nspace, bool empty, object? htmlProps, bool search)
        {
            if (!CanOutput(state))
            {
                return Processor.OutputResult.Overflow;
            }
 
            Debug.Assert(_recordState == NoRecord || (state & StateMachine.BeginRecord) == 0);
 
            AdjustDepth(state);
            ResetRecord(state);
            PopElementScope();
 
            prefix = (prefix != null) ? _nameTable.Add(prefix) : _atoms.Empty;
            name = (name != null) ? _nameTable.Add(name) : _atoms.Empty;
            nspace = (nspace != null) ? _nameTable.Add(nspace) : _atoms.Empty;
 
            switch (nodeType)
            {
                case XPathNodeType.Element:
                    _mainNode.htmlProps = htmlProps as HtmlElementProps;
                    _mainNode.search = search;
                    BeginElement(prefix, name, nspace, empty);
                    break;
                case XPathNodeType.Attribute:
                    BeginAttribute(prefix, name, nspace, htmlProps, search);
                    break;
                case XPathNodeType.Namespace:
                    BeginNamespace(name, nspace);
                    break;
                case XPathNodeType.Text:
                    break;
                case XPathNodeType.ProcessingInstruction:
                    if (BeginProcessingInstruction(prefix, name, nspace) == false)
                    {
                        return Processor.OutputResult.Error;
                    }
                    break;
                case XPathNodeType.Comment:
                    BeginComment();
                    break;
                case XPathNodeType.Root:
                    break;
                case XPathNodeType.Whitespace:
                case XPathNodeType.SignificantWhitespace:
                case XPathNodeType.All:
                    break;
            }
 
            return CheckRecordBegin(state);
        }
 
        internal Processor.OutputResult TextEvent(int state, string? text, bool disableOutputEscaping)
        {
            if (!CanOutput(state))
            {
                return Processor.OutputResult.Overflow;
            }
 
            Debug.Assert(_recordState == NoRecord || (state & StateMachine.BeginRecord) == 0);
 
            AdjustDepth(state);
            ResetRecord(state);
            PopElementScope();
 
            if ((state & StateMachine.BeginRecord) != 0)
            {
                _currentInfo!.Depth = _recordDepth;
                _currentInfo.NodeType = XmlNodeType.Text;
            }
 
            ValueAppend(text, disableOutputEscaping);
 
            return CheckRecordBegin(state);
        }
 
        internal Processor.OutputResult EndEvent(int state, XPathNodeType nodeType)
        {
            if (!CanOutput(state))
            {
                return Processor.OutputResult.Overflow;
            }
 
            AdjustDepth(state);
            PopElementScope();
            _popScope = (state & StateMachine.PopScope) != 0;
 
            if ((state & StateMachine.EmptyTag) != 0 && _mainNode.IsEmptyTag)
            {
                return Processor.OutputResult.Continue;
            }
 
            ResetRecord(state);
 
            if ((state & StateMachine.BeginRecord) != 0)
            {
                if (nodeType == XPathNodeType.Element)
                {
                    EndElement();
                }
            }
 
            return CheckRecordEnd(state);
        }
 
        internal void Reset()
        {
            if (_recordState == HaveRecord)
            {
                _recordState = NoRecord;
            }
        }
 
        internal void TheEnd()
        {
            if (_recordState == SomeRecord)
            {
                _recordState = HaveRecord;
                FinalizeRecord();
                _output.RecordDone(this);
            }
            _output.TheEnd();
        }
 
        //
        // Utility implementation methods
        //
 
        private int FindAttribute(string name, string nspace, ref string prefix)
        {
            Debug.Assert(_attributeCount <= _attributeList.Count);
 
            for (int attrib = 0; attrib < _attributeCount; attrib++)
            {
                Debug.Assert(_attributeList[attrib] != null && _attributeList[attrib] is BuilderInfo);
 
                BuilderInfo attribute = (BuilderInfo)_attributeList[attrib]!;
 
                if (Ref.Equal(attribute.LocalName, name))
                {
                    if (Ref.Equal(attribute.NamespaceURI, nspace))
                    {
                        return attrib;
                    }
                    if (Ref.Equal(attribute.Prefix, prefix))
                    {
                        // prefix conflict. Should be renamed.
                        prefix = string.Empty;
                    }
                }
            }
 
            return -1;
        }
 
        private void BeginElement(string prefix, string name, string nspace, bool empty)
        {
            Debug.Assert(_attributeCount == 0);
 
            _currentInfo!.NodeType = XmlNodeType.Element;
            _currentInfo.Prefix = prefix;
            _currentInfo.LocalName = name;
            _currentInfo.NamespaceURI = nspace;
            _currentInfo.Depth = _recordDepth;
            _currentInfo.IsEmptyTag = empty;
 
            _scopeManager.PushScope(name, nspace, prefix);
        }
 
        private void EndElement()
        {
            Debug.Assert(_attributeCount == 0);
            OutputScope elementScope = _scopeManager.CurrentElementScope;
 
            _currentInfo!.NodeType = XmlNodeType.EndElement;
            _currentInfo.Prefix = elementScope.Prefix;
            _currentInfo.LocalName = elementScope.Name;
            _currentInfo.NamespaceURI = elementScope.Namespace;
            _currentInfo.Depth = _recordDepth;
        }
 
        private int NewAttribute()
        {
            if (_attributeCount >= _attributeList.Count)
            {
                Debug.Assert(_attributeCount == _attributeList.Count);
                _attributeList.Add(new BuilderInfo());
            }
            return _attributeCount++;
        }
 
        private void BeginAttribute(string prefix, string name, string nspace, object? htmlAttrProps, bool search)
        {
            int attrib = FindAttribute(name, nspace, ref prefix);
 
            if (attrib == -1)
            {
                attrib = NewAttribute();
            }
 
            Debug.Assert(_attributeList[attrib] != null && _attributeList[attrib] is BuilderInfo);
 
            BuilderInfo attribute = (BuilderInfo)_attributeList[attrib]!;
            attribute.Initialize(prefix, name, nspace);
            attribute.Depth = _recordDepth;
            attribute.NodeType = XmlNodeType.Attribute;
            attribute.htmlAttrProps = htmlAttrProps as HtmlAttributeProps;
            attribute.search = search;
            _currentInfo = attribute;
        }
 
        private void BeginNamespace(string name, string nspace)
        {
            bool thisScope;
            if (Ref.Equal(name, _atoms.Empty))
            {
                if (Ref.Equal(nspace, _scopeManager.DefaultNamespace))
                {
                    // Main Node is OK
                }
                else if (Ref.Equal(_mainNode.NamespaceURI, _atoms.Empty))
                {
                    // http://www.w3.org/1999/11/REC-xslt-19991116-errata/ E25
                    // Should throw an error but ingnoring it in Everett.
                    // Would be a breaking change
                }
                else
                {
                    DeclareNamespace(nspace, name);
                }
            }
            else
            {
                string? nspaceDeclared = _scopeManager.ResolveNamespace(name, out thisScope);
                if (nspaceDeclared != null)
                {
                    if (!Ref.Equal(nspace, nspaceDeclared))
                    {
                        if (!thisScope)
                        {
                            DeclareNamespace(nspace, name);
                        }
                    }
                }
                else
                {
                    DeclareNamespace(nspace, name);
                }
            }
            _currentInfo = _dummy;
            _currentInfo.NodeType = XmlNodeType.Attribute;
        }
 
        private bool BeginProcessingInstruction(string prefix, string name, string nspace)
        {
            _currentInfo!.NodeType = XmlNodeType.ProcessingInstruction;
            _currentInfo.Prefix = prefix;
            _currentInfo.LocalName = name;
            _currentInfo.NamespaceURI = nspace;
            _currentInfo.Depth = _recordDepth;
            return true;
        }
 
        private void BeginComment()
        {
            _currentInfo!.NodeType = XmlNodeType.Comment;
            _currentInfo.Depth = _recordDepth;
        }
 
        private void AdjustDepth(int state)
        {
            switch (state & StateMachine.DepthMask)
            {
                case StateMachine.DepthUp:
                    _recordDepth++;
                    break;
                case StateMachine.DepthDown:
                    _recordDepth--;
                    break;
                default:
                    break;
            }
        }
 
        private void ResetRecord(int state)
        {
            Debug.Assert(_recordState == NoRecord || _recordState == SomeRecord);
 
            if ((state & StateMachine.BeginRecord) != 0)
            {
                _attributeCount = 0;
                _namespaceCount = 0;
                _currentInfo = _mainNode;
 
                _currentInfo.Initialize(_atoms.Empty, _atoms.Empty, _atoms.Empty);
                _currentInfo.NodeType = XmlNodeType.None;
                _currentInfo.IsEmptyTag = false;
                _currentInfo.htmlProps = null;
                _currentInfo.htmlAttrProps = null;
            }
        }
 
        private void PopElementScope()
        {
            if (_popScope)
            {
                _scopeManager.PopScope();
                _popScope = false;
            }
        }
 
        private Processor.OutputResult CheckRecordBegin(int state)
        {
            Debug.Assert(_recordState == NoRecord || _recordState == SomeRecord);
 
            if ((state & StateMachine.EndRecord) != 0)
            {
                _recordState = HaveRecord;
                FinalizeRecord();
                SetEmptyFlag(state);
                return _output.RecordDone(this);
            }
            else
            {
                _recordState = SomeRecord;
                return Processor.OutputResult.Continue;
            }
        }
 
        private Processor.OutputResult CheckRecordEnd(int state)
        {
            Debug.Assert(_recordState == NoRecord || _recordState == SomeRecord);
 
            if ((state & StateMachine.EndRecord) != 0)
            {
                _recordState = HaveRecord;
                FinalizeRecord();
                SetEmptyFlag(state);
                return _output.RecordDone(this);
            }
            else
            {
                // For end event, if there is no end token, don't force token
                return Processor.OutputResult.Continue;
            }
        }
 
        private void SetEmptyFlag(int state)
        {
            Debug.Assert(_mainNode != null);
 
            if ((state & StateMachine.BeginChild) != 0)
            {
                _mainNode.IsEmptyTag = false;
            }
        }
 
 
        private void AnalyzeSpaceLang()
        {
            Debug.Assert(_mainNode.NodeType == XmlNodeType.Element);
 
            for (int attr = 0; attr < _attributeCount; attr++)
            {
                Debug.Assert(_attributeList[attr] is BuilderInfo);
                BuilderInfo info = (BuilderInfo)_attributeList[attr]!;
 
                if (Ref.Equal(info.Prefix, _atoms.Xml))
                {
                    OutputScope scope = _scopeManager.CurrentElementScope;
 
                    if (Ref.Equal(info.LocalName, _atoms.Lang))
                    {
                        scope.Lang = info.Value;
                    }
                    else if (Ref.Equal(info.LocalName, _atoms.Space))
                    {
                        scope.Space = TranslateXmlSpace(info.Value);
                    }
                }
            }
        }
 
        private void FixupElement()
        {
            Debug.Assert(_mainNode.NodeType == XmlNodeType.Element);
 
            if (Ref.Equal(_mainNode.NamespaceURI, _atoms.Empty))
            {
                _mainNode.Prefix = _atoms.Empty;
            }
 
            if (Ref.Equal(_mainNode.Prefix, _atoms.Empty))
            {
                if (Ref.Equal(_mainNode.NamespaceURI, _scopeManager.DefaultNamespace))
                {
                    // Main Node is OK
                }
                else
                {
                    DeclareNamespace(_mainNode.NamespaceURI, _mainNode.Prefix);
                }
            }
            else
            {
                bool thisScope;
                string? nspace = _scopeManager.ResolveNamespace(_mainNode.Prefix, out thisScope);
                if (nspace != null)
                {
                    if (!Ref.Equal(_mainNode.NamespaceURI, nspace))
                    {
                        if (thisScope)
                        {    // Prefix conflict
                            _mainNode.Prefix = GetPrefixForNamespace(_mainNode.NamespaceURI);
                        }
                        else
                        {
                            DeclareNamespace(_mainNode.NamespaceURI, _mainNode.Prefix);
                        }
                    }
                }
                else
                {
                    DeclareNamespace(_mainNode.NamespaceURI, _mainNode.Prefix);
                }
            }
 
            OutputScope elementScope = _scopeManager.CurrentElementScope;
            elementScope.Prefix = _mainNode.Prefix;
        }
 
        private void FixupAttributes(int attributeCount)
        {
            for (int attr = 0; attr < attributeCount; attr++)
            {
                Debug.Assert(_attributeList[attr] is BuilderInfo);
                BuilderInfo info = (BuilderInfo)_attributeList[attr]!;
 
 
                if (Ref.Equal(info.NamespaceURI, _atoms.Empty))
                {
                    info.Prefix = _atoms.Empty;
                }
                else
                {
                    if (Ref.Equal(info.Prefix, _atoms.Empty))
                    {
                        info.Prefix = GetPrefixForNamespace(info.NamespaceURI);
                    }
                    else
                    {
                        bool thisScope;
                        string? nspace = _scopeManager.ResolveNamespace(info.Prefix, out thisScope);
                        if (nspace != null)
                        {
                            if (!Ref.Equal(info.NamespaceURI, nspace))
                            {
                                if (thisScope)
                                { // prefix conflict
                                    info.Prefix = GetPrefixForNamespace(info.NamespaceURI);
                                }
                                else
                                {
                                    DeclareNamespace(info.NamespaceURI, info.Prefix);
                                }
                            }
                        }
                        else
                        {
                            DeclareNamespace(info.NamespaceURI, info.Prefix);
                        }
                    }
                }
            }
        }
 
        private void AppendNamespaces()
        {
            for (int i = _namespaceCount - 1; i >= 0; i--)
            {
                BuilderInfo attribute = (BuilderInfo)_attributeList[NewAttribute()]!;
                attribute.Initialize((BuilderInfo)_namespaceList[i]!);
            }
        }
 
        private void AnalyzeComment()
        {
            Debug.Assert(_mainNode.NodeType == XmlNodeType.Comment);
            Debug.Assert((object?)_currentInfo == (object)_mainNode);
 
            StringBuilder? newComment = null;
            string comment = _mainNode.Value;
            bool minus = false;
            int index = 0, begin = 0;
 
            for (; index < comment.Length; index++)
            {
                switch (comment[index])
                {
                    case s_Minus:
                        if (minus)
                        {
                            if (newComment == null)
                            {
                                newComment = new StringBuilder(comment, begin, index, 2 * comment.Length);
                            }
                            else
                            {
                                newComment.Append(comment, begin, index - begin);
                            }
 
                            newComment.Append(s_SpaceMinus);
                            begin = index + 1;
                        }
                        minus = true;
                        break;
                    default:
                        minus = false;
                        break;
                }
            }
 
            if (newComment != null)
            {
                if (begin < comment.Length)
                    newComment.Append(comment, begin, comment.Length - begin);
 
                if (minus)
                    newComment.Append(s_Space);
 
                _mainNode.Value = newComment.ToString();
            }
            else if (minus)
            {
                _mainNode.ValueAppend(" ", false);
            }
        }
 
        private void AnalyzeProcessingInstruction()
        {
            Debug.Assert(_mainNode.NodeType == XmlNodeType.ProcessingInstruction || _mainNode.NodeType == XmlNodeType.XmlDeclaration);
            //Debug.Assert((object) this.currentInfo == (object) this.mainNode);
 
            StringBuilder? newPI = null;
            string pi = _mainNode.Value;
            bool question = false;
            int index = 0, begin = 0;
 
            for (; index < pi.Length; index++)
            {
                switch (pi[index])
                {
                    case s_Question:
                        question = true;
                        break;
                    case s_Greater:
                        if (question)
                        {
                            if (newPI == null)
                            {
                                newPI = new StringBuilder(pi, begin, index, 2 * pi.Length);
                            }
                            else
                            {
                                newPI.Append(pi, begin, index - begin);
                            }
                            newPI.Append(s_SpaceGreater);
                            begin = index + 1;
                        }
                        question = false;
                        break;
                    default:
                        question = false;
                        break;
                }
            }
 
            if (newPI != null)
            {
                if (begin < pi.Length)
                {
                    newPI.Append(pi, begin, pi.Length - begin);
                }
                _mainNode.Value = newPI.ToString();
            }
        }
 
        private void FinalizeRecord()
        {
            switch (_mainNode.NodeType)
            {
                case XmlNodeType.Element:
                    // Save count since FixupElement can add attribute...
                    int attributeCount = _attributeCount;
 
                    FixupElement();
                    FixupAttributes(attributeCount);
                    AnalyzeSpaceLang();
                    AppendNamespaces();
                    break;
                case XmlNodeType.Comment:
                    AnalyzeComment();
                    break;
                case XmlNodeType.ProcessingInstruction:
                    AnalyzeProcessingInstruction();
                    break;
            }
        }
 
        private int NewNamespace()
        {
            if (_namespaceCount >= _namespaceList.Count)
            {
                Debug.Assert(_namespaceCount == _namespaceList.Count);
                _namespaceList.Add(new BuilderInfo());
            }
            return _namespaceCount++;
        }
 
        private void DeclareNamespace(string nspace, string prefix)
        {
            int index = NewNamespace();
 
            Debug.Assert(_namespaceList[index] != null && _namespaceList[index] is BuilderInfo);
 
            BuilderInfo ns = (BuilderInfo)_namespaceList[index]!;
            if (prefix == _atoms.Empty)
            {
                ns.Initialize(_atoms.Empty, _atoms.Xmlns, _atoms.XmlnsNamespace);
            }
            else
            {
                ns.Initialize(_atoms.Xmlns, prefix, _atoms.XmlnsNamespace);
            }
            ns.Depth = _recordDepth;
            ns.NodeType = XmlNodeType.Attribute;
            ns.Value = nspace;
 
            _scopeManager.PushNamespace(prefix, nspace);
        }
 
        private string DeclareNewNamespace(string nspace)
        {
            string prefix = _scopeManager.GeneratePrefix(PrefixFormat);
            DeclareNamespace(nspace, prefix);
            return prefix;
        }
 
        internal string GetPrefixForNamespace(string nspace)
        {
            string? prefix;
 
            if (_scopeManager.FindPrefix(nspace, out prefix))
            {
                Debug.Assert(prefix != null && prefix.Length > 0);
                return prefix;
            }
            else
            {
                return DeclareNewNamespace(nspace);
            }
        }
 
        private static XmlSpace TranslateXmlSpace(string space)
        {
            if (space == "default")
            {
                return XmlSpace.Default;
            }
            else if (space == "preserve")
            {
                return XmlSpace.Preserve;
            }
            else
            {
                return XmlSpace.None;
            }
        }
    }
}