File: System\Xml\Xsl\Runtime\XmlSequenceWriter.cs
Project: ndp\fx\src\XmlUtils\System.Data.SqlXml.csproj (System.Data.SqlXml)
//------------------------------------------------------------------------------
// <copyright file="XmlSequenceWriter.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <owner current="true" primary="true">Microsoft</owner>
//------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Schema;
 
namespace System.Xml.Xsl.Runtime {
    using Res           = System.Xml.Utils.Res;
 
    /// <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 class XmlCachedSequenceWriter : XmlSequenceWriter {
        private XmlQueryItemSequence seqTyped;
        private XPathDocument doc;
        private XmlRawWriter writer;
 
        /// <summary>
        /// Constructor.
        /// </summary>
        public XmlCachedSequenceWriter() {
            this.seqTyped = new XmlQueryItemSequence();
        }
 
        /// <summary>
        /// Return the sequence after it has been fully constructed.
        /// </summary>
        public XmlQueryItemSequence ResultSequence {
            get { return this.seqTyped; }
        }
 
        /// <summary>
        /// Start construction of a new Xml tree (document or fragment).
        /// </summary>
        [SuppressMessage("Microsoft.Security.Xml", "CA3059:UseXmlReaderForXPathDocument", Justification="Use internal constructor of XPathDocument to build a document and not to read it")]
        public override XmlRawWriter StartTree(XPathNodeType rootType, IXmlNamespaceResolver nsResolver, XmlNameTable nameTable) {
            // Build XPathDocument
            // If rootType != XPathNodeType.Root, then build an XQuery fragment
            this.doc = new XPathDocument(nameTable);
            this.writer = doc.LoadFromWriter(XPathDocument.LoadFlags.AtomizeNames | (rootType == XPathNodeType.Root ? XPathDocument.LoadFlags.None : XPathDocument.LoadFlags.Fragment), string.Empty);
            this.writer.NamespaceResolver = nsResolver;
            return this.writer;
        }
 
        /// <summary>
        /// End construction of a new Xml tree (document or fragment).
        /// </summary>
        public override void EndTree() {
            // Add newly constructed document to sequence
            this.writer.Close();
            this.seqTyped.Add(this.doc.CreateNavigator());
        }
 
        /// <summary>
        /// Write a top-level item by reference.
        /// </summary>
        public override void WriteItem(XPathItem item) {
            // Preserve identity
            this.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 class XmlMergeSequenceWriter : XmlSequenceWriter {
        private XmlRawWriter xwrt;
        private bool lastItemWasAtomic;
 
        /// <summary>
        /// Constructor.
        /// </summary>
        public XmlMergeSequenceWriter(XmlRawWriter xwrt) {
            this.xwrt = xwrt;
            this.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(Res.XmlIl_TopLevelAttrNmsp, string.Empty);
 
            // Provide a namespace resolver to the writer
            this.xwrt.NamespaceResolver = nsResolver;
 
            return this.xwrt;
        }
 
        /// <summary>
        /// End construction of a new Xml tree (document or fragment).
        /// </summary>
        public override void EndTree() {
            this.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(Res.XmlIl_TopLevelAttrNmsp, string.Empty);
 
                // Copy navigator to raw writer
                CopyNode(nav);
                this.lastItemWasAtomic = false;
            }
            else {
                WriteString(item.Value);
            }
        }
 
        /// <summary>
        /// Write the string value of a top-level atomic value.
        /// </summary>
        private void WriteString(string value) {
            if (this.lastItemWasAtomic) {
                // Insert space character between adjacent atomic values
                this.xwrt.WriteWhitespace(" ");
            }
            else {
                this.lastItemWasAtomic = true;
            }
            this.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();
                        }
 
                        this.xwrt.StartElementContent();
                    }
 
                    // If children exist, move down to next level
                    if (nav.MoveToFirstChild()) {
                        iLevel++;
                        continue;
                    }
                    else {
                        // EndElement
                        if (nav.NodeType == XPathNodeType.Element)
                            this.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)
                        this.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:
                    this.xwrt.WriteStartElement(nav.Prefix, nav.LocalName, nav.NamespaceURI);
                    mayHaveChildren = true;
                    break;
 
                case XPathNodeType.Attribute:
                    this.xwrt.WriteStartAttribute(nav.Prefix, nav.LocalName, nav.NamespaceURI);
                    this.xwrt.WriteString(nav.Value);
                    this.xwrt.WriteEndAttribute();
                    break;
 
                case XPathNodeType.Text:
                    this.xwrt.WriteString(nav.Value);
                    break;
 
                case XPathNodeType.SignificantWhitespace:
                case XPathNodeType.Whitespace:
                    this.xwrt.WriteWhitespace(nav.Value);
                    break;
 
                case XPathNodeType.Root:
                    mayHaveChildren = true;
                    break;
 
                case XPathNodeType.Comment:
                    this.xwrt.WriteComment(nav.Value);
                    break;
 
                case XPathNodeType.ProcessingInstruction:
                    this.xwrt.WriteProcessingInstruction(nav.LocalName, nav.Value);
                    break;
 
                case XPathNodeType.Namespace:
                    this.xwrt.WriteNamespaceDeclaration(nav.LocalName, nav.Value);
                    break;
 
                default:
                    Debug.Assert(false);
                    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);
            }
 
            this.xwrt.WriteNamespaceDeclaration(prefix, ns);
        }
    }
}