Updates referencesource to .NET 4.7
[mono-project.git] / mcs / class / referencesource / System.Xml / System / Xml / Core / XmlTextWriter.cs
blob4f6a768e7af272570915d3cc0a6d8e1a40af4ec2
1 //------------------------------------------------------------------------------
2 // <copyright file="XmlTextWriter.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">Microsoft</owner>
6 //------------------------------------------------------------------------------
8 using System;
9 using System.Collections;
10 using System.Collections.Generic;
11 using System.IO;
12 using System.Text;
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).
22 None,
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.
28 Indented,
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 {
38 // Private types
40 enum NamespaceState {
41 Uninitialized,
42 NotDeclaredButInScope,
43 DeclaredButNotWrittenOut,
44 DeclaredAndWrittenOut
47 struct TagInfo {
48 internal string name;
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 ) {
59 name = null;
60 defaultNs = String.Empty;
61 defaultNsState = NamespaceState.Uninitialized;
62 xmlSpace = XmlSpace.None;
63 xmlLang = null;
64 prevNsTop = nsTop;
65 prefixCount = 0;
66 mixed = false;
70 struct Namespace {
71 internal string prefix;
72 internal string ns;
73 internal bool declared;
74 internal int prevNsIndex;
76 internal void Set( string prefix, string ns, bool declared ) {
77 this.prefix = prefix;
78 this.ns = ns;
79 this.declared = declared;
80 this.prevNsIndex = -1;
84 enum SpecialAttr {
85 None,
86 XmlSpace,
87 XmlLang,
88 XmlNs
91 // State machine is working through autocomplete
92 private enum State {
93 Start,
94 Prolog,
95 PostDTD,
96 Element,
97 Attribute,
98 Content,
99 AttrOnly,
100 Epilog,
101 Error,
102 Closed,
105 private enum Token {
107 Doctype,
108 Comment,
109 CData,
110 StartElement,
111 EndElement,
112 LongEndElement,
113 StartAttribute,
114 EndAttribute,
115 Content,
116 Base64,
117 RawData,
118 Whitespace,
119 Empty
123 // Fields
125 // output
126 TextWriter textWriter;
127 XmlTextEncoder xmlEncoder;
128 Encoding encoding;
130 // formatting
131 Formatting formatting;
132 bool indented; // perf - faster to check a boolean.
133 int indentation;
134 char indentChar;
136 // element stack
137 TagInfo[] stack;
138 int top;
140 // state machine for AutoComplete
141 State[] stateTable;
142 State currentState;
143 Token lastToken;
145 // Base64 content
146 XmlTextWriterBase64Encoder base64Encoder;
148 // misc
149 char quoteChar;
150 char curQuoteChar;
151 bool namespaces;
152 SpecialAttr specialAttr;
153 string prefixForXmlNs;
154 bool flush;
156 // namespaces
157 Namespace[] nsStack;
158 int nsTop;
159 Dictionary<string, int> nsHashtable;
160 bool useNsHashtable;
162 // char types
163 XmlCharType xmlCharType = XmlCharType.Instance;
166 // Constants and constant tables
168 const int NamespaceStackInitialSize = 8;
169 #if DEBUG
170 const int MaxNamespacesWalkCount = 3;
171 #else
172 const int MaxNamespacesWalkCount = 16;
173 #endif
175 static string[] stateName = {
176 "Start",
177 "Prolog",
178 "PostDTD",
179 "Element",
180 "Attribute",
181 "Content",
182 "AttrOnly",
183 "Epilog",
184 "Error",
185 "Closed",
188 static string[] tokenName = {
189 "PI",
190 "Doctype",
191 "Comment",
192 "CData",
193 "StartElement",
194 "EndElement",
195 "LongEndElement",
196 "StartAttribute",
197 "EndAttribute",
198 "Content",
199 "Base64",
200 "RawData",
201 "Whitespace",
202 "Empty"
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,
242 // Constructors
244 internal XmlTextWriter() {
245 namespaces = true;
246 formatting = Formatting.None;
247 indentation = 2;
248 indentChar = ' ';
249 // namespaces
250 nsStack = new Namespace[NamespaceStackInitialSize];
251 nsTop = -1;
252 // element stack
253 stack = new TagInfo[10];
254 top = 0;// 0 is an empty sentanial element
255 stack[top].Init( -1 );
256 quoteChar = '"';
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);
268 else
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() {
284 textWriter = w;
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 {
296 get {
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;}
305 set {
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;}
322 set {
323 if (value < 0)
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;}
338 set {
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() {
352 StartDocument(-1);
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() {
362 try {
363 AutoCompleteAll();
364 if (this.currentState != State.Epilog) {
365 if (this.currentState == State.Closed) {
366 throw new ArgumentException(Res.GetString(Res.Xml_ClosedOrError));
368 else {
369 throw new ArgumentException(Res.GetString(Res.Xml_NoRoot));
372 this.stateTable = stateTableDefault;
373 this.currentState = State.Start;
374 this.lastToken = Token.Empty;
376 catch {
377 currentState = State.Error;
378 throw;
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) {
384 try {
385 ValidateName(name, false);
387 AutoComplete(Token.Doctype);
388 textWriter.Write("<!DOCTYPE ");
389 textWriter.Write(name);
390 if (pubid != null) {
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('>');
409 catch {
410 currentState = State.Error;
411 throw;
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) {
417 try {
418 AutoComplete(Token.StartElement);
419 PushStack();
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;
428 if (ns == null) {
429 // use defined prefix
430 if (prefix != null && prefix.Length != 0 && (LookupNamespace(prefix) == -1)) {
431 throw new ArgumentException(Res.GetString(Res.Xml_UndefPrefix));
434 else {
435 if (prefix == null) {
436 string definedPrefix = FindPrefix(ns);
437 if (definedPrefix != null) {
438 prefix = definedPrefix;
440 else {
441 PushNamespace(null, ns, false); // new default
444 else if (prefix.Length == 0) {
445 PushNamespace(null, ns, false); // new default
447 else {
448 if (ns.Length == 0) {
449 prefix = null;
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(':');
462 else {
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);
470 catch {
471 currentState = State.Error;
472 throw;
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) {
488 try {
489 AutoComplete(Token.StartAttribute);
491 this.specialAttr = SpecialAttr.None;
492 if (this.namespaces) {
494 if (prefix != null && prefix.Length == 0) {
495 prefix = null;
498 if (ns == XmlReservedNs.NsXmlNs && prefix == null && localName != "xmlns") {
499 prefix = "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
512 else {
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) {
522 localName = prefix;
523 prefix = null;
524 this.prefixForXmlNs = null;
526 else {
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;
539 else {
540 if (ns == 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) {
553 prefix = null;
555 // Now verify prefix validity
556 string definedPrefix = FindPrefix(ns);
557 if (definedPrefix != null && (prefix == null || prefix == definedPrefix)) {
558 prefix = definedPrefix;
560 else {
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(':');
573 else {
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);
594 catch {
595 currentState = State.Error;
596 throw;
600 // Closes the attribute opened by WriteStartAttribute.
601 public override void WriteEndAttribute() {
602 try {
603 AutoComplete(Token.EndAttribute);
605 catch {
606 currentState = State.Error;
607 throw;
611 // Writes out a &lt;![CDATA[...]]&gt; block containing the specified text.
612 public override void WriteCData(string text) {
613 try {
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[");
620 if (null != text) {
621 xmlEncoder.WriteRawWithSurrogateChecking(text);
623 textWriter.Write("]]>");
625 catch {
626 currentState = State.Error;
627 throw;
631 // Writes out a comment <!--...--> containing the specified text.
632 public override void WriteComment(string text) {
633 try {
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("<!--");
639 if (null != text) {
640 xmlEncoder.WriteRawWithSurrogateChecking(text);
642 textWriter.Write("-->");
644 catch {
645 currentState = State.Error;
646 throw;
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) {
652 try {
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);
662 catch {
663 currentState = State.Error;
664 throw;
668 // Writes out an entity reference as follows: "&"+name+";".
669 public override void WriteEntityRef(string name) {
670 try {
671 ValidateName(name, false);
672 AutoComplete(Token.Content);
673 xmlEncoder.WriteEntityRef(name);
675 catch {
676 currentState = State.Error;
677 throw;
681 // Forces the generation of a character entity for the specified Unicode character value.
682 public override void WriteCharEntity(char ch) {
683 try {
684 AutoComplete(Token.Content);
685 xmlEncoder.WriteCharEntity(ch);
687 catch {
688 currentState = State.Error;
689 throw;
693 // Writes out the given whitespace.
694 public override void WriteWhitespace(string ws) {
695 try {
696 if (null == ws) {
697 ws = String.Empty;
700 if (!xmlCharType.IsOnlyWhitespace(ws)) {
701 throw new ArgumentException(Res.GetString(Res.Xml_NonWhitespace));
703 AutoComplete(Token.Whitespace);
704 xmlEncoder.Write(ws);
706 catch {
707 currentState = State.Error;
708 throw;
712 // Writes out the specified text content.
713 public override void WriteString(string text) {
714 try {
715 if (null != text && text.Length != 0 ) {
716 AutoComplete(Token.Content);
717 xmlEncoder.Write(text);
720 catch {
721 currentState = State.Error;
722 throw;
726 // Writes out the specified surrogate pair as a character entity.
727 public override void WriteSurrogateCharEntity(char lowChar, char highChar){
728 try {
729 AutoComplete(Token.Content);
730 xmlEncoder.WriteSurrogateCharEntity(lowChar, highChar);
732 catch {
733 currentState = State.Error;
734 throw;
739 // Writes out the specified text content.
740 public override void WriteChars(Char[] buffer, int index, int count) {
741 try {
742 AutoComplete(Token.Content);
743 xmlEncoder.Write(buffer, index, count);
745 catch {
746 currentState = State.Error;
747 throw;
751 // Writes raw markup from the specified character buffer.
752 public override void WriteRaw(Char[] buffer, int index, int count) {
753 try {
754 AutoComplete(Token.RawData);
755 xmlEncoder.WriteRaw(buffer, index, count);
757 catch {
758 currentState = State.Error;
759 throw;
763 // Writes raw markup from the specified character string.
764 public override void WriteRaw(String data) {
765 try {
766 AutoComplete(Token.RawData);
767 xmlEncoder.WriteRawWithSurrogateChecking(data);
769 catch {
770 currentState = State.Error;
771 throw;
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) {
777 try {
778 if (!this.flush) {
779 AutoComplete(Token.Base64);
782 this.flush = true;
783 // No need for us to explicitly validate the args. The StreamWriter will do
784 // it for us.
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 );
791 catch {
792 currentState = State.Error;
793 throw;
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 ) {
800 try {
801 AutoComplete( Token.Content );
802 BinHexEncoder.Encode( buffer, index, count, this );
804 catch {
805 currentState = State.Error;
806 throw;
810 // Returns the state of the XmlWriter.
811 public override WriteState WriteState {
812 get {
813 switch (this.currentState) {
814 case State.Start :
815 return WriteState.Start;
816 case State.Prolog :
817 case State.PostDTD :
818 return WriteState.Prolog;
819 case State.Element :
820 return WriteState.Element;
821 case State.Attribute :
822 case State.AttrOnly:
823 return WriteState.Attribute;
824 case State.Content :
825 case State.Epilog :
826 return WriteState.Content;
827 case State.Error:
828 return WriteState.Error;
829 case State.Closed:
830 return WriteState.Closed;
831 default:
832 Debug.Assert( false );
833 return WriteState.Error;
838 // Closes the XmlWriter and the underlying stream/TextWriter.
839 public override void Close() {
840 try {
841 AutoCompleteAll();
843 catch { // never fail
845 finally {
846 this.currentState = State.Closed;
847 textWriter.Close();
851 // Flushes whatever is in the buffer to the underlying stream/TextWriter and flushes the underlying stream/TextWriter.
852 public override void Flush() {
853 textWriter.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) {
859 try {
860 AutoComplete(Token.Content);
861 InternalWriteName(name, false);
863 catch {
864 currentState = State.Error;
865 throw;
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) {
871 try {
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);
894 catch {
895 currentState = State.Error;
896 throw;
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) {
907 s = string.Empty;
909 return s;
912 // Gets an XmlSpace representing the current xml:space scope.
913 public override XmlSpace XmlSpace {
914 get {
915 for (int i = top; i > 0; i--) {
916 XmlSpace xs = stack[i].xmlSpace;
917 if (xs != XmlSpace.None)
918 return xs;
920 return XmlSpace.None;
924 // Gets the current xml:lang scope.
925 public override string XmlLang {
926 get {
927 for (int i = top; i > 0; i--) {
928 String xlang = stack[i].xmlLang;
929 if (xlang != null)
930 return xlang;
932 return null;
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) {
939 try {
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);
950 catch {
951 currentState = State.Error;
952 throw;
957 // Private implementation methods
959 void StartDocument(int standalone) {
960 try {
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());
983 catch {
984 currentState = State.Error;
985 throw;
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]));
1002 switch (token) {
1003 case Token.Doctype:
1004 if (this.indented && this.currentState != State.Start) {
1005 Indent(false);
1007 break;
1009 case Token.StartElement:
1010 case Token.Comment:
1011 case Token.PI:
1012 case Token.CData:
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) {
1024 Indent(false);
1026 break;
1028 case Token.EndElement:
1029 case Token.LongEndElement:
1030 if (this.flush) {
1031 FlushEncoders();
1033 if (this.currentState == State.Attribute) {
1034 WriteEndAttributeQuote();
1036 if (this.currentState == State.Content) {
1037 token = Token.LongEndElement;
1039 else {
1040 WriteEndStartTag(token == Token.EndElement);
1042 if (stateTableDocument == this.stateTable && top == 1) {
1043 newState = State.Epilog;
1045 break;
1047 case Token.StartAttribute:
1048 if (this.flush) {
1049 FlushEncoders();
1051 if (this.currentState == State.Attribute) {
1052 WriteEndAttributeQuote();
1053 textWriter.Write(' ');
1055 else if (this.currentState == State.Element) {
1056 textWriter.Write(' ');
1058 break;
1060 case Token.EndAttribute:
1061 if (this.flush) {
1062 FlushEncoders();
1064 WriteEndAttributeQuote();
1065 break;
1067 case Token.Whitespace:
1068 case Token.Content:
1069 case Token.RawData:
1070 case Token.Base64:
1072 if (token != Token.Base64 && this.flush) {
1073 FlushEncoders();
1075 if (this.currentState == State.Element && this.lastToken != Token.Content) {
1076 WriteEndStartTag(false);
1078 if (newState == State.Content) {
1079 stack[top].mixed = true;
1081 break;
1083 default:
1084 throw new InvalidOperationException(Res.GetString(Res.Xml_InvalidOperation));
1086 this.currentState = newState;
1087 this.lastToken = token;
1090 void AutoCompleteAll() {
1091 if (this.flush) {
1092 FlushEncoders();
1094 while (top > 0) {
1095 WriteEndElement();
1099 void InternalWriteEndElement(bool longFormat) {
1100 try {
1101 if (top <= 0) {
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) {
1108 Indent(true);
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('>');
1120 // pop namespaces
1121 int prevNsTop = stack[top].prevNsTop;
1122 if (useNsHashtable && prevNsTop < nsTop) {
1123 PopNamespaces(prevNsTop + 1, nsTop);
1125 nsTop = prevNsTop;
1126 top--;
1128 catch {
1129 currentState = State.Error;
1130 throw;
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);
1147 // Default
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();
1158 if (empty) {
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) {
1174 // pretty printing.
1175 if (top == 0) {
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
1199 // like to keep.
1200 break;
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;
1205 break;
1206 default:
1207 Debug.Assert(false, "Should have never come here");
1208 return;
1210 stack[top].defaultNsState = (declared ? NamespaceState.DeclaredAndWrittenOut : NamespaceState.DeclaredButNotWrittenOut);
1212 else {
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.
1220 if (declared) {
1221 nsStack[existingNsIndex].declared = true;
1224 else {
1225 // see if prefix conflicts for the current element
1226 if (declared) {
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);
1241 nsStack = newStack;
1243 nsStack[nsIndex].Set(prefix, ns, declared);
1245 if (useNsHashtable) {
1246 AddToNamespaceHashtable(nsIndex);
1248 else if (nsIndex == MaxNamespacesWalkCount) {
1249 // add all
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);
1274 else {
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(' ');
1291 if (null != text) {
1292 xmlEncoder.WriteRawWithSurrogateChecking(text);
1294 textWriter.Write("?>");
1297 int LookupNamespace( string prefix ) {
1298 if ( useNsHashtable ) {
1299 int nsIndex;
1300 if ( nsHashtable.TryGetValue( prefix, out nsIndex ) ) {
1301 return nsIndex;
1304 else {
1305 for ( int i = nsTop; i >= 0; i-- ) {
1306 if ( nsStack[i].prefix == prefix ) {
1307 return i;
1311 return -1;
1314 int LookupNamespaceInCurrentScope( string prefix ) {
1315 if ( useNsHashtable ) {
1316 int nsIndex;
1317 if ( nsHashtable.TryGetValue( prefix, out nsIndex ) ) {
1318 if ( nsIndex > stack[top].prevNsTop ) {
1319 return nsIndex;
1323 else {
1324 for ( int i = nsTop; i > stack[top].prevNsTop; i-- ) {
1325 if ( nsStack[i].prefix == prefix ) {
1326 return i;
1330 return -1;
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;
1341 return null;
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
1366 if (namespaces) {
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);
1374 Continue:
1375 if (position == nameLength) {
1376 return;
1379 // we have prefix:localName
1380 if (name[position] == ':') {
1381 if (!isNCName) {
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
1388 position++;
1389 position += ValidateNames.ParseNmtoken(name, position);
1390 goto Continue;
1396 // Namespaces not supported
1397 else {
1398 if (ValidateNames.IsNameNoNamespaces(name)) {
1399 return;
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;
1410 break;
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;
1420 else {
1421 throw new ArgumentException(Res.GetString(Res.Xml_InvalidXmlSpace, value));
1423 break;
1424 case SpecialAttr.XmlNs:
1425 VerifyPrefixXml(this.prefixForXmlNs, value);
1426 PushNamespace(this.prefixForXmlNs, value, true);
1427 break;
1432 void VerifyPrefixXml(string prefix, string ns) {
1434 if (prefix != null && prefix.Length == 3) {
1435 if (
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));
1447 void PushStack() {
1448 if (top == stack.Length - 1) {
1449 TagInfo[] na = new TagInfo[stack.Length + 10];
1450 if (top > 0) Array.Copy(stack,na,top + 1);
1451 stack = na;
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();
1464 this.flush = false;