1 //------------------------------------------------------------------------------
2 // <copyright file="XmlTextWriter.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">Microsoft</owner>
6 //------------------------------------------------------------------------------
9 using System
.Collections
;
10 using System
.Collections
.Generic
;
13 using System
.Diagnostics
;
14 using System
.Globalization
;
15 using System
.Runtime
.Versioning
;
17 namespace System
.Xml
{
19 // Specifies formatting options for XmlTextWriter.
20 public enum Formatting
{
21 // No special formatting is done (this is the default).
24 //This option causes child elements to be indented using the Indentation and IndentChar properties.
25 // It only indents Element Content (http://www.w3.org/TR/1998/REC-xml-19980210#sec-element-content)
26 // and not Mixed Content (http://www.w3.org/TR/1998/REC-xml-19980210#sec-mixed-content)
27 // according to the XML 1.0 definitions of these terms.
31 // Represents a writer that provides fast non-cached forward-only way of generating XML streams
32 // containing XML documents that conform to the W3CExtensible Markup Language (XML) 1.0 specification
33 // and the Namespaces in XML specification.
35 [System
.ComponentModel
.EditorBrowsable(System
.ComponentModel
.EditorBrowsableState
.Never
)]
36 public class XmlTextWriter
: XmlWriter
{
42 NotDeclaredButInScope
,
43 DeclaredButNotWrittenOut
,
49 internal string prefix
;
50 internal string defaultNs
;
51 internal NamespaceState defaultNsState
;
52 internal XmlSpace xmlSpace
;
53 internal string xmlLang
;
54 internal int prevNsTop
;
55 internal int prefixCount
;
56 internal bool mixed
; // whether to pretty print the contents of this element.
58 internal void Init( int nsTop
) {
60 defaultNs
= String
.Empty
;
61 defaultNsState
= NamespaceState
.Uninitialized
;
62 xmlSpace
= XmlSpace
.None
;
71 internal string prefix
;
73 internal bool declared
;
74 internal int prevNsIndex
;
76 internal void Set( string prefix
, string ns
, bool declared
) {
79 this.declared
= declared
;
80 this.prevNsIndex
= -1;
91 // State machine is working through autocomplete
126 TextWriter textWriter
;
127 XmlTextEncoder xmlEncoder
;
131 Formatting formatting
;
132 bool indented
; // perf - faster to check a boolean.
140 // state machine for AutoComplete
146 XmlTextWriterBase64Encoder base64Encoder
;
152 SpecialAttr specialAttr
;
153 string prefixForXmlNs
;
159 Dictionary
<string, int> nsHashtable
;
163 XmlCharType xmlCharType
= XmlCharType
.Instance
;
166 // Constants and constant tables
168 const int NamespaceStackInitialSize
= 8;
170 const int MaxNamespacesWalkCount
= 3;
172 const int MaxNamespacesWalkCount
= 16;
175 static string[] stateName
= {
188 static string[] tokenName
= {
205 static readonly State
[] stateTableDefault
= {
206 // State.Start State.Prolog State.PostDTD State.Element State.Attribute State.Content State.AttrOnly State.Epilog
208 /* Token.PI */ State
.Prolog
, State
.Prolog
, State
.PostDTD
, State
.Content
, State
.Content
, State
.Content
, State
.Error
, State
.Epilog
,
209 /* Token.Doctype */ State
.PostDTD
, State
.PostDTD
, State
.Error
, State
.Error
, State
.Error
, State
.Error
, State
.Error
, State
.Error
,
210 /* Token.Comment */ State
.Prolog
, State
.Prolog
, State
.PostDTD
, State
.Content
, State
.Content
, State
.Content
, State
.Error
, State
.Epilog
,
211 /* Token.CData */ State
.Content
, State
.Content
, State
.Error
, State
.Content
, State
.Content
, State
.Content
, State
.Error
, State
.Epilog
,
212 /* Token.StartElement */ State
.Element
, State
.Element
, State
.Element
, State
.Element
, State
.Element
, State
.Element
, State
.Error
, State
.Element
,
213 /* Token.EndElement */ State
.Error
, State
.Error
, State
.Error
, State
.Content
, State
.Content
, State
.Content
, State
.Error
, State
.Error
,
214 /* Token.LongEndElement */ State
.Error
, State
.Error
, State
.Error
, State
.Content
, State
.Content
, State
.Content
, State
.Error
, State
.Error
,
215 /* Token.StartAttribute */ State
.AttrOnly
, State
.Error
, State
.Error
, State
.Attribute
, State
.Attribute
, State
.Error
, State
.Error
, State
.Error
,
216 /* Token.EndAttribute */ State
.Error
, State
.Error
, State
.Error
, State
.Error
, State
.Element
, State
.Error
, State
.Epilog
, State
.Error
,
217 /* Token.Content */ State
.Content
, State
.Content
, State
.Error
, State
.Content
, State
.Attribute
, State
.Content
, State
.Attribute
, State
.Epilog
,
218 /* Token.Base64 */ State
.Content
, State
.Content
, State
.Error
, State
.Content
, State
.Attribute
, State
.Content
, State
.Attribute
, State
.Epilog
,
219 /* Token.RawData */ State
.Prolog
, State
.Prolog
, State
.PostDTD
, State
.Content
, State
.Attribute
, State
.Content
, State
.Attribute
, State
.Epilog
,
220 /* Token.Whitespace */ State
.Prolog
, State
.Prolog
, State
.PostDTD
, State
.Content
, State
.Attribute
, State
.Content
, State
.Attribute
, State
.Epilog
,
223 static readonly State
[] stateTableDocument
= {
224 // State.Start State.Prolog State.PostDTD State.Element State.Attribute State.Content State.AttrOnly State.Epilog
226 /* Token.PI */ State
.Error
, State
.Prolog
, State
.PostDTD
, State
.Content
, State
.Content
, State
.Content
, State
.Error
, State
.Epilog
,
227 /* Token.Doctype */ State
.Error
, State
.PostDTD
, State
.Error
, State
.Error
, State
.Error
, State
.Error
, State
.Error
, State
.Error
,
228 /* Token.Comment */ State
.Error
, State
.Prolog
, State
.PostDTD
, State
.Content
, State
.Content
, State
.Content
, State
.Error
, State
.Epilog
,
229 /* Token.CData */ State
.Error
, State
.Error
, State
.Error
, State
.Content
, State
.Content
, State
.Content
, State
.Error
, State
.Error
,
230 /* Token.StartElement */ State
.Error
, State
.Element
, State
.Element
, State
.Element
, State
.Element
, State
.Element
, State
.Error
, State
.Error
,
231 /* Token.EndElement */ State
.Error
, State
.Error
, State
.Error
, State
.Content
, State
.Content
, State
.Content
, State
.Error
, State
.Error
,
232 /* Token.LongEndElement */ State
.Error
, State
.Error
, State
.Error
, State
.Content
, State
.Content
, State
.Content
, State
.Error
, State
.Error
,
233 /* Token.StartAttribute */ State
.Error
, State
.Error
, State
.Error
, State
.Attribute
, State
.Attribute
, State
.Error
, State
.Error
, State
.Error
,
234 /* Token.EndAttribute */ State
.Error
, State
.Error
, State
.Error
, State
.Error
, State
.Element
, State
.Error
, State
.Error
, State
.Error
,
235 /* Token.Content */ State
.Error
, State
.Error
, State
.Error
, State
.Content
, State
.Attribute
, State
.Content
, State
.Error
, State
.Error
,
236 /* Token.Base64 */ State
.Error
, State
.Error
, State
.Error
, State
.Content
, State
.Attribute
, State
.Content
, State
.Error
, State
.Error
,
237 /* Token.RawData */ State
.Error
, State
.Prolog
, State
.PostDTD
, State
.Content
, State
.Attribute
, State
.Content
, State
.Error
, State
.Epilog
,
238 /* Token.Whitespace */ State
.Error
, State
.Prolog
, State
.PostDTD
, State
.Content
, State
.Attribute
, State
.Content
, State
.Error
, State
.Epilog
,
244 internal XmlTextWriter() {
246 formatting
= Formatting
.None
;
250 nsStack
= new Namespace
[NamespaceStackInitialSize
];
253 stack
= new TagInfo
[10];
254 top
= 0;// 0 is an empty sentanial element
255 stack
[top
].Init( -1 );
258 stateTable
= stateTableDefault
;
259 currentState
= State
.Start
;
260 lastToken
= Token
.Empty
;
263 // Creates an instance of the XmlTextWriter class using the specified stream.
264 public XmlTextWriter(Stream w
, Encoding encoding
) : this() {
265 this.encoding
= encoding
;
266 if (encoding
!= null)
267 textWriter
= new StreamWriter(w
, encoding
);
269 textWriter
= new StreamWriter(w
);
270 xmlEncoder
= new XmlTextEncoder(textWriter
);
271 xmlEncoder
.QuoteChar
= this.quoteChar
;
274 // Creates an instance of the XmlTextWriter class using the specified file.
275 [ResourceConsumption(ResourceScope
.Machine
)]
276 [ResourceExposure(ResourceScope
.Machine
)]
277 public XmlTextWriter(String filename
, Encoding encoding
)
278 : this(new FileStream(filename
, FileMode
.Create
,
279 FileAccess
.Write
, FileShare
.Read
), encoding
) {
282 // Creates an instance of the XmlTextWriter class using the specified TextWriter.
283 public XmlTextWriter(TextWriter w
) : this() {
286 encoding
= w
.Encoding
;
287 xmlEncoder
= new XmlTextEncoder(w
);
288 xmlEncoder
.QuoteChar
= this.quoteChar
;
292 // XmlTextWriter properties
294 // Gets the XmlTextWriter base stream.
295 public Stream BaseStream
{
297 StreamWriter streamWriter
= textWriter
as StreamWriter
;
298 return (streamWriter
== null ? null : streamWriter
.BaseStream
);
302 // Gets or sets a value indicating whether to do namespace support.
303 public bool Namespaces
{
304 get { return this.namespaces;}
306 if (this.currentState
!= State
.Start
)
307 throw new InvalidOperationException(Res
.GetString(Res
.Xml_NotInWriteState
));
309 this.namespaces
= value;
313 // Indicates how the output is formatted.
314 public Formatting Formatting
{
315 get { return this.formatting;}
316 set { this.formatting = value; this.indented = value == Formatting.Indented;}
319 // Gets or sets how many IndentChars to write for each level in the hierarchy when Formatting is set to "Indented".
320 public int Indentation
{
321 get { return this.indentation;}
324 throw new ArgumentException(Res
.GetString(Res
.Xml_InvalidIndentation
));
325 this.indentation
= value;
329 // Gets or sets which character to use for indenting when Formatting is set to "Indented".
330 public char IndentChar
{
331 get { return this.indentChar;}
332 set { this.indentChar = value;}
335 // Gets or sets which character to use to quote attribute values.
336 public char QuoteChar
{
337 get { return this.quoteChar;}
339 if (value != '"' && value != '\'') {
340 throw new ArgumentException(Res
.GetString(Res
.Xml_InvalidQuote
));
342 this.quoteChar
= value;
343 this.xmlEncoder
.QuoteChar
= value;
348 // XmlWriter implementation
350 // Writes out the XML declaration with the version "1.0".
351 public override void WriteStartDocument() {
355 // Writes out the XML declaration with the version "1.0" and the standalone attribute.
356 public override void WriteStartDocument(bool standalone
) {
357 StartDocument(standalone
? 1 : 0);
360 // Closes any open elements or attributes and puts the writer back in the Start state.
361 public override void WriteEndDocument() {
364 if (this.currentState
!= State
.Epilog
) {
365 if (this.currentState
== State
.Closed
) {
366 throw new ArgumentException(Res
.GetString(Res
.Xml_ClosedOrError
));
369 throw new ArgumentException(Res
.GetString(Res
.Xml_NoRoot
));
372 this.stateTable
= stateTableDefault
;
373 this.currentState
= State
.Start
;
374 this.lastToken
= Token
.Empty
;
377 currentState
= State
.Error
;
382 // Writes out the DOCTYPE declaration with the specified name and optional attributes.
383 public override void WriteDocType(string name
, string pubid
, string sysid
, string subset
) {
385 ValidateName(name
, false);
387 AutoComplete(Token
.Doctype
);
388 textWriter
.Write("<!DOCTYPE ");
389 textWriter
.Write(name
);
391 textWriter
.Write(" PUBLIC " + quoteChar
);
392 textWriter
.Write(pubid
);
393 textWriter
.Write(quoteChar
+ " " + quoteChar
);
394 textWriter
.Write(sysid
);
395 textWriter
.Write(quoteChar
);
397 else if (sysid
!= null) {
398 textWriter
.Write(" SYSTEM " + quoteChar
);
399 textWriter
.Write(sysid
);
400 textWriter
.Write(quoteChar
);
402 if (subset
!= null) {
403 textWriter
.Write("[");
404 textWriter
.Write(subset
);
405 textWriter
.Write("]");
407 textWriter
.Write('>');
410 currentState
= State
.Error
;
415 // Writes out the specified start tag and associates it with the given namespace and prefix.
416 public override void WriteStartElement(string prefix
, string localName
, string ns
) {
418 AutoComplete(Token
.StartElement
);
420 textWriter
.Write('<');
422 if (this.namespaces
) {
423 // Propagate default namespace and mix model down the stack.
424 stack
[top
].defaultNs
= stack
[top
-1].defaultNs
;
425 if (stack
[top
-1].defaultNsState
!= NamespaceState
.Uninitialized
)
426 stack
[top
].defaultNsState
= NamespaceState
.NotDeclaredButInScope
;
427 stack
[top
].mixed
= stack
[top
-1].mixed
;
429 // use defined prefix
430 if (prefix
!= null && prefix
.Length
!= 0 && (LookupNamespace(prefix
) == -1)) {
431 throw new ArgumentException(Res
.GetString(Res
.Xml_UndefPrefix
));
435 if (prefix
== null) {
436 string definedPrefix
= FindPrefix(ns
);
437 if (definedPrefix
!= null) {
438 prefix
= definedPrefix
;
441 PushNamespace(null, ns
, false); // new default
444 else if (prefix
.Length
== 0) {
445 PushNamespace(null, ns
, false); // new default
448 if (ns
.Length
== 0) {
451 VerifyPrefixXml(prefix
, ns
);
452 PushNamespace(prefix
, ns
, false); // define
455 stack
[top
].prefix
= null;
456 if (prefix
!= null && prefix
.Length
!= 0) {
457 stack
[top
].prefix
= prefix
;
458 textWriter
.Write(prefix
);
459 textWriter
.Write(':');
463 if ((ns
!= null && ns
.Length
!= 0) || (prefix
!= null && prefix
.Length
!= 0)) {
464 throw new ArgumentException(Res
.GetString(Res
.Xml_NoNamespaces
));
467 stack
[top
].name
= localName
;
468 textWriter
.Write(localName
);
471 currentState
= State
.Error
;
476 // Closes one element and pops the corresponding namespace scope.
477 public override void WriteEndElement() {
478 InternalWriteEndElement(false);
481 // Closes one element and pops the corresponding namespace scope.
482 public override void WriteFullEndElement() {
483 InternalWriteEndElement(true);
486 // Writes the start of an attribute.
487 public override void WriteStartAttribute(string prefix
, string localName
, string ns
) {
489 AutoComplete(Token
.StartAttribute
);
491 this.specialAttr
= SpecialAttr
.None
;
492 if (this.namespaces
) {
494 if (prefix
!= null && prefix
.Length
== 0) {
498 if (ns
== XmlReservedNs
.NsXmlNs
&& prefix
== null && localName
!= "xmlns") {
502 if (prefix
== "xml") {
503 if (localName
== "lang") {
504 this.specialAttr
= SpecialAttr
.XmlLang
;
506 else if (localName
== "space") {
507 this.specialAttr
= SpecialAttr
.XmlSpace
;
509 /* bug54408. to be fwd compatible we need to treat xml prefix as reserved
510 and not really insist on a specific value. Who knows in the future it
511 might be OK to say xml:blabla
513 throw new ArgumentException(Res.GetString(Res.Xml_InvalidPrefix));
516 else if (prefix
== "xmlns") {
518 if (XmlReservedNs
.NsXmlNs
!= ns
&& ns
!= null) {
519 throw new ArgumentException(Res
.GetString(Res
.Xml_XmlnsBelongsToReservedNs
));
521 if (localName
== null || localName
.Length
== 0) {
524 this.prefixForXmlNs
= null;
527 this.prefixForXmlNs
= localName
;
529 this.specialAttr
= SpecialAttr
.XmlNs
;
531 else if (prefix
== null && localName
== "xmlns") {
532 if (XmlReservedNs
.NsXmlNs
!= ns
&& ns
!= null) {
533 // add the below line back in when DOM is fixed
534 throw new ArgumentException(Res
.GetString(Res
.Xml_XmlnsBelongsToReservedNs
));
536 this.specialAttr
= SpecialAttr
.XmlNs
;
537 this.prefixForXmlNs
= null;
541 // use defined prefix
542 if (prefix
!= null && (LookupNamespace(prefix
) == -1)) {
543 throw new ArgumentException(Res
.GetString(Res
.Xml_UndefPrefix
));
546 else if (ns
.Length
== 0) {
547 // empty namespace require null prefix
548 prefix
= string.Empty
;
550 else { // ns.Length != 0
551 VerifyPrefixXml(prefix
, ns
);
552 if (prefix
!= null && LookupNamespaceInCurrentScope(prefix
) != -1) {
555 // Now verify prefix validity
556 string definedPrefix
= FindPrefix(ns
);
557 if (definedPrefix
!= null && (prefix
== null || prefix
== definedPrefix
)) {
558 prefix
= definedPrefix
;
561 if (prefix
== null) {
562 prefix
= GeneratePrefix(); // need a prefix if
564 PushNamespace(prefix
, ns
, false);
568 if (prefix
!= null && prefix
.Length
!= 0) {
569 textWriter
.Write(prefix
);
570 textWriter
.Write(':');
574 if ((ns
!= null && ns
.Length
!= 0) || (prefix
!= null && prefix
.Length
!= 0)) {
575 throw new ArgumentException(Res
.GetString(Res
.Xml_NoNamespaces
));
577 if (localName
== "xml:lang") {
578 this.specialAttr
= SpecialAttr
.XmlLang
;
580 else if (localName
== "xml:space") {
581 this.specialAttr
= SpecialAttr
.XmlSpace
;
584 xmlEncoder
.StartAttribute(this.specialAttr
!= SpecialAttr
.None
);
586 textWriter
.Write(localName
);
587 textWriter
.Write('=');
588 if (this.curQuoteChar
!= this.quoteChar
) {
589 this.curQuoteChar
= this.quoteChar
;
590 xmlEncoder
.QuoteChar
= this.quoteChar
;
592 textWriter
.Write(this.curQuoteChar
);
595 currentState
= State
.Error
;
600 // Closes the attribute opened by WriteStartAttribute.
601 public override void WriteEndAttribute() {
603 AutoComplete(Token
.EndAttribute
);
606 currentState
= State
.Error
;
611 // Writes out a <![CDATA[...]]> block containing the specified text.
612 public override void WriteCData(string text
) {
614 AutoComplete(Token
.CData
);
615 if (null != text
&& text
.IndexOf("]]>", StringComparison
.Ordinal
) >= 0) {
616 throw new ArgumentException(Res
.GetString(Res
.Xml_InvalidCDataChars
));
618 textWriter
.Write("<![CDATA[");
621 xmlEncoder
.WriteRawWithSurrogateChecking(text
);
623 textWriter
.Write("]]>");
626 currentState
= State
.Error
;
631 // Writes out a comment <!--...--> containing the specified text.
632 public override void WriteComment(string text
) {
634 if (null != text
&& (text
.IndexOf("--", StringComparison
.Ordinal
)>=0 || (text
.Length
!= 0 && text
[text
.Length
-1] == '-'))) {
635 throw new ArgumentException(Res
.GetString(Res
.Xml_InvalidCommentChars
));
637 AutoComplete(Token
.Comment
);
638 textWriter
.Write("<!--");
640 xmlEncoder
.WriteRawWithSurrogateChecking(text
);
642 textWriter
.Write("-->");
645 currentState
= State
.Error
;
650 // Writes out a processing instruction with a space between the name and text as follows: <?name text?>
651 public override void WriteProcessingInstruction(string name
, string text
) {
653 if (null != text
&& text
.IndexOf("?>", StringComparison
.Ordinal
)>=0) {
654 throw new ArgumentException(Res
.GetString(Res
.Xml_InvalidPiChars
));
656 if (0 == String
.Compare(name
, "xml", StringComparison
.OrdinalIgnoreCase
) && this.stateTable
== stateTableDocument
) {
657 throw new ArgumentException(Res
.GetString(Res
.Xml_DupXmlDecl
));
659 AutoComplete(Token
.PI
);
660 InternalWriteProcessingInstruction(name
, text
);
663 currentState
= State
.Error
;
668 // Writes out an entity reference as follows: "&"+name+";".
669 public override void WriteEntityRef(string name
) {
671 ValidateName(name
, false);
672 AutoComplete(Token
.Content
);
673 xmlEncoder
.WriteEntityRef(name
);
676 currentState
= State
.Error
;
681 // Forces the generation of a character entity for the specified Unicode character value.
682 public override void WriteCharEntity(char ch
) {
684 AutoComplete(Token
.Content
);
685 xmlEncoder
.WriteCharEntity(ch
);
688 currentState
= State
.Error
;
693 // Writes out the given whitespace.
694 public override void WriteWhitespace(string ws
) {
700 if (!xmlCharType
.IsOnlyWhitespace(ws
)) {
701 throw new ArgumentException(Res
.GetString(Res
.Xml_NonWhitespace
));
703 AutoComplete(Token
.Whitespace
);
704 xmlEncoder
.Write(ws
);
707 currentState
= State
.Error
;
712 // Writes out the specified text content.
713 public override void WriteString(string text
) {
715 if (null != text
&& text
.Length
!= 0 ) {
716 AutoComplete(Token
.Content
);
717 xmlEncoder
.Write(text
);
721 currentState
= State
.Error
;
726 // Writes out the specified surrogate pair as a character entity.
727 public override void WriteSurrogateCharEntity(char lowChar
, char highChar
){
729 AutoComplete(Token
.Content
);
730 xmlEncoder
.WriteSurrogateCharEntity(lowChar
, highChar
);
733 currentState
= State
.Error
;
739 // Writes out the specified text content.
740 public override void WriteChars(Char
[] buffer
, int index
, int count
) {
742 AutoComplete(Token
.Content
);
743 xmlEncoder
.Write(buffer
, index
, count
);
746 currentState
= State
.Error
;
751 // Writes raw markup from the specified character buffer.
752 public override void WriteRaw(Char
[] buffer
, int index
, int count
) {
754 AutoComplete(Token
.RawData
);
755 xmlEncoder
.WriteRaw(buffer
, index
, count
);
758 currentState
= State
.Error
;
763 // Writes raw markup from the specified character string.
764 public override void WriteRaw(String data
) {
766 AutoComplete(Token
.RawData
);
767 xmlEncoder
.WriteRawWithSurrogateChecking(data
);
770 currentState
= State
.Error
;
775 // Encodes the specified binary bytes as base64 and writes out the resulting text.
776 public override void WriteBase64(byte[] buffer
, int index
, int count
) {
779 AutoComplete(Token
.Base64
);
783 // No need for us to explicitly validate the args. The StreamWriter will do
785 if (null == this.base64Encoder
) {
786 this.base64Encoder
= new XmlTextWriterBase64Encoder( xmlEncoder
);
788 // Encode will call WriteRaw to write out the encoded characters
789 this.base64Encoder
.Encode( buffer
, index
, count
);
792 currentState
= State
.Error
;
798 // Encodes the specified binary bytes as binhex and writes out the resulting text.
799 public override void WriteBinHex( byte[] buffer
, int index
, int count
) {
801 AutoComplete( Token
.Content
);
802 BinHexEncoder
.Encode( buffer
, index
, count
, this );
805 currentState
= State
.Error
;
810 // Returns the state of the XmlWriter.
811 public override WriteState WriteState
{
813 switch (this.currentState
) {
815 return WriteState
.Start
;
818 return WriteState
.Prolog
;
820 return WriteState
.Element
;
821 case State
.Attribute
:
823 return WriteState
.Attribute
;
826 return WriteState
.Content
;
828 return WriteState
.Error
;
830 return WriteState
.Closed
;
832 Debug
.Assert( false );
833 return WriteState
.Error
;
838 // Closes the XmlWriter and the underlying stream/TextWriter.
839 public override void Close() {
843 catch { // never fail
846 this.currentState
= State
.Closed
;
851 // Flushes whatever is in the buffer to the underlying stream/TextWriter and flushes the underlying stream/TextWriter.
852 public override void Flush() {
856 // Writes out the specified name, ensuring it is a valid Name according to the XML specification
857 // (http://www.w3.org/TR/1998/REC-xml-19980210#NT-Name
858 public override void WriteName(string name
) {
860 AutoComplete(Token
.Content
);
861 InternalWriteName(name
, false);
864 currentState
= State
.Error
;
869 // Writes out the specified namespace-qualified name by looking up the prefix that is in scope for the given namespace.
870 public override void WriteQualifiedName(string localName
, string ns
) {
872 AutoComplete(Token
.Content
);
873 if (this.namespaces
) {
874 if (ns
!= null && ns
.Length
!= 0 && ns
!= stack
[top
].defaultNs
) {
875 string prefix
= FindPrefix(ns
);
876 if (prefix
== null) {
877 if (this.currentState
!= State
.Attribute
) {
878 throw new ArgumentException(Res
.GetString(Res
.Xml_UndefNamespace
, ns
));
880 prefix
= GeneratePrefix(); // need a prefix if
881 PushNamespace(prefix
, ns
, false);
883 if (prefix
.Length
!= 0) {
884 InternalWriteName(prefix
, true);
885 textWriter
.Write(':');
889 else if (ns
!= null && ns
.Length
!= 0) {
890 throw new ArgumentException(Res
.GetString(Res
.Xml_NoNamespaces
));
892 InternalWriteName(localName
, true);
895 currentState
= State
.Error
;
900 // Returns the closest prefix defined in the current namespace scope for the specified namespace URI.
901 public override string LookupPrefix(string ns
) {
902 if (ns
== null || ns
.Length
== 0) {
903 throw new ArgumentException(Res
.GetString(Res
.Xml_EmptyName
));
905 string s
= FindPrefix(ns
);
906 if (s
== null && ns
== stack
[top
].defaultNs
) {
912 // Gets an XmlSpace representing the current xml:space scope.
913 public override XmlSpace XmlSpace
{
915 for (int i
= top
; i
> 0; i
--) {
916 XmlSpace xs
= stack
[i
].xmlSpace
;
917 if (xs
!= XmlSpace
.None
)
920 return XmlSpace
.None
;
924 // Gets the current xml:lang scope.
925 public override string XmlLang
{
927 for (int i
= top
; i
> 0; i
--) {
928 String xlang
= stack
[i
].xmlLang
;
936 // Writes out the specified name, ensuring it is a valid NmToken
937 // according to the XML specification (http://www.w3.org/TR/1998/REC-xml-19980210#NT-Name).
938 public override void WriteNmToken(string name
) {
940 AutoComplete(Token
.Content
);
942 if (name
== null || name
.Length
== 0) {
943 throw new ArgumentException(Res
.GetString(Res
.Xml_EmptyName
));
945 if (!ValidateNames
.IsNmtokenNoNamespaces(name
)) {
946 throw new ArgumentException(Res
.GetString(Res
.Xml_InvalidNameChars
, name
));
948 textWriter
.Write(name
);
951 currentState
= State
.Error
;
957 // Private implementation methods
959 void StartDocument(int standalone
) {
961 if (this.currentState
!= State
.Start
) {
962 throw new InvalidOperationException(Res
.GetString(Res
.Xml_NotTheFirst
));
964 this.stateTable
= stateTableDocument
;
965 this.currentState
= State
.Prolog
;
967 StringBuilder bufBld
= new StringBuilder(128);
968 bufBld
.Append("version=" + quoteChar
+ "1.0" + quoteChar
);
969 if (this.encoding
!= null) {
970 bufBld
.Append(" encoding=");
971 bufBld
.Append(quoteChar
);
972 bufBld
.Append(this.encoding
.WebName
);
973 bufBld
.Append(quoteChar
);
975 if (standalone
>= 0) {
976 bufBld
.Append(" standalone=");
977 bufBld
.Append(quoteChar
);
978 bufBld
.Append(standalone
== 0 ? "no" : "yes");
979 bufBld
.Append(quoteChar
);
981 InternalWriteProcessingInstruction("xml", bufBld
.ToString());
984 currentState
= State
.Error
;
989 void AutoComplete(Token token
) {
990 if (this.currentState
== State
.Closed
) {
991 throw new InvalidOperationException(Res
.GetString(Res
.Xml_Closed
));
993 else if (this.currentState
== State
.Error
) {
994 throw new InvalidOperationException(Res
.GetString(Res
.Xml_WrongToken
, tokenName
[(int)token
], stateName
[(int)State
.Error
]));
997 State newState
= this.stateTable
[(int)token
* 8 + (int)this.currentState
];
998 if (newState
== State
.Error
) {
999 throw new InvalidOperationException(Res
.GetString(Res
.Xml_WrongToken
, tokenName
[(int)token
], stateName
[(int)this.currentState
]));
1004 if (this.indented
&& this.currentState
!= State
.Start
) {
1009 case Token
.StartElement
:
1013 if (this.currentState
== State
.Attribute
) {
1014 WriteEndAttributeQuote();
1015 WriteEndStartTag(false);
1017 else if (this.currentState
== State
.Element
) {
1018 WriteEndStartTag(false);
1020 if (token
== Token
.CData
) {
1021 stack
[top
].mixed
= true;
1023 else if (this.indented
&& this.currentState
!= State
.Start
) {
1028 case Token
.EndElement
:
1029 case Token
.LongEndElement
:
1033 if (this.currentState
== State
.Attribute
) {
1034 WriteEndAttributeQuote();
1036 if (this.currentState
== State
.Content
) {
1037 token
= Token
.LongEndElement
;
1040 WriteEndStartTag(token
== Token
.EndElement
);
1042 if (stateTableDocument
== this.stateTable
&& top
== 1) {
1043 newState
= State
.Epilog
;
1047 case Token
.StartAttribute
:
1051 if (this.currentState
== State
.Attribute
) {
1052 WriteEndAttributeQuote();
1053 textWriter
.Write(' ');
1055 else if (this.currentState
== State
.Element
) {
1056 textWriter
.Write(' ');
1060 case Token
.EndAttribute
:
1064 WriteEndAttributeQuote();
1067 case Token
.Whitespace
:
1072 if (token
!= Token
.Base64
&& this.flush
) {
1075 if (this.currentState
== State
.Element
&& this.lastToken
!= Token
.Content
) {
1076 WriteEndStartTag(false);
1078 if (newState
== State
.Content
) {
1079 stack
[top
].mixed
= true;
1084 throw new InvalidOperationException(Res
.GetString(Res
.Xml_InvalidOperation
));
1086 this.currentState
= newState
;
1087 this.lastToken
= token
;
1090 void AutoCompleteAll() {
1099 void InternalWriteEndElement(bool longFormat
) {
1102 throw new InvalidOperationException(Res
.GetString(Res
.Xml_NoStartTag
));
1104 // if we are in the element, we need to close it.
1105 AutoComplete(longFormat
? Token
.LongEndElement
: Token
.EndElement
);
1106 if (this.lastToken
== Token
.LongEndElement
) {
1107 if (this.indented
) {
1110 textWriter
.Write('<');
1111 textWriter
.Write('/');
1112 if (this.namespaces
&& stack
[top
].prefix
!= null) {
1113 textWriter
.Write(stack
[top
].prefix
);
1114 textWriter
.Write(':');
1116 textWriter
.Write(stack
[top
].name
);
1117 textWriter
.Write('>');
1121 int prevNsTop
= stack
[top
].prevNsTop
;
1122 if (useNsHashtable
&& prevNsTop
< nsTop
) {
1123 PopNamespaces(prevNsTop
+ 1, nsTop
);
1129 currentState
= State
.Error
;
1134 void WriteEndStartTag(bool empty
) {
1135 xmlEncoder
.StartAttribute(false);
1136 for (int i
= nsTop
; i
> stack
[top
].prevNsTop
; i
--) {
1137 if (!nsStack
[i
].declared
) {
1138 textWriter
.Write(" xmlns");
1139 textWriter
.Write(':');
1140 textWriter
.Write(nsStack
[i
].prefix
);
1141 textWriter
.Write('=');
1142 textWriter
.Write(this.quoteChar
);
1143 xmlEncoder
.Write(nsStack
[i
].ns
);
1144 textWriter
.Write(this.quoteChar
);
1148 if ((stack
[top
].defaultNs
!= stack
[top
- 1].defaultNs
) &&
1149 (stack
[top
].defaultNsState
== NamespaceState
.DeclaredButNotWrittenOut
)) {
1150 textWriter
.Write(" xmlns");
1151 textWriter
.Write('=');
1152 textWriter
.Write(this.quoteChar
);
1153 xmlEncoder
.Write(stack
[top
].defaultNs
);
1154 textWriter
.Write(this.quoteChar
);
1155 stack
[top
].defaultNsState
= NamespaceState
.DeclaredAndWrittenOut
;
1157 xmlEncoder
.EndAttribute();
1159 textWriter
.Write(" /");
1161 textWriter
.Write('>');
1164 void WriteEndAttributeQuote() {
1165 if (this.specialAttr
!= SpecialAttr
.None
) {
1166 // Ok, now to handle xmlspace, etc.
1167 HandleSpecialAttribute();
1169 xmlEncoder
.EndAttribute();
1170 textWriter
.Write(this.curQuoteChar
);
1173 void Indent(bool beforeEndElement
) {
1176 textWriter
.WriteLine();
1178 else if (!stack
[top
].mixed
) {
1179 textWriter
.WriteLine();
1180 int i
= beforeEndElement
? top
- 1 : top
;
1181 for (i
*= this.indentation
; i
> 0; i
--) {
1182 textWriter
.Write(this.indentChar
);
1187 // pushes new namespace scope, and returns generated prefix, if one
1188 // was needed to resolve conflicts.
1189 void PushNamespace(string prefix
, string ns
, bool declared
) {
1190 if (XmlReservedNs
.NsXmlNs
== ns
) {
1191 throw new ArgumentException(Res
.GetString(Res
.Xml_CanNotBindToReservedNamespace
));
1194 if (prefix
== null) {
1195 switch (stack
[top
].defaultNsState
) {
1196 case NamespaceState
.DeclaredButNotWrittenOut
:
1197 Debug
.Assert (declared
== true, "Unexpected situation!!");
1198 // the first namespace that the user gave us is what we
1201 case NamespaceState
.Uninitialized
:
1202 case NamespaceState
.NotDeclaredButInScope
:
1203 // we now got a brand new namespace that we need to remember
1204 stack
[top
].defaultNs
= ns
;
1207 Debug
.Assert(false, "Should have never come here");
1210 stack
[top
].defaultNsState
= (declared
? NamespaceState
.DeclaredAndWrittenOut
: NamespaceState
.DeclaredButNotWrittenOut
);
1213 if (prefix
.Length
!= 0 && ns
.Length
== 0) {
1214 throw new ArgumentException(Res
.GetString(Res
.Xml_PrefixForEmptyNs
));
1217 int existingNsIndex
= LookupNamespace(prefix
);
1218 if (existingNsIndex
!= -1 && nsStack
[existingNsIndex
].ns
== ns
) {
1219 // it is already in scope.
1221 nsStack
[existingNsIndex
].declared
= true;
1225 // see if prefix conflicts for the current element
1227 if (existingNsIndex
!= -1 && existingNsIndex
> stack
[top
].prevNsTop
) {
1228 nsStack
[existingNsIndex
].declared
= true; // old one is silenced now
1231 AddNamespace(prefix
, ns
, declared
);
1236 void AddNamespace(string prefix
, string ns
, bool declared
) {
1237 int nsIndex
= ++nsTop
;
1238 if ( nsIndex
== nsStack
.Length
) {
1239 Namespace
[] newStack
= new Namespace
[nsIndex
* 2];
1240 Array
.Copy(nsStack
, newStack
, nsIndex
);
1243 nsStack
[nsIndex
].Set(prefix
, ns
, declared
);
1245 if (useNsHashtable
) {
1246 AddToNamespaceHashtable(nsIndex
);
1248 else if (nsIndex
== MaxNamespacesWalkCount
) {
1250 nsHashtable
= new Dictionary
<string, int>(new SecureStringHasher());
1251 for (int i
= 0; i
<= nsIndex
; i
++) {
1252 AddToNamespaceHashtable(i
);
1254 useNsHashtable
= true;
1258 void AddToNamespaceHashtable(int namespaceIndex
) {
1259 string prefix
= nsStack
[namespaceIndex
].prefix
;
1260 int existingNsIndex
;
1261 if ( nsHashtable
.TryGetValue(prefix
, out existingNsIndex
)) {
1262 nsStack
[namespaceIndex
].prevNsIndex
= existingNsIndex
;
1264 nsHashtable
[prefix
] = namespaceIndex
;
1267 private void PopNamespaces(int indexFrom
, int indexTo
) {
1268 Debug
.Assert(useNsHashtable
);
1269 for (int i
= indexTo
; i
>= indexFrom
; i
--) {
1270 Debug
.Assert(nsHashtable
.ContainsKey(nsStack
[i
].prefix
));
1271 if (nsStack
[i
].prevNsIndex
== -1) {
1272 nsHashtable
.Remove(nsStack
[i
].prefix
);
1275 nsHashtable
[nsStack
[i
].prefix
] = nsStack
[i
].prevNsIndex
;
1280 string GeneratePrefix() {
1281 int temp
= stack
[top
].prefixCount
++ + 1;
1282 return "d" + top
.ToString("d", CultureInfo
.InvariantCulture
)
1283 + "p" + temp
.ToString("d", CultureInfo
.InvariantCulture
);
1286 void InternalWriteProcessingInstruction(string name
, string text
) {
1287 textWriter
.Write("<?");
1288 ValidateName(name
, false);
1289 textWriter
.Write(name
);
1290 textWriter
.Write(' ');
1292 xmlEncoder
.WriteRawWithSurrogateChecking(text
);
1294 textWriter
.Write("?>");
1297 int LookupNamespace( string prefix
) {
1298 if ( useNsHashtable
) {
1300 if ( nsHashtable
.TryGetValue( prefix
, out nsIndex
) ) {
1305 for ( int i
= nsTop
; i
>= 0; i
-- ) {
1306 if ( nsStack
[i
].prefix
== prefix
) {
1314 int LookupNamespaceInCurrentScope( string prefix
) {
1315 if ( useNsHashtable
) {
1317 if ( nsHashtable
.TryGetValue( prefix
, out nsIndex
) ) {
1318 if ( nsIndex
> stack
[top
].prevNsTop
) {
1324 for ( int i
= nsTop
; i
> stack
[top
].prevNsTop
; i
-- ) {
1325 if ( nsStack
[i
].prefix
== prefix
) {
1333 string FindPrefix(string ns
) {
1334 for (int i
= nsTop
; i
>= 0; i
--) {
1335 if (nsStack
[i
].ns
== ns
) {
1336 if (LookupNamespace(nsStack
[i
].prefix
) == i
) {
1337 return nsStack
[i
].prefix
;
1344 // There are three kind of strings we write out - Name, LocalName and Prefix.
1345 // Both LocalName and Prefix can be represented with NCName == false and Name
1346 // can be represented as NCName == true
1348 void InternalWriteName(string name
, bool isNCName
) {
1349 ValidateName(name
, isNCName
);
1350 textWriter
.Write(name
);
1353 // This method is used for validation of the DOCTYPE, processing instruction and entity names plus names
1354 // written out by the user via WriteName and WriteQualifiedName.
1355 // Unfortunatelly the names of elements and attributes are not validated by the XmlTextWriter.
1356 // Also this method does not check wheather the character after ':' is a valid start name character. It accepts
1357 // all valid name characters at that position. This can't be changed because of backwards compatibility.
1358 private unsafe void ValidateName(string name
, bool isNCName
) {
1359 if (name
== null || name
.Length
== 0) {
1360 throw new ArgumentException(Res
.GetString(Res
.Xml_EmptyName
));
1363 int nameLength
= name
.Length
;
1365 // Namespaces supported
1367 // We can't use ValidateNames.ParseQName here because of backwards compatibility bug we need to preserve.
1368 // The bug is that the character after ':' is validated only as a NCName characters instead of NCStartName.
1369 int colonPosition
= -1;
1371 // Parse NCName (may be prefix, may be local name)
1372 int position
= ValidateNames
.ParseNCName(name
);
1375 if (position
== nameLength
) {
1379 // we have prefix:localName
1380 if (name
[position
] == ':') {
1382 // first colon in qname
1383 if (colonPosition
== -1) {
1384 // make sure it is not the first or last characters
1385 if (position
> 0 && position
+ 1 < nameLength
) {
1386 colonPosition
= position
;
1387 // Because of the back-compat bug (described above) parse the rest as Nmtoken
1389 position
+= ValidateNames
.ParseNmtoken(name
, position
);
1396 // Namespaces not supported
1398 if (ValidateNames
.IsNameNoNamespaces(name
)) {
1402 throw new ArgumentException(Res
.GetString(Res
.Xml_InvalidNameChars
, name
));
1405 void HandleSpecialAttribute() {
1406 string value = xmlEncoder
.AttributeValue
;
1407 switch (this.specialAttr
) {
1408 case SpecialAttr
.XmlLang
:
1409 stack
[top
].xmlLang
= value;
1411 case SpecialAttr
.XmlSpace
:
1412 // validate XmlSpace attribute
1413 value = XmlConvert
.TrimString(value);
1414 if (value == "default") {
1415 stack
[top
].xmlSpace
= XmlSpace
.Default
;
1417 else if (value == "preserve") {
1418 stack
[top
].xmlSpace
= XmlSpace
.Preserve
;
1421 throw new ArgumentException(Res
.GetString(Res
.Xml_InvalidXmlSpace
, value));
1424 case SpecialAttr
.XmlNs
:
1425 VerifyPrefixXml(this.prefixForXmlNs
, value);
1426 PushNamespace(this.prefixForXmlNs
, value, true);
1432 void VerifyPrefixXml(string prefix
, string ns
) {
1434 if (prefix
!= null && prefix
.Length
== 3) {
1436 (prefix
[0] == 'x' || prefix
[0] == 'X') &&
1437 (prefix
[1] == 'm' || prefix
[1] == 'M') &&
1438 (prefix
[2] == 'l' || prefix
[2] == 'L')
1440 if (XmlReservedNs
.NsXml
!= ns
) {
1441 throw new ArgumentException(Res
.GetString(Res
.Xml_InvalidPrefix
));
1448 if (top
== stack
.Length
- 1) {
1449 TagInfo
[] na
= new TagInfo
[stack
.Length
+ 10];
1450 if (top
> 0) Array
.Copy(stack
,na
,top
+ 1);
1454 top
++; // Move up stack
1455 stack
[top
].Init(nsTop
);
1458 void FlushEncoders()
1460 if (null != this.base64Encoder
) {
1461 // The Flush will call WriteRaw to write out the rest of the encoded characters
1462 this.base64Encoder
.Flush();