File: System\Xml\Xsl\Xslt\Focus.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.Generic;
using System.Diagnostics;
using System.Xml.Xsl.Qil;
using System.Xml.Xsl.XPath;
using T = System.Xml.Xsl.XmlQueryTypeFactory;
 
namespace System.Xml.Xsl.Xslt
{
    // <spec>http://www.w3.org/TR/xslt20/#dt-singleton-focus</spec>
    internal enum SingletonFocusType
    {
        // No context set
        // Used to prevent bugs
        None,
 
        // Document node of the document containing the initial context node
        // Used while compiling global variables and params
        InitialDocumentNode,
 
        // Initial context node for the transformation
        // Used while compiling initial apply-templates
        InitialContextNode,
 
        // Context node is specified by iterator
        // Used while compiling keys
        Iterator,
    }
 
    internal struct SingletonFocus : IFocus
    {
        private readonly XPathQilFactory _f;
        private SingletonFocusType _focusType;
        private QilIterator? _current;
 
        public SingletonFocus(XPathQilFactory f)
        {
            _f = f;
            _focusType = SingletonFocusType.None;
            _current = null;
        }
 
        public void SetFocus(SingletonFocusType focusType)
        {
            Debug.Assert(focusType != SingletonFocusType.Iterator);
            _focusType = focusType;
        }
 
        public void SetFocus(QilIterator? current)
        {
            if (current != null)
            {
                _focusType = SingletonFocusType.Iterator;
                _current = current;
            }
            else
            {
                _focusType = SingletonFocusType.None;
                _current = null;
            }
        }
 
        [Conditional("DEBUG")]
        private void CheckFocus()
        {
            Debug.Assert(_focusType != SingletonFocusType.None, "Focus is not set, call SetFocus first");
        }
 
        public QilNode GetCurrent()
        {
            CheckFocus();
            switch (_focusType)
            {
                case SingletonFocusType.InitialDocumentNode: return _f.Root(_f.XmlContext());
                case SingletonFocusType.InitialContextNode: return _f.XmlContext();
                default:
                    Debug.Assert(_focusType == SingletonFocusType.Iterator && _current != null, "Unexpected singleton focus type");
                    return _current;
            }
        }
 
        public QilNode GetPosition()
        {
            CheckFocus();
            return _f.Double(1);
        }
 
        public QilNode GetLast()
        {
            CheckFocus();
            return _f.Double(1);
        }
    }
 
    internal struct FunctionFocus : IFocus
    {
        private bool _isSet;
        private QilParameter? _current, _position, _last;
 
        public void StartFocus(IList<QilNode> args, XslFlags flags)
        {
            Debug.Assert(!IsFocusSet, "Focus was already set");
            int argNum = 0;
            if ((flags & XslFlags.Current) != 0)
            {
                _current = (QilParameter)args[argNum++];
                Debug.Assert(_current.Name!.NamespaceUri == XmlReservedNs.NsXslDebug && _current.Name.LocalName == "current");
            }
            if ((flags & XslFlags.Position) != 0)
            {
                _position = (QilParameter)args[argNum++];
                Debug.Assert(_position.Name!.NamespaceUri == XmlReservedNs.NsXslDebug && _position.Name.LocalName == "position");
            }
            if ((flags & XslFlags.Last) != 0)
            {
                _last = (QilParameter)args[argNum++];
                Debug.Assert(_last.Name!.NamespaceUri == XmlReservedNs.NsXslDebug && _last.Name.LocalName == "last");
            }
            _isSet = true;
        }
        public void StopFocus()
        {
            Debug.Assert(IsFocusSet, "Focus was not set");
            _isSet = false;
            _current = _position = _last = null;
        }
        public bool IsFocusSet
        {
            get { return _isSet; }
        }
 
        public QilNode GetCurrent()
        {
            Debug.Assert(_current != null, "Naked current() is not expected in this function");
            return _current;
        }
 
        public QilNode GetPosition()
        {
            Debug.Assert(_position != null, "Naked position() is not expected in this function");
            return _position;
        }
 
        public QilNode GetLast()
        {
            Debug.Assert(_last != null, "Naked last() is not expected in this function");
            return _last;
        }
    }
 
    internal struct LoopFocus : IFocus
    {
        private readonly XPathQilFactory _f;
        private QilIterator? _current, _cached, _last;
 
        public LoopFocus(XPathQilFactory f)
        {
            _f = f;
            _current = _cached = _last = null;
        }
 
        public void SetFocus(QilIterator current)
        {
            _current = current;
            _cached = _last = null;
        }
 
        public bool IsFocusSet
        {
            get { return _current != null; }
        }
 
        public QilNode? GetCurrent()
        {
            return _current;
        }
 
        public QilNode GetPosition()
        {
            return _f.XsltConvert(_f.PositionOf(_current!), T.DoubleX);
        }
 
        public QilNode GetLast() =>
            // Create a let that will be fixed up later in ConstructLoop or by LastFixupVisitor
            _last ??= _f.Let(_f.Double(0));
 
        public void EnsureCache()
        {
            if (_cached == null)
            {
                _cached = _f.Let(_current!.Binding!);
                _current.Binding = _cached;
            }
        }
 
        public void Sort(QilNode? sortKeys)
        {
            if (sortKeys != null)
            {
                // If sorting is required, cache the input node-set to support last() within sort key expressions
                EnsureCache();
                // The rest of the loop content must be compiled in the context of already sorted node-set
                _current = _f.For(_f.Sort(_current!, sortKeys));
            }
        }
 
        public QilLoop ConstructLoop(QilNode body)
        {
            QilLoop result;
            if (_last != null)
            {
                // last() encountered either in the sort keys or in the body of the current loop
                EnsureCache();
                _last.Binding = _f.XsltConvert(_f.Length(_cached!), T.DoubleX);
            }
 
            result = _f.BaseFactory.Loop(_current!, body);
            if (_last != null)
            {
                result = _f.BaseFactory.Loop(_last, result);
            }
            if (_cached != null)
            {
                result = _f.BaseFactory.Loop(_cached, result);
            }
            return result;
        }
    }
}