1 //------------------------------------------------------------------------------
2 // <copyright file="XmlSequenceWriter.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">[....]</owner>
6 //------------------------------------------------------------------------------
8 using System.Collections.Generic;
9 using System.Diagnostics;
11 using System.Xml.XPath;
12 using System.Xml.Schema;
14 namespace System.Xml.Xsl.Runtime {
15 using Res = System.Xml.Utils.Res;
18 /// External XmlWriter Cached Sequence
19 /// ===================================================================================================
20 /// Multiple Trees Merged into Entity Multiple Trees
22 /// Attributes Error Floating
23 /// at top-level Attribute
25 /// Namespace Error Floating
26 /// at top-level Namespace
28 /// Elements, Text, PI Implicit Root Floating
29 /// Comments at top-level Nodes
31 /// Root at top-level Ignored Root
33 /// Atomic Values Whitespace-Separated Atomic Values
34 /// at top-level Text Node
36 /// Nodes By Reference Copied Preserve Identity
38 internal abstract class XmlSequenceWriter {
40 /// Start construction of a new Xml tree (document or fragment).
42 public abstract XmlRawWriter StartTree(XPathNodeType rootType, IXmlNamespaceResolver nsResolver, XmlNameTable nameTable);
45 /// End construction of a new Xml tree (document or fragment).
47 public abstract void EndTree();
50 /// Write a top-level item by reference.
52 public abstract void WriteItem(XPathItem item);
57 /// An implementation of XmlSequenceWriter that builds a cached XPath/XQuery sequence.
59 internal class XmlCachedSequenceWriter : XmlSequenceWriter {
60 private XmlQueryItemSequence seqTyped;
61 private XPathDocument doc;
62 private XmlRawWriter writer;
67 public XmlCachedSequenceWriter() {
68 this.seqTyped = new XmlQueryItemSequence();
72 /// Return the sequence after it has been fully constructed.
74 public XmlQueryItemSequence ResultSequence {
75 get { return this.seqTyped; }
79 /// Start construction of a new Xml tree (document or fragment).
81 public override XmlRawWriter StartTree(XPathNodeType rootType, IXmlNamespaceResolver nsResolver, XmlNameTable nameTable) {
82 // Build XPathDocument
83 // If rootType != XPathNodeType.Root, then build an XQuery fragment
84 this.doc = new XPathDocument(nameTable);
85 this.writer = doc.LoadFromWriter(XPathDocument.LoadFlags.AtomizeNames | (rootType == XPathNodeType.Root ? XPathDocument.LoadFlags.None : XPathDocument.LoadFlags.Fragment), string.Empty);
86 this.writer.NamespaceResolver = nsResolver;
91 /// End construction of a new Xml tree (document or fragment).
93 public override void EndTree() {
94 // Add newly constructed document to sequence
96 this.seqTyped.Add(this.doc.CreateNavigator());
100 /// Write a top-level item by reference.
102 public override void WriteItem(XPathItem item) {
104 this.seqTyped.AddClone(item);
110 /// An implementation of XmlSequenceWriter that converts an instance of the XQuery data model into a series
111 /// of calls to XmlRawWriter. The algorithm to do this is designed to be compatible with the rules in the
112 /// "XSLT 2.0 and XQuery 1.0 Serialization" spec. Here are the rules we use:
113 /// 1. An exception is thrown if the top-level sequence contains attribute or namespace nodes
114 /// 2. Each atomic value in the top-level sequence is converted to text, and XmlWriter.WriteString is called
115 /// 3. A call to XmlRawWriter.WriteWhitespace(" ") is made between adjacent atomic values at the top-level
116 /// 4. All items in the top-level sequence are merged together into a single result document.
118 internal class XmlMergeSequenceWriter : XmlSequenceWriter {
119 private XmlRawWriter xwrt;
120 private bool lastItemWasAtomic;
125 public XmlMergeSequenceWriter(XmlRawWriter xwrt) {
127 this.lastItemWasAtomic = false;
131 /// Start construction of a new Xml tree (document or fragment).
133 public override XmlRawWriter StartTree(XPathNodeType rootType, IXmlNamespaceResolver nsResolver, XmlNameTable nameTable) {
134 if (rootType == XPathNodeType.Attribute || rootType == XPathNodeType.Namespace)
135 throw new XslTransformException(Res.XmlIl_TopLevelAttrNmsp, string.Empty);
137 // Provide a namespace resolver to the writer
138 this.xwrt.NamespaceResolver = nsResolver;
144 /// End construction of a new Xml tree (document or fragment).
146 public override void EndTree() {
147 this.lastItemWasAtomic = false;
151 /// Write a top-level item by reference.
153 public override void WriteItem(XPathItem item) {
155 XPathNavigator nav = item as XPathNavigator;
157 if (nav.NodeType == XPathNodeType.Attribute || nav.NodeType == XPathNodeType.Namespace)
158 throw new XslTransformException(Res.XmlIl_TopLevelAttrNmsp, string.Empty);
160 // Copy navigator to raw writer
162 this.lastItemWasAtomic = false;
165 WriteString(item.Value);
170 /// Write the string value of a top-level atomic value.
172 private void WriteString(string value) {
173 if (this.lastItemWasAtomic) {
174 // Insert space character between adjacent atomic values
175 this.xwrt.WriteWhitespace(" ");
178 this.lastItemWasAtomic = true;
180 this.xwrt.WriteString(value);
184 /// Copy the navigator subtree to the raw writer.
186 private void CopyNode(XPathNavigator nav) {
187 XPathNodeType nodeType;
191 if (CopyShallowNode(nav)) {
192 nodeType = nav.NodeType;
193 if (nodeType == XPathNodeType.Element) {
195 if (nav.MoveToFirstAttribute()) {
197 CopyShallowNode(nav);
199 while (nav.MoveToNextAttribute());
203 // Copy namespaces in document order (navigator returns them in reverse document order)
204 XPathNamespaceScope nsScope = (iLevel == 0) ? XPathNamespaceScope.ExcludeXml : XPathNamespaceScope.Local;
205 if (nav.MoveToFirstNamespace(nsScope)) {
206 CopyNamespaces(nav, nsScope);
210 this.xwrt.StartElementContent();
213 // If children exist, move down to next level
214 if (nav.MoveToFirstChild()) {
220 if (nav.NodeType == XPathNodeType.Element)
221 this.xwrt.WriteEndElement(nav.Prefix, nav.LocalName, nav.NamespaceURI);
228 // The entire subtree has been copied
232 if (nav.MoveToNext()) {
233 // Found a sibling, so break to outer loop
237 // No siblings, so move up to previous level
242 if (nav.NodeType == XPathNodeType.Element)
243 this.xwrt.WriteFullEndElement(nav.Prefix, nav.LocalName, nav.NamespaceURI);
249 /// Begin shallow copy of the specified node to the writer. Returns true if the node might have content.
251 private bool CopyShallowNode(XPathNavigator nav) {
252 bool mayHaveChildren = false;
254 switch (nav.NodeType) {
255 case XPathNodeType.Element:
256 this.xwrt.WriteStartElement(nav.Prefix, nav.LocalName, nav.NamespaceURI);
257 mayHaveChildren = true;
260 case XPathNodeType.Attribute:
261 this.xwrt.WriteStartAttribute(nav.Prefix, nav.LocalName, nav.NamespaceURI);
262 this.xwrt.WriteString(nav.Value);
263 this.xwrt.WriteEndAttribute();
266 case XPathNodeType.Text:
267 this.xwrt.WriteString(nav.Value);
270 case XPathNodeType.SignificantWhitespace:
271 case XPathNodeType.Whitespace:
272 this.xwrt.WriteWhitespace(nav.Value);
275 case XPathNodeType.Root:
276 mayHaveChildren = true;
279 case XPathNodeType.Comment:
280 this.xwrt.WriteComment(nav.Value);
283 case XPathNodeType.ProcessingInstruction:
284 this.xwrt.WriteProcessingInstruction(nav.LocalName, nav.Value);
287 case XPathNodeType.Namespace:
288 this.xwrt.WriteNamespaceDeclaration(nav.LocalName, nav.Value);
296 return mayHaveChildren;
300 /// Copy all or some (which depends on nsScope) of the namespaces on the navigator's current node to the
303 private void CopyNamespaces(XPathNavigator nav, XPathNamespaceScope nsScope) {
304 string prefix = nav.LocalName;
305 string ns = nav.Value;
307 if (nav.MoveToNextNamespace(nsScope)) {
308 CopyNamespaces(nav, nsScope);
311 this.xwrt.WriteNamespaceDeclaration(prefix, ns);