1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.Xml;
9 using System.Xml.XPath;
10 using System.Xml.Schema;
11 
12 namespace System.Xml.Xsl.Runtime
13 {
14     /// <summary>
15     ///                         External XmlWriter      Cached Sequence
16     /// ===================================================================================================
17     /// Multiple Trees          Merged into Entity      Multiple Trees
18     ///
19     /// Attributes              Error                   Floating
20     /// at top-level                                    Attribute
21     ///
22     /// Namespace               Error                   Floating
23     /// at top-level                                    Namespace
24     ///
25     /// Elements, Text, PI      Implicit Root           Floating
26     /// Comments at top-level                           Nodes
27     ///
28     /// Root at top-level       Ignored                 Root
29     ///
30     /// Atomic Values           Whitespace-Separated    Atomic Values
31     /// at top-level            Text Node
32     ///
33     /// Nodes By Reference      Copied                  Preserve Identity
34     /// </summary>
35     internal abstract class XmlSequenceWriter
36     {
37         /// <summary>
38         /// Start construction of a new Xml tree (document or fragment).
39         /// </summary>
StartTree(XPathNodeType rootType, IXmlNamespaceResolver nsResolver, XmlNameTable nameTable)40         public abstract XmlRawWriter StartTree(XPathNodeType rootType, IXmlNamespaceResolver nsResolver, XmlNameTable nameTable);
41 
42         /// <summary>
43         /// End construction of a new Xml tree (document or fragment).
44         /// </summary>
EndTree()45         public abstract void EndTree();
46 
47         /// <summary>
48         /// Write a top-level item by reference.
49         /// </summary>
WriteItem(XPathItem item)50         public abstract void WriteItem(XPathItem item);
51     }
52 
53 
54     /// <summary>
55     /// An implementation of XmlSequenceWriter that builds a cached XPath/XQuery sequence.
56     /// </summary>
57     internal class XmlCachedSequenceWriter : XmlSequenceWriter
58     {
59         private XmlQueryItemSequence _seqTyped;
60         private XPathDocument _doc;
61         private XmlRawWriter _writer;
62 
63         /// <summary>
64         /// Constructor.
65         /// </summary>
XmlCachedSequenceWriter()66         public XmlCachedSequenceWriter()
67         {
68             _seqTyped = new XmlQueryItemSequence();
69         }
70 
71         /// <summary>
72         /// Return the sequence after it has been fully constructed.
73         /// </summary>
74         public XmlQueryItemSequence ResultSequence
75         {
76             get { return _seqTyped; }
77         }
78 
79         /// <summary>
80         /// Start construction of a new Xml tree (document or fragment).
81         /// </summary>
StartTree(XPathNodeType rootType, IXmlNamespaceResolver nsResolver, XmlNameTable nameTable)82         public override XmlRawWriter StartTree(XPathNodeType rootType, IXmlNamespaceResolver nsResolver, XmlNameTable nameTable)
83         {
84             // Build XPathDocument
85             // If rootType != XPathNodeType.Root, then build an XQuery fragment
86             _doc = new XPathDocument(nameTable);
87             _writer = _doc.LoadFromWriter(XPathDocument.LoadFlags.AtomizeNames | (rootType == XPathNodeType.Root ? XPathDocument.LoadFlags.None : XPathDocument.LoadFlags.Fragment), string.Empty);
88             _writer.NamespaceResolver = nsResolver;
89             return _writer;
90         }
91 
92         /// <summary>
93         /// End construction of a new Xml tree (document or fragment).
94         /// </summary>
EndTree()95         public override void EndTree()
96         {
97             // Add newly constructed document to sequence
98             _writer.Close();
99             _seqTyped.Add(_doc.CreateNavigator());
100         }
101 
102         /// <summary>
103         /// Write a top-level item by reference.
104         /// </summary>
WriteItem(XPathItem item)105         public override void WriteItem(XPathItem item)
106         {
107             // Preserve identity
108             _seqTyped.AddClone(item);
109         }
110     }
111 
112 
113     /// <summary>
114     /// An implementation of XmlSequenceWriter that converts an instance of the XQuery data model into a series
115     /// of calls to XmlRawWriter.  The algorithm to do this is designed to be compatible with the rules in the
116     /// "XSLT 2.0 and XQuery 1.0 Serialization" spec.  Here are the rules we use:
117     ///   1. An exception is thrown if the top-level sequence contains attribute or namespace nodes
118     ///   2. Each atomic value in the top-level sequence is converted to text, and XmlWriter.WriteString is called
119     ///   3. A call to XmlRawWriter.WriteWhitespace(" ") is made between adjacent atomic values at the top-level
120     ///   4. All items in the top-level sequence are merged together into a single result document.
121     /// </summary>
122     internal class XmlMergeSequenceWriter : XmlSequenceWriter
123     {
124         private XmlRawWriter _xwrt;
125         private bool _lastItemWasAtomic;
126 
127         /// <summary>
128         /// Constructor.
129         /// </summary>
XmlMergeSequenceWriter(XmlRawWriter xwrt)130         public XmlMergeSequenceWriter(XmlRawWriter xwrt)
131         {
132             _xwrt = xwrt;
133             _lastItemWasAtomic = false;
134         }
135 
136         /// <summary>
137         /// Start construction of a new Xml tree (document or fragment).
138         /// </summary>
StartTree(XPathNodeType rootType, IXmlNamespaceResolver nsResolver, XmlNameTable nameTable)139         public override XmlRawWriter StartTree(XPathNodeType rootType, IXmlNamespaceResolver nsResolver, XmlNameTable nameTable)
140         {
141             if (rootType == XPathNodeType.Attribute || rootType == XPathNodeType.Namespace)
142                 throw new XslTransformException(SR.XmlIl_TopLevelAttrNmsp, string.Empty);
143 
144             // Provide a namespace resolver to the writer
145             _xwrt.NamespaceResolver = nsResolver;
146 
147             return _xwrt;
148         }
149 
150         /// <summary>
151         /// End construction of a new Xml tree (document or fragment).
152         /// </summary>
EndTree()153         public override void EndTree()
154         {
155             _lastItemWasAtomic = false;
156         }
157 
158         /// <summary>
159         /// Write a top-level item by reference.
160         /// </summary>
WriteItem(XPathItem item)161         public override void WriteItem(XPathItem item)
162         {
163             if (item.IsNode)
164             {
165                 XPathNavigator nav = item as XPathNavigator;
166 
167                 if (nav.NodeType == XPathNodeType.Attribute || nav.NodeType == XPathNodeType.Namespace)
168                     throw new XslTransformException(SR.XmlIl_TopLevelAttrNmsp, string.Empty);
169 
170                 // Copy navigator to raw writer
171                 CopyNode(nav);
172                 _lastItemWasAtomic = false;
173             }
174             else
175             {
176                 WriteString(item.Value);
177             }
178         }
179 
180         /// <summary>
181         /// Write the string value of a top-level atomic value.
182         /// </summary>
WriteString(string value)183         private void WriteString(string value)
184         {
185             if (_lastItemWasAtomic)
186             {
187                 // Insert space character between adjacent atomic values
188                 _xwrt.WriteWhitespace(" ");
189             }
190             else
191             {
192                 _lastItemWasAtomic = true;
193             }
194             _xwrt.WriteString(value);
195         }
196 
197         /// <summary>
198         /// Copy the navigator subtree to the raw writer.
199         /// </summary>
CopyNode(XPathNavigator nav)200         private void CopyNode(XPathNavigator nav)
201         {
202             XPathNodeType nodeType;
203             int iLevel = 0;
204 
205             while (true)
206             {
207                 if (CopyShallowNode(nav))
208                 {
209                     nodeType = nav.NodeType;
210                     if (nodeType == XPathNodeType.Element)
211                     {
212                         // Copy attributes
213                         if (nav.MoveToFirstAttribute())
214                         {
215                             do
216                             {
217                                 CopyShallowNode(nav);
218                             }
219                             while (nav.MoveToNextAttribute());
220                             nav.MoveToParent();
221                         }
222 
223                         // Copy namespaces in document order (navigator returns them in reverse document order)
224                         XPathNamespaceScope nsScope = (iLevel == 0) ? XPathNamespaceScope.ExcludeXml : XPathNamespaceScope.Local;
225                         if (nav.MoveToFirstNamespace(nsScope))
226                         {
227                             CopyNamespaces(nav, nsScope);
228                             nav.MoveToParent();
229                         }
230 
231                         _xwrt.StartElementContent();
232                     }
233 
234                     // If children exist, move down to next level
235                     if (nav.MoveToFirstChild())
236                     {
237                         iLevel++;
238                         continue;
239                     }
240                     else
241                     {
242                         // EndElement
243                         if (nav.NodeType == XPathNodeType.Element)
244                             _xwrt.WriteEndElement(nav.Prefix, nav.LocalName, nav.NamespaceURI);
245                     }
246                 }
247 
248                 // No children
249                 while (true)
250                 {
251                     if (iLevel == 0)
252                     {
253                         // The entire subtree has been copied
254                         return;
255                     }
256 
257                     if (nav.MoveToNext())
258                     {
259                         // Found a sibling, so break to outer loop
260                         break;
261                     }
262 
263                     // No siblings, so move up to previous level
264                     iLevel--;
265                     nav.MoveToParent();
266 
267                     // EndElement
268                     if (nav.NodeType == XPathNodeType.Element)
269                         _xwrt.WriteFullEndElement(nav.Prefix, nav.LocalName, nav.NamespaceURI);
270                 }
271             }
272         }
273 
274         /// <summary>
275         /// Begin shallow copy of the specified node to the writer.  Returns true if the node might have content.
276         /// </summary>
CopyShallowNode(XPathNavigator nav)277         private bool CopyShallowNode(XPathNavigator nav)
278         {
279             bool mayHaveChildren = false;
280 
281             switch (nav.NodeType)
282             {
283                 case XPathNodeType.Element:
284                     _xwrt.WriteStartElement(nav.Prefix, nav.LocalName, nav.NamespaceURI);
285                     mayHaveChildren = true;
286                     break;
287 
288                 case XPathNodeType.Attribute:
289                     _xwrt.WriteStartAttribute(nav.Prefix, nav.LocalName, nav.NamespaceURI);
290                     _xwrt.WriteString(nav.Value);
291                     _xwrt.WriteEndAttribute();
292                     break;
293 
294                 case XPathNodeType.Text:
295                     _xwrt.WriteString(nav.Value);
296                     break;
297 
298                 case XPathNodeType.SignificantWhitespace:
299                 case XPathNodeType.Whitespace:
300                     _xwrt.WriteWhitespace(nav.Value);
301                     break;
302 
303                 case XPathNodeType.Root:
304                     mayHaveChildren = true;
305                     break;
306 
307                 case XPathNodeType.Comment:
308                     _xwrt.WriteComment(nav.Value);
309                     break;
310 
311                 case XPathNodeType.ProcessingInstruction:
312                     _xwrt.WriteProcessingInstruction(nav.LocalName, nav.Value);
313                     break;
314 
315                 case XPathNodeType.Namespace:
316                     _xwrt.WriteNamespaceDeclaration(nav.LocalName, nav.Value);
317                     break;
318 
319                 default:
320                     Debug.Assert(false);
321                     break;
322             }
323 
324             return mayHaveChildren;
325         }
326 
327         /// <summary>
328         /// Copy all or some (which depends on nsScope) of the namespaces on the navigator's current node to the
329         /// raw writer.
330         /// </summary>
CopyNamespaces(XPathNavigator nav, XPathNamespaceScope nsScope)331         private void CopyNamespaces(XPathNavigator nav, XPathNamespaceScope nsScope)
332         {
333             string prefix = nav.LocalName;
334             string ns = nav.Value;
335 
336             if (nav.MoveToNextNamespace(nsScope))
337             {
338                 CopyNamespaces(nav, nsScope);
339             }
340 
341             _xwrt.WriteNamespaceDeclaration(prefix, ns);
342         }
343     }
344 }
345