Update Reference Sources to .NET Framework 4.6.1
[mono.git] / mcs / class / referencesource / System.Data.SqlXml / System / Xml / Xsl / Runtime / XmlSequenceWriter.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="XmlSequenceWriter.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">[....]</owner>
6 //------------------------------------------------------------------------------
7 using System;
8 using System.Collections.Generic;
9 using System.Diagnostics;
10 using System.Xml;
11 using System.Xml.XPath;
12 using System.Xml.Schema;
13
14 namespace System.Xml.Xsl.Runtime {
15     using Res           = System.Xml.Utils.Res;
16
17     /// <summary>
18     ///                         External XmlWriter      Cached Sequence
19     /// ===================================================================================================
20     /// Multiple Trees          Merged into Entity      Multiple Trees
21     ///
22     /// Attributes              Error                   Floating
23     /// at top-level                                    Attribute
24     ///
25     /// Namespace               Error                   Floating
26     /// at top-level                                    Namespace
27     ///
28     /// Elements, Text, PI      Implicit Root           Floating            
29     /// Comments at top-level                           Nodes
30     ///
31     /// Root at top-level       Ignored                 Root
32     ///
33     /// Atomic Values           Whitespace-Separated    Atomic Values
34     /// at top-level            Text Node
35     ///
36     /// Nodes By Reference      Copied                  Preserve Identity
37     /// </summary>
38     internal abstract class XmlSequenceWriter {
39         /// <summary>
40         /// Start construction of a new Xml tree (document or fragment).
41         /// </summary>
42         public abstract XmlRawWriter StartTree(XPathNodeType rootType, IXmlNamespaceResolver nsResolver, XmlNameTable nameTable);
43
44         /// <summary>
45         /// End construction of a new Xml tree (document or fragment).
46         /// </summary>
47         public abstract void EndTree();
48
49         /// <summary>
50         /// Write a top-level item by reference.
51         /// </summary>
52         public abstract void WriteItem(XPathItem item);
53     }
54
55
56     /// <summary>
57     /// An implementation of XmlSequenceWriter that builds a cached XPath/XQuery sequence.
58     /// </summary>
59     internal class XmlCachedSequenceWriter : XmlSequenceWriter {
60         private XmlQueryItemSequence seqTyped;
61         private XPathDocument doc;
62         private XmlRawWriter writer;
63
64         /// <summary>
65         /// Constructor.
66         /// </summary>
67         public XmlCachedSequenceWriter() {
68             this.seqTyped = new XmlQueryItemSequence();
69         }
70
71         /// <summary>
72         /// Return the sequence after it has been fully constructed.
73         /// </summary>
74         public XmlQueryItemSequence ResultSequence {
75             get { return this.seqTyped; }
76         }
77
78         /// <summary>
79         /// Start construction of a new Xml tree (document or fragment).
80         /// </summary>
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;
87             return this.writer;
88         }
89
90         /// <summary>
91         /// End construction of a new Xml tree (document or fragment).
92         /// </summary>
93         public override void EndTree() {
94             // Add newly constructed document to sequence
95             this.writer.Close();
96             this.seqTyped.Add(this.doc.CreateNavigator());
97         }
98
99         /// <summary>
100         /// Write a top-level item by reference.
101         /// </summary>
102         public override void WriteItem(XPathItem item) {
103             // Preserve identity
104             this.seqTyped.AddClone(item);
105         }
106     }
107
108
109     /// <summary>
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.
117     /// </summary>
118     internal class XmlMergeSequenceWriter : XmlSequenceWriter {
119         private XmlRawWriter xwrt;
120         private bool lastItemWasAtomic;
121
122         /// <summary>
123         /// Constructor.
124         /// </summary>
125         public XmlMergeSequenceWriter(XmlRawWriter xwrt) {
126             this.xwrt = xwrt;
127             this.lastItemWasAtomic = false;
128         }
129
130         /// <summary>
131         /// Start construction of a new Xml tree (document or fragment).
132         /// </summary>
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);
136
137             // Provide a namespace resolver to the writer
138             this.xwrt.NamespaceResolver = nsResolver;
139
140             return this.xwrt;
141         }
142
143         /// <summary>
144         /// End construction of a new Xml tree (document or fragment).
145         /// </summary>
146         public override void EndTree() {
147             this.lastItemWasAtomic = false;
148         }
149
150         /// <summary>
151         /// Write a top-level item by reference.
152         /// </summary>
153         public override void WriteItem(XPathItem item) {
154             if (item.IsNode) {
155                 XPathNavigator nav = item as XPathNavigator;
156
157                 if (nav.NodeType == XPathNodeType.Attribute || nav.NodeType == XPathNodeType.Namespace)
158                     throw new XslTransformException(Res.XmlIl_TopLevelAttrNmsp, string.Empty);
159
160                 // Copy navigator to raw writer
161                 CopyNode(nav);
162                 this.lastItemWasAtomic = false;
163             }
164             else {
165                 WriteString(item.Value);
166             }
167         }
168
169         /// <summary>
170         /// Write the string value of a top-level atomic value.
171         /// </summary>
172         private void WriteString(string value) {
173             if (this.lastItemWasAtomic) {
174                 // Insert space character between adjacent atomic values
175                 this.xwrt.WriteWhitespace(" ");
176             }
177             else {
178                 this.lastItemWasAtomic = true;
179             }
180             this.xwrt.WriteString(value);
181         }
182
183         /// <summary>
184         /// Copy the navigator subtree to the raw writer.
185         /// </summary>
186         private void CopyNode(XPathNavigator nav) {
187             XPathNodeType nodeType;
188             int iLevel = 0;
189
190             while (true) {
191                 if (CopyShallowNode(nav)) {
192                     nodeType = nav.NodeType;
193                     if (nodeType == XPathNodeType.Element) {
194                         // Copy attributes
195                         if (nav.MoveToFirstAttribute()) {
196                             do {
197                                 CopyShallowNode(nav);
198                             }
199                             while (nav.MoveToNextAttribute());
200                             nav.MoveToParent();
201                         }
202
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);
207                             nav.MoveToParent();
208                         }
209
210                         this.xwrt.StartElementContent();
211                     }
212
213                     // If children exist, move down to next level
214                     if (nav.MoveToFirstChild()) {
215                         iLevel++;
216                         continue;
217                     }
218                     else {
219                         // EndElement
220                         if (nav.NodeType == XPathNodeType.Element)
221                             this.xwrt.WriteEndElement(nav.Prefix, nav.LocalName, nav.NamespaceURI);
222                     }
223                 }
224
225                 // No children
226                 while (true) {
227                     if (iLevel == 0) {
228                         // The entire subtree has been copied
229                         return;
230                     }
231
232                     if (nav.MoveToNext()) {
233                         // Found a sibling, so break to outer loop
234                         break;
235                     }
236
237                     // No siblings, so move up to previous level
238                     iLevel--;
239                     nav.MoveToParent();
240
241                     // EndElement
242                     if (nav.NodeType == XPathNodeType.Element)
243                         this.xwrt.WriteFullEndElement(nav.Prefix, nav.LocalName, nav.NamespaceURI);
244                 }
245             }
246         }
247
248         /// <summary>
249         /// Begin shallow copy of the specified node to the writer.  Returns true if the node might have content.
250         /// </summary>
251         private bool CopyShallowNode(XPathNavigator nav) {
252             bool mayHaveChildren = false;
253
254             switch (nav.NodeType) {
255                 case XPathNodeType.Element:
256                     this.xwrt.WriteStartElement(nav.Prefix, nav.LocalName, nav.NamespaceURI);
257                     mayHaveChildren = true;
258                     break;
259
260                 case XPathNodeType.Attribute:
261                     this.xwrt.WriteStartAttribute(nav.Prefix, nav.LocalName, nav.NamespaceURI);
262                     this.xwrt.WriteString(nav.Value);
263                     this.xwrt.WriteEndAttribute();
264                     break;
265
266                 case XPathNodeType.Text:
267                     this.xwrt.WriteString(nav.Value);
268                     break;
269
270                 case XPathNodeType.SignificantWhitespace:
271                 case XPathNodeType.Whitespace:
272                     this.xwrt.WriteWhitespace(nav.Value);
273                     break;
274
275                 case XPathNodeType.Root:
276                     mayHaveChildren = true;
277                     break;
278
279                 case XPathNodeType.Comment:
280                     this.xwrt.WriteComment(nav.Value);
281                     break;
282
283                 case XPathNodeType.ProcessingInstruction:
284                     this.xwrt.WriteProcessingInstruction(nav.LocalName, nav.Value);
285                     break;
286
287                 case XPathNodeType.Namespace:
288                     this.xwrt.WriteNamespaceDeclaration(nav.LocalName, nav.Value);
289                     break;
290
291                 default:
292                     Debug.Assert(false);
293                     break;
294             }
295
296             return mayHaveChildren;
297         }
298
299         /// <summary>
300         /// Copy all or some (which depends on nsScope) of the namespaces on the navigator's current node to the
301         /// raw writer.
302         /// </summary>
303         private void CopyNamespaces(XPathNavigator nav, XPathNamespaceScope nsScope) {
304             string prefix = nav.LocalName;
305             string ns = nav.Value;
306
307             if (nav.MoveToNextNamespace(nsScope)) {
308                 CopyNamespaces(nav, nsScope);
309             }
310
311             this.xwrt.WriteNamespaceDeclaration(prefix, ns);
312         }
313     }
314 }