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