File: System\Xml\Xsl\XsltOld\RootAction.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.Globalization;
using System.Security;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Xsl.Runtime;
using MS.Internal.Xml.XPath;
 
namespace System.Xml.Xsl.XsltOld
{
    internal sealed class Key
    {
        private readonly XmlQualifiedName _name;
        private readonly int _matchKey;
        private readonly int _useKey;
        private ArrayList? _keyNodes;
 
        public Key(XmlQualifiedName name, int matchkey, int usekey)
        {
            _name = name;
            _matchKey = matchkey;
            _useKey = usekey;
            _keyNodes = null;
        }
 
        public XmlQualifiedName Name { get { return _name; } }
        public int MatchKey { get { return _matchKey; } }
        public int UseKey { get { return _useKey; } }
 
        public void AddKey(XPathNavigator root, Hashtable table)
        {
            _keyNodes ??= new ArrayList();
            _keyNodes.Add(new DocumentKeyList(root, table));
        }
 
        public Hashtable? GetKeys(XPathNavigator root)
        {
            if (_keyNodes != null)
            {
                for (int i = 0; i < _keyNodes.Count; i++)
                {
                    if (((DocumentKeyList)_keyNodes[i]!).RootNav.IsSamePosition(root))
                    {
                        return ((DocumentKeyList)_keyNodes[i]!).KeyTable;
                    }
                }
            }
            return null;
        }
 
        public Key Clone()
        {
            return new Key(_name, _matchKey, _useKey);
        }
    }
 
    internal readonly struct DocumentKeyList
    {
        private readonly XPathNavigator _rootNav;
        private readonly Hashtable _keyTable;
 
        public DocumentKeyList(XPathNavigator rootNav, Hashtable keyTable)
        {
            _rootNav = rootNav;
            _keyTable = keyTable;
        }
 
        public XPathNavigator RootNav { get { return _rootNav; } }
        public Hashtable KeyTable { get { return _keyTable; } }
    }
 
    internal sealed class RootAction : TemplateBaseAction
    {
        private const int QueryInitialized = 2;
        private const int RootProcessed = 3;
 
        private readonly Hashtable _attributeSetTable = new Hashtable();
        private readonly Hashtable _decimalFormatTable = new Hashtable();
        private List<Key>? _keyList;
        private XsltOutput? _output;
 
        internal XsltOutput Output => _output ??= new XsltOutput();
 
        /*
         * Compile
         */
        internal override void Compile(Compiler compiler)
        {
            CompileDocument(compiler, /*inInclude*/ false);
        }
 
        internal void InsertKey(XmlQualifiedName name, int MatchKey, int UseKey)
        {
            _keyList ??= new List<Key>();
            _keyList.Add(new Key(name, MatchKey, UseKey));
        }
 
        internal AttributeSetAction GetAttributeSet(XmlQualifiedName name)
        {
            AttributeSetAction? action = (AttributeSetAction?)_attributeSetTable[name];
            if (action == null)
            {
                throw XsltException.Create(SR.Xslt_NoAttributeSet, name.ToString());
            }
            return action;
        }
 
 
        public void PorcessAttributeSets(Stylesheet rootStylesheet)
        {
            MirgeAttributeSets(rootStylesheet);
 
            // As we mentioned we need to invert all lists.
            foreach (AttributeSetAction attSet in _attributeSetTable.Values)
            {
                attSet.containedActions?.Reverse();
            }
 
            //  ensures there are no cycles in the attribute-sets use dfs marking method
            CheckAttributeSets_RecurceInList(new Hashtable(), _attributeSetTable.Keys);
        }
 
        private void MirgeAttributeSets(Stylesheet stylesheet)
        {
            // mirge stylesheet.AttributeSetTable to this.AttributeSetTable
 
            if (stylesheet.AttributeSetTable != null)
            {
                foreach (AttributeSetAction srcAttSet in stylesheet.AttributeSetTable.Values)
                {
                    ArrayList? srcAttList = srcAttSet.containedActions;
                    AttributeSetAction? dstAttSet = (AttributeSetAction?)_attributeSetTable[srcAttSet.Name!];
                    if (dstAttSet == null)
                    {
                        dstAttSet = new AttributeSetAction();
                        {
                            dstAttSet.name = srcAttSet.Name;
                            dstAttSet.containedActions = new ArrayList();
                        }
                        _attributeSetTable[srcAttSet.Name!] = dstAttSet;
                    }
 
                    ArrayList? dstAttList = dstAttSet.containedActions;
                    // We adding attributes in reverse order for purpuse. In the mirged list most important attset should go last one
                    // so we'll need to invert dstAttList finaly.
                    if (srcAttList != null)
                    {
                        for (int src = srcAttList.Count - 1; 0 <= src; src--)
                        {
                            // We can ignore duplicate attributes here.
                            dstAttList!.Add(srcAttList[src]);
                        }
                    }
                }
            }
 
            foreach (Stylesheet importedStylesheet in stylesheet.Imports)
            {
                MirgeAttributeSets(importedStylesheet);
            }
        }
 
        private void CheckAttributeSets_RecurceInList(Hashtable markTable, ICollection setQNames)
        {
            const string PROCESSING = "P";
            const string DONE = "D";
 
            foreach (XmlQualifiedName qname in setQNames)
            {
                object? mark = markTable[qname];
                if (mark == (object)PROCESSING)
                {
                    throw XsltException.Create(SR.Xslt_CircularAttributeSet, qname.ToString());
                }
                else if (mark == (object)DONE)
                {
                    continue; // optimization: we already investigated this attribute-set.
                }
                else
                {
                    Debug.Assert(mark == null);
 
                    markTable[qname] = (object)PROCESSING;
                    CheckAttributeSets_RecurceInContainer(markTable, GetAttributeSet(qname));
                    markTable[qname] = (object)DONE;
                }
            }
        }
 
        private void CheckAttributeSets_RecurceInContainer(Hashtable markTable, ContainerAction container)
        {
            if (container.containedActions == null)
            {
                return;
            }
            foreach (Action action in container.containedActions)
            {
                if (action is UseAttributeSetsAction)
                {
                    CheckAttributeSets_RecurceInList(markTable, ((UseAttributeSetsAction)action).UsedSets!);
                }
                else if (action is ContainerAction)
                {
                    CheckAttributeSets_RecurceInContainer(markTable, (ContainerAction)action);
                }
            }
        }
 
        internal void AddDecimalFormat(XmlQualifiedName name, DecimalFormat formatinfo)
        {
            DecimalFormat? exist = (DecimalFormat?)_decimalFormatTable[name];
            if (exist != null)
            {
                NumberFormatInfo info = exist.info;
                NumberFormatInfo newinfo = formatinfo.info;
                if (info.NumberDecimalSeparator != newinfo.NumberDecimalSeparator ||
                    info.NumberGroupSeparator != newinfo.NumberGroupSeparator ||
                    info.PositiveInfinitySymbol != newinfo.PositiveInfinitySymbol ||
                    info.NegativeSign != newinfo.NegativeSign ||
                    info.NaNSymbol != newinfo.NaNSymbol ||
                    info.PercentSymbol != newinfo.PercentSymbol ||
                    info.PerMilleSymbol != newinfo.PerMilleSymbol ||
                    exist.zeroDigit != formatinfo.zeroDigit ||
                    exist.digit != formatinfo.digit ||
                    exist.patternSeparator != formatinfo.patternSeparator
                )
                {
                    throw XsltException.Create(SR.Xslt_DupDecimalFormat, name.ToString());
                }
            }
            _decimalFormatTable[name] = formatinfo;
        }
 
        internal DecimalFormat? GetDecimalFormat(XmlQualifiedName name)
        {
            return _decimalFormatTable[name] as DecimalFormat;
        }
 
        internal List<Key>? KeyList
        {
            get { return _keyList; }
        }
 
        internal override void Execute(Processor processor, ActionFrame frame)
        {
            Debug.Assert(processor != null && frame != null);
 
            switch (frame.State)
            {
                case Initialized:
                    frame.AllocateVariables(variableCount);
                    XPathNavigator root = processor.Document.Clone();
                    root.MoveToRoot();
                    frame.InitNodeSet(new XPathSingletonIterator(root));
 
                    if (this.containedActions != null && this.containedActions.Count > 0)
                    {
                        processor.PushActionFrame(frame);
                    }
                    frame.State = QueryInitialized;
                    break;
                case QueryInitialized:
                    Debug.Assert(frame.State == QueryInitialized);
                    frame.NextNode(processor);
                    Debug.Assert(Processor.IsRoot(frame.Node!));
                    if (processor.Debugger != null)
                    {
                        // this is like apply-templates, but we don't have it on stack.
                        // Pop the stack, otherwise last instruction will be on it.
                        processor.PopDebuggerStack();
                    }
                    processor.PushTemplateLookup(frame.NodeSet, /*mode:*/null, /*importsOf:*/null);
 
                    frame.State = RootProcessed;
                    break;
 
                case RootProcessed:
                    Debug.Assert(frame.State == RootProcessed);
                    frame.Finished();
                    break;
                default:
                    Debug.Fail("Invalid RootAction execution state");
                    break;
            }
        }
    }
}