| File: System\Xml\Xsl\Runtime\XmlSequenceWriter.cs | Web Access |
| Project: src\runtime\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); } } }