File: System\Xml\Xsl\Runtime\XmlSequenceWriter.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.Generic;
using System.Diagnostics;
using System.Xml;
using System.Xml.Schema;
using System.Xml.XPath;
 
namespace System.Xml.Xsl.Runtime
{
    /// <summary>
    ///                         External XmlWriter      Cached Sequence
    /// ===================================================================================================
    /// Multiple Trees          Merged into Entity      Multiple Trees
    ///
    /// Attributes              Error                   Floating
    /// at top-level                                    Attribute
    ///
    /// Namespace               Error                   Floating
    /// at top-level                                    Namespace
    ///
    /// Elements, Text, PI      Implicit Root           Floating
    /// Comments at top-level                           Nodes
    ///
    /// Root at top-level       Ignored                 Root
    ///
    /// Atomic Values           Whitespace-Separated    Atomic Values
    /// at top-level            Text Node
    ///
    /// Nodes By Reference      Copied                  Preserve Identity
    /// </summary>
    internal abstract class XmlSequenceWriter
    {
        /// <summary>
        /// Start construction of a new Xml tree (document or fragment).
        /// </summary>
        public abstract XmlRawWriter StartTree(XPathNodeType rootType, IXmlNamespaceResolver? nsResolver, XmlNameTable nameTable);
 
        /// <summary>
        /// End construction of a new Xml tree (document or fragment).
        /// </summary>
        public abstract void EndTree();
 
        /// <summary>
        /// Write a top-level item by reference.
        /// </summary>
        public abstract void WriteItem(XPathItem item);
    }
 
 
    /// <summary>
    /// An implementation of XmlSequenceWriter that builds a cached XPath/XQuery sequence.
    /// </summary>
    internal sealed class XmlCachedSequenceWriter : XmlSequenceWriter
    {
        private readonly XmlQueryItemSequence _seqTyped;
        private XPathDocument? _doc;
        private XmlRawWriter? _writer;
 
        /// <summary>
        /// Constructor.
        /// </summary>
        public XmlCachedSequenceWriter()
        {
            _seqTyped = new XmlQueryItemSequence();
        }
 
        /// <summary>
        /// Return the sequence after it has been fully constructed.
        /// </summary>
        public XmlQueryItemSequence ResultSequence
        {
            get { return _seqTyped; }
        }
 
        /// <summary>
        /// Start construction of a new Xml tree (document or fragment).
        /// </summary>
        public override XmlRawWriter StartTree(XPathNodeType rootType, IXmlNamespaceResolver? nsResolver, XmlNameTable nameTable)
        {
            // Build XPathDocument
            // If rootType != XPathNodeType.Root, then build an XQuery fragment
            _doc = new XPathDocument(nameTable);
            _writer = _doc.LoadFromWriter(XPathDocument.LoadFlags.AtomizeNames | (rootType == XPathNodeType.Root ? XPathDocument.LoadFlags.None : XPathDocument.LoadFlags.Fragment), string.Empty);
            _writer.NamespaceResolver = nsResolver;
            return _writer;
        }
 
        /// <summary>
        /// End construction of a new Xml tree (document or fragment).
        /// </summary>
        public override void EndTree()
        {
            // Add newly constructed document to sequence
            _writer!.Close();
            _seqTyped.Add(_doc!.CreateNavigator());
        }
 
        /// <summary>
        /// Write a top-level item by reference.
        /// </summary>
        public override void WriteItem(XPathItem item)
        {
            // Preserve identity
            _seqTyped.AddClone(item);
        }
    }
 
 
    /// <summary>
    /// An implementation of XmlSequenceWriter that converts an instance of the XQuery data model into a series
    /// of calls to XmlRawWriter.  The algorithm to do this is designed to be compatible with the rules in the
    /// "XSLT 2.0 and XQuery 1.0 Serialization" spec.  Here are the rules we use:
    ///   1. An exception is thrown if the top-level sequence contains attribute or namespace nodes
    ///   2. Each atomic value in the top-level sequence is converted to text, and XmlWriter.WriteString is called
    ///   3. A call to XmlRawWriter.WriteWhitespace(" ") is made between adjacent atomic values at the top-level
    ///   4. All items in the top-level sequence are merged together into a single result document.
    /// </summary>
    internal sealed class XmlMergeSequenceWriter : XmlSequenceWriter
    {
        private readonly XmlRawWriter _xwrt;
        private bool _lastItemWasAtomic;
 
        /// <summary>
        /// Constructor.
        /// </summary>
        public XmlMergeSequenceWriter(XmlRawWriter xwrt)
        {
            _xwrt = xwrt;
            _lastItemWasAtomic = false;
        }
 
        /// <summary>
        /// Start construction of a new Xml tree (document or fragment).
        /// </summary>
        public override XmlRawWriter StartTree(XPathNodeType rootType, IXmlNamespaceResolver? nsResolver, XmlNameTable nameTable)
        {
            if (rootType == XPathNodeType.Attribute || rootType == XPathNodeType.Namespace)
                throw new XslTransformException(SR.XmlIl_TopLevelAttrNmsp, string.Empty);
 
            // Provide a namespace resolver to the writer
            _xwrt.NamespaceResolver = nsResolver;
 
            return _xwrt;
        }
 
        /// <summary>
        /// End construction of a new Xml tree (document or fragment).
        /// </summary>
        public override void EndTree()
        {
            _lastItemWasAtomic = false;
        }
 
        /// <summary>
        /// Write a top-level item by reference.
        /// </summary>
        public override void WriteItem(XPathItem item)
        {
            if (item.IsNode)
            {
                XPathNavigator nav = (item as XPathNavigator)!;
 
                if (nav.NodeType == XPathNodeType.Attribute || nav.NodeType == XPathNodeType.Namespace)
                    throw new XslTransformException(SR.XmlIl_TopLevelAttrNmsp, string.Empty);
 
                // Copy navigator to raw writer
                CopyNode(nav);
                _lastItemWasAtomic = false;
            }
            else
            {
                WriteString(item.Value);
            }
        }
 
        /// <summary>
        /// Write the string value of a top-level atomic value.
        /// </summary>
        private void WriteString(string value)
        {
            if (_lastItemWasAtomic)
            {
                // Insert space character between adjacent atomic values
                _xwrt.WriteWhitespace(" ");
            }
            else
            {
                _lastItemWasAtomic = true;
            }
            _xwrt.WriteString(value);
        }
 
        /// <summary>
        /// Copy the navigator subtree to the raw writer.
        /// </summary>
        private void CopyNode(XPathNavigator nav)
        {
            XPathNodeType nodeType;
            int iLevel = 0;
 
            while (true)
            {
                if (CopyShallowNode(nav))
                {
                    nodeType = nav.NodeType;
                    if (nodeType == XPathNodeType.Element)
                    {
                        // Copy attributes
                        if (nav.MoveToFirstAttribute())
                        {
                            do
                            {
                                CopyShallowNode(nav);
                            }
                            while (nav.MoveToNextAttribute());
                            nav.MoveToParent();
                        }
 
                        // Copy namespaces in document order (navigator returns them in reverse document order)
                        XPathNamespaceScope nsScope = (iLevel == 0) ? XPathNamespaceScope.ExcludeXml : XPathNamespaceScope.Local;
                        if (nav.MoveToFirstNamespace(nsScope))
                        {
                            CopyNamespaces(nav, nsScope);
                            nav.MoveToParent();
                        }
 
                        _xwrt.StartElementContent();
                    }
 
                    // If children exist, move down to next level
                    if (nav.MoveToFirstChild())
                    {
                        iLevel++;
                        continue;
                    }
                    else
                    {
                        // EndElement
                        if (nav.NodeType == XPathNodeType.Element)
                            _xwrt.WriteEndElement(nav.Prefix, nav.LocalName, nav.NamespaceURI);
                    }
                }
 
                // No children
                while (true)
                {
                    if (iLevel == 0)
                    {
                        // The entire subtree has been copied
                        return;
                    }
 
                    if (nav.MoveToNext())
                    {
                        // Found a sibling, so break to outer loop
                        break;
                    }
 
                    // No siblings, so move up to previous level
                    iLevel--;
                    nav.MoveToParent();
 
                    // EndElement
                    if (nav.NodeType == XPathNodeType.Element)
                        _xwrt.WriteFullEndElement(nav.Prefix, nav.LocalName, nav.NamespaceURI);
                }
            }
        }
 
        /// <summary>
        /// Begin shallow copy of the specified node to the writer.  Returns true if the node might have content.
        /// </summary>
        private bool CopyShallowNode(XPathNavigator nav)
        {
            bool mayHaveChildren = false;
 
            switch (nav.NodeType)
            {
                case XPathNodeType.Element:
                    _xwrt.WriteStartElement(nav.Prefix, nav.LocalName, nav.NamespaceURI);
                    mayHaveChildren = true;
                    break;
 
                case XPathNodeType.Attribute:
                    _xwrt.WriteStartAttribute(nav.Prefix, nav.LocalName, nav.NamespaceURI);
                    _xwrt.WriteString(nav.Value);
                    _xwrt.WriteEndAttribute();
                    break;
 
                case XPathNodeType.Text:
                    _xwrt.WriteString(nav.Value);
                    break;
 
                case XPathNodeType.SignificantWhitespace:
                case XPathNodeType.Whitespace:
                    _xwrt.WriteWhitespace(nav.Value);
                    break;
 
                case XPathNodeType.Root:
                    mayHaveChildren = true;
                    break;
 
                case XPathNodeType.Comment:
                    _xwrt.WriteComment(nav.Value);
                    break;
 
                case XPathNodeType.ProcessingInstruction:
                    _xwrt.WriteProcessingInstruction(nav.LocalName, nav.Value);
                    break;
 
                case XPathNodeType.Namespace:
                    _xwrt.WriteNamespaceDeclaration(nav.LocalName, nav.Value);
                    break;
 
                default:
                    Debug.Fail($"Unexpected node type {nav.NodeType}");
                    break;
            }
 
            return mayHaveChildren;
        }
 
        /// <summary>
        /// Copy all or some (which depends on nsScope) of the namespaces on the navigator's current node to the
        /// raw writer.
        /// </summary>
        private void CopyNamespaces(XPathNavigator nav, XPathNamespaceScope nsScope)
        {
            string prefix = nav.LocalName;
            string ns = nav.Value;
 
            if (nav.MoveToNextNamespace(nsScope))
            {
                CopyNamespaces(nav, nsScope);
            }
 
            _xwrt.WriteNamespaceDeclaration(prefix, ns);
        }
    }
}