2005-12-05 Lluis Sanchez Gual <lluis@novell.com>
[mono.git] / mcs / class / System.XML / System.Xml / XmlTextWriter.cs
1 //
2 // System.Xml.XmlTextWriter
3 //
4 // Author:
5 //   Kral Ferch <kral_ferch@hotmail.com>
6 //   Atsushi Enomoto <ginga@kit.hi-ho.ne.jp>
7 //
8 // (C) 2002 Kral Ferch
9 // (C) 2003 Atsushi Enomoto
10 //
11
12 //
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
20 // 
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
23 // 
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 //
32 using System;
33 using System.Collections;
34 using System.Globalization;
35 using System.IO;
36 using System.Text;
37
38 namespace System.Xml
39 {
40         public class XmlTextWriter : XmlWriter
41         {
42                 #region Fields
43                 const string XmlnsNamespace = "http://www.w3.org/2000/xmlns/";
44
45                 WriteState ws = WriteState.Start;
46                 TextWriter w;
47                 bool nullEncoding = false;
48                 bool openWriter = true;
49                 bool openStartElement = false;
50                 bool documentStarted = false;
51                 bool namespaces = true;
52                 bool openAttribute = false;
53                 bool attributeWrittenForElement = false;
54                 ArrayList openElements = new ArrayList ();
55                 int openElementCount;
56                 Formatting formatting = Formatting.None;
57                 int indentation = 2;
58                 char indentChar = ' ';
59                 string indentChars = "  ";
60                 char quoteChar = '\"';
61                 int indentLevel = 0;
62                 bool indentLocal;
63                 Stream baseStream = null;
64                 string xmlLang = null;
65                 XmlSpace xmlSpace = XmlSpace.None;
66                 bool openXmlLang = false;
67                 bool openXmlSpace = false;
68                 string openElementPrefix;
69                 string openElementNS;
70                 bool hasRoot = false;
71                 bool isDocumentEntity = false;
72                 Hashtable newAttributeNamespaces = new Hashtable ();
73                 Hashtable userWrittenNamespaces = new Hashtable ();
74                 StringBuilder cachedStringBuilder;
75                 int autoCreatedPrefixes;
76
77                 XmlNamespaceManager namespaceManager = new XmlNamespaceManager (new NameTable ());
78                 string savingAttributeValue = String.Empty;
79                 bool saveAttributeValue;
80                 string savedAttributePrefix;
81                 bool shouldAddSavedNsToManager;
82                 bool shouldCheckElementXmlns;
83
84                 // XmlWriterSettings support
85                 bool checkCharacters;
86                 bool closeOutput = true;
87                 bool newLineOnAttributes;
88                 string newLineChars;
89 #if NET_2_0
90                 bool outputXmlDeclaration;
91                 ConformanceLevel conformanceLevel;
92 #endif
93
94                 #endregion
95
96                 #region Constructors
97
98                 public XmlTextWriter (TextWriter w) : base ()
99                 {
100                         this.w = w;
101                         nullEncoding = (w.Encoding == null);
102                         StreamWriter sw = w as StreamWriter;
103                         if (sw != null)
104                                 baseStream = sw.BaseStream;
105                         newLineChars = w.NewLine;
106                 }
107
108                 public XmlTextWriter (Stream w, Encoding encoding) : base ()
109                 {
110                         if (encoding == null) {
111                                 nullEncoding = true;
112                                 this.w = new StreamWriter (w);
113                         } else 
114                                 this.w = new StreamWriter (w, encoding);
115                         baseStream = w;
116                         newLineChars = this.w.NewLine;
117                 }
118
119                 public XmlTextWriter (string filename, Encoding encoding) :
120                         this (new FileStream (filename, FileMode.Create, FileAccess.Write, FileShare.None), encoding)
121                 {
122                 }
123
124                 #endregion
125
126                 #region Properties
127
128                 public Stream BaseStream {
129                         get { return baseStream; }
130                 }
131
132
133                 public Formatting Formatting {
134                         get { return formatting; }
135                         set { formatting = value; }
136                 }
137
138                 private bool IndentingOverriden 
139                 {
140                         get {
141                                 if (openElementCount == 0)
142                                         return false;
143                                 else
144                                         return ((XmlTextWriterOpenElement)
145 openElements [openElementCount - 1]).IndentingOverriden;
146                         }
147                         set {
148                                 if (openElementCount > 0)
149                                         ((XmlTextWriterOpenElement) openElements [openElementCount - 1]).IndentingOverriden = value;
150                         }
151                 }
152
153                 private bool ParentIndentingOverriden {
154                         get {
155                                 if (openElementCount < 2)
156                                         return false;
157                                 return ((XmlTextWriterOpenElement) openElements [openElementCount - 2]).IndentingOverriden;
158                         }
159                 }
160
161                 public int Indentation {
162                         get { return indentation; }
163                         set {
164                                 indentation = value;
165                                 UpdateIndentChars ();
166                         }
167                 }
168
169                 public char IndentChar {
170                         get { return indentChar; }
171                         set {
172                                 indentChar = value;
173                                 UpdateIndentChars ();
174                         }
175                 }
176
177 #if NET_2_0
178                 internal bool CheckCharacters {
179                         get { return checkCharacters; }
180                         set { checkCharacters = value; }
181                 }
182
183                 internal bool CloseOutput {
184 //                      get { return closeOutput; }
185                         set { closeOutput = value; }
186                 }
187
188                 // As for ConformanceLevel, MS.NET is inconsistent with
189                 // MSDN documentation. For example, even if ConformanceLevel
190                 // is set as .Auto, multiple WriteStartDocument() calls
191                 // result in an error.
192                 // ms-help://MS.NETFramework.v20.en/wd_xml/html/7db8802b-53d8-4735-a637-4d2d2158d643.htm
193                 [MonoTODO]
194                 internal ConformanceLevel ConformanceLevel {
195                         get { return conformanceLevel; }
196                         set {
197                                 conformanceLevel = value;
198                                 if (value == ConformanceLevel.Fragment)
199                                         documentStarted = true;
200                         }
201                 }
202
203                 internal string IndentChars {
204 //                      get { return indentChars; }
205                         set { indentChars = value == null ? String.Empty : value; }
206                 }
207
208                 internal string NewLineChars {
209 //                      get { return newLineChars; }
210                         set { newLineChars = value == null ? String.Empty : value; }
211                 }
212
213                 internal bool NewLineOnAttributes {
214 //                      get { return newLineOnAttributes; }
215                         set { newLineOnAttributes = value; }
216                 }
217
218                 internal bool OmitXmlDeclaration {
219 //                      get { return !outputXmlDeclaration; }
220                         set { outputXmlDeclaration = !value; }
221                 }
222 #endif
223
224                 public bool Namespaces {
225                         get { return namespaces; }
226                         set {
227                                 if (ws != WriteState.Start)
228                                         throw new InvalidOperationException ("NotInWriteState.");
229                                 
230                                 namespaces = value;
231                         }
232                 }
233
234                 public char QuoteChar {
235                         get { return quoteChar; }
236                         set {
237                                 if ((value != '\'') && (value != '\"'))
238                                         throw ArgumentError ("This is an invalid XML attribute quote character. Valid attribute quote characters are ' and \".");
239                                 
240                                 quoteChar = value;
241                         }
242                 }
243
244                 public override WriteState WriteState {
245                         get { return ws; }
246                 }
247                 
248                 public override string XmlLang {
249                         get {
250                                 string xmlLang = null;
251                                 int i;
252
253                                 for (i = openElementCount - 1; i >= 0; i--) {
254                                         xmlLang = ((XmlTextWriterOpenElement) openElements [i]).XmlLang;
255                                         if (xmlLang != null)
256                                                 break;
257                                 }
258
259                                 return xmlLang;
260                         }
261                 }
262
263                 public override XmlSpace XmlSpace {
264                         get {
265                                 XmlSpace xmlSpace = XmlSpace.None;
266                                 int i;
267
268                                 for (i = openElementCount - 1; i >= 0; i--) {
269                                         xmlSpace = ((XmlTextWriterOpenElement)openElements [i]).XmlSpace;
270                                         if (xmlSpace != XmlSpace.None)
271                                                 break;
272                                 }
273
274                                 return xmlSpace;
275                         }
276                 }
277
278                 #endregion
279
280                 #region Methods
281 #if NET_2_0
282                 private void CheckStartDocument ()
283                 {
284                         if (outputXmlDeclaration &&
285                                 conformanceLevel == ConformanceLevel.Document &&
286                                 ws == WriteState.Start)
287                                 WriteStartDocument ();
288                 }
289 #endif
290
291                 private void AddMissingElementXmlns ()
292                 {
293                         // output namespace declaration if not exist.
294                         string prefix = openElementPrefix;
295                         string ns = openElementNS;
296                         openElementPrefix = null;
297                         openElementNS = null;
298
299                         // LAMESPEC: If prefix was already assigned another nsuri, then this element's nsuri goes away!
300
301                         if (this.shouldCheckElementXmlns) {
302                                 if (userWrittenNamespaces [prefix] == null) {
303                                         if (prefix != string.Empty) {
304                                                 w.Write (" xmlns:");
305                                                 w.Write (prefix);
306                                                 w.Write ('=');
307                                                 w.Write (quoteChar);
308                                                 w.Write (EscapeString (ns, false));
309                                                 w.Write (quoteChar);
310                                         }
311                                         else {
312                                                 w.Write (" xmlns=");
313                                                 w.Write (quoteChar);
314                                                 w.Write (EscapeString (ns, false));
315                                                 w.Write (quoteChar);
316                                         }
317                                 }
318
319                                 shouldCheckElementXmlns = false;
320                         }
321
322                         if (newAttributeNamespaces.Count > 0)
323                         {
324                                 foreach (DictionaryEntry ent in newAttributeNamespaces)
325                                 {
326                                         string ans = (string) ent.Value;
327                                         string aprefix = (string) ent.Key;
328
329                                         if (namespaceManager.LookupNamespace (aprefix, false) == ans)
330                                                 continue;
331                                         ans = EscapeString (ans, false);
332                                         w.Write (" xmlns:");
333                                         w.Write (aprefix);
334                                         w.Write ('=');
335                                         w.Write (quoteChar);
336                                         w.Write (ans);
337                                         w.Write (quoteChar);
338                                         namespaceManager.AddNamespace (aprefix, ans);
339                                 }
340                                 newAttributeNamespaces.Clear ();
341                         }
342                         autoCreatedPrefixes = 0;
343                 }
344
345                 private void CheckState ()
346                 {
347 #if NET_2_0
348                         CheckStartDocument ();
349 #endif
350                         CheckOutputState ();
351                 }
352
353                 private void CheckOutputState ()
354                 {
355 #if NET_2_0
356                         if (ws == WriteState.Error)
357                                 throw new InvalidOperationException ("Writing at state Error would result in a wrong result.");
358 #endif
359                         if (!openWriter) {
360                                 throw new InvalidOperationException ("The Writer is closed.");
361                         }
362                         if ((documentStarted == true) && (formatting == Formatting.Indented) && (!IndentingOverriden)) {
363                                 indentLocal = true;
364                         }
365                         else
366                                 indentLocal = false;
367
368                         documentStarted = true;
369                 }
370
371                 public override void Close ()
372                 {
373                         CloseOpenAttributeAndElements ();
374
375                         if (closeOutput)
376                                 w.Close ();
377                         else if (ws != WriteState.Closed)
378                                 w.Flush ();
379                         ws = WriteState.Closed;
380                         openWriter = false;
381                 }
382
383                 private void CloseOpenAttributeAndElements ()
384                 {
385                         if (openAttribute)
386                                 WriteEndAttribute ();
387
388                         while (openElementCount > 0) {
389                                 WriteEndElement();
390                         }
391                 }
392
393                 private void CloseStartElement ()
394                 {
395                         if (!openStartElement)
396                                 return;
397
398                         AddMissingElementXmlns ();
399
400                         w.Write (">");
401                         ws = WriteState.Content;
402                         openStartElement = false;
403                         attributeWrittenForElement = false;
404                         newAttributeNamespaces.Clear ();
405                         userWrittenNamespaces.Clear ();
406                 }
407
408                 public override void Flush ()
409                 {
410                         w.Flush ();
411                 }
412
413                 public override string LookupPrefix (string ns)
414                 {
415                         if (ns == null || ns == String.Empty)
416                                 throw ArgumentError ("The Namespace cannot be empty.");
417
418                         string prefix = namespaceManager.LookupPrefix (ns, false);
419                         // XmlNamespaceManager might return such prefix that
420                         // is *previously* mapped to ns passed above.
421                         if (prefix == null || namespaceManager.LookupNamespace (prefix) != ns)
422                                 return null;
423
424                         // XmlNamespaceManager has changed to return null when NSURI not found.
425                         // (Contradiction to the ECMA documentation.)
426                         return prefix;
427                 }
428
429                 private void UpdateIndentChars ()
430                 {
431                         indentChars = new string (indentChar, indentation);
432                 }
433
434                 public override void WriteBase64 (byte[] buffer, int index, int count)
435                 {
436                         CheckState ();
437
438                         if (!openAttribute) {
439                                 IndentingOverriden = true;
440                                 CloseStartElement ();
441                         }
442
443                         w.Write (Convert.ToBase64String (buffer, index, count));
444                 }
445
446                 public override void WriteBinHex (byte[] buffer, int index, int count)
447                 {
448                         CheckState ();
449
450                         if (!openAttribute) {
451                                 IndentingOverriden = true;
452                                 CloseStartElement ();
453                         }
454
455                         XmlConvert.WriteBinHex (buffer, index, count, w);
456                 }
457
458                 public override void WriteCData (string text)
459                 {
460                         if (text.IndexOf ("]]>") >= 0)
461                                 throw ArgumentError ("CDATA section cannot contain text \"]]>\".");
462
463                         CheckState ();
464                         IndentingOverriden = true;
465                         CloseStartElement ();
466                         
467                         w.Write ("<![CDATA[");
468                         w.Write (text);
469                         w.Write ("]]>");
470                 }
471
472                 public override void WriteCharEntity (char ch)
473                 {
474                         Int16   intCh = (Int16)ch;
475
476                         // Make sure the character is not in the surrogate pair
477                         // character range, 0xd800- 0xdfff
478                         if ((intCh >= -10240) && (intCh <= -8193))
479                                 throw ArgumentError ("Surrogate Pair is invalid.");
480
481                         w.Write("&#x{0:X};", intCh);
482                 }
483
484                 public override void WriteChars (char[] buffer, int index, int count)
485                 {
486                         CheckState ();
487
488                         if (!openAttribute) {
489                                 IndentingOverriden = true;
490                                 CloseStartElement ();
491                         }
492
493                         w.Write (buffer, index, count);
494                 }
495
496                 public override void WriteComment (string text)
497                 {
498                         if (text.EndsWith("-"))
499                                 throw ArgumentError ("An XML comment cannot contain \"--\" inside.");
500                         else if (text.IndexOf("--") > 0)
501                                 throw ArgumentError ("An XML comment cannot end with \"-\".");
502
503                         CheckState ();
504                         CloseStartElement ();
505
506                         WriteIndent ();
507
508                         w.Write ("<!--");
509                         w.Write (text);
510                         w.Write ("-->");
511                 }
512
513                 public override void WriteDocType (string name, string pubid, string sysid, string subset)
514                 {
515                         if (name == null || name.Trim (XmlChar.WhitespaceChars).Length == 0)
516                                 throw ArgumentError ("Invalid DOCTYPE name", "name");
517
518                         if (ws == WriteState.Prolog && formatting == Formatting.Indented)
519                                 w.WriteLine ();
520
521                         w.Write ("<!DOCTYPE ");
522                         w.Write (name);
523                         if (pubid != null) {
524                                 w.Write (" PUBLIC ");
525                                 w.Write (quoteChar);
526                                 w.Write (pubid);
527                                 w.Write (quoteChar);
528                                 w.Write (' ');
529                                 w.Write (quoteChar);
530                                 w.Write (sysid);
531                                 w.Write (quoteChar);
532                         } else if (sysid != null) {
533                                 w.Write (" SYSTEM ");
534                                 w.Write (quoteChar);
535                                 w.Write (sysid);
536                                 w.Write (quoteChar);
537                         }
538
539                         if (subset != null) {
540                                 w.Write ('[');
541                                 w.Write (subset);
542                                 w.Write (']');
543                         }
544                         
545                         w.Write('>');
546                 }
547
548                 public override void WriteEndAttribute ()
549                 {
550                         if (!openAttribute)
551                                 throw InvalidOperationError ("Token EndAttribute in state Start would result in an invalid XML document.");
552
553                         CheckState ();
554
555                         if (openXmlLang) {
556                                 w.Write (xmlLang);
557                                 openXmlLang = false;
558                                 ((XmlTextWriterOpenElement) openElements [openElementCount - 1]).XmlLang = xmlLang;
559                         }
560
561                         if (openXmlSpace) 
562                         {
563                                 if (xmlSpace == XmlSpace.Preserve)
564                                         w.Write ("preserve");
565                                 else if (xmlSpace == XmlSpace.Default)
566                                         w.Write ("default");
567                                 openXmlSpace = false;
568                                 ((XmlTextWriterOpenElement) openElements [openElementCount - 1]).XmlSpace = xmlSpace;
569                         }
570
571                         w.Write (quoteChar);
572
573                         openAttribute = false;
574
575                         if (saveAttributeValue) {
576                                 if (savedAttributePrefix.Length > 0 && savingAttributeValue.Length == 0)
577                                         throw ArgumentError ("Cannot use prefix with an empty namespace.");
578
579                                 // add namespace
580                                 if (shouldAddSavedNsToManager) // not OLD one
581                                         namespaceManager.AddNamespace (savedAttributePrefix, savingAttributeValue);
582                                 userWrittenNamespaces [savedAttributePrefix] = savingAttributeValue;
583                                 saveAttributeValue = false;
584                                 savedAttributePrefix = String.Empty;
585                                 savingAttributeValue = String.Empty;
586                         }
587                 }
588
589                 public override void WriteEndDocument ()
590                 {
591                         CloseOpenAttributeAndElements ();
592
593                         if (!hasRoot)
594                                 throw ArgumentError ("This document does not have a root element.");
595
596                         ws = WriteState.Start;
597                         hasRoot = false;
598                 }
599
600                 public override void WriteEndElement ()
601                 {
602                         WriteEndElementInternal (false);
603                 }
604
605                 private void WriteIndent ()
606                 {
607                         if (!indentLocal)
608                                 return;
609                         w.Write (newLineChars);
610                         for (int i = 0; i < indentLevel; i++)
611                                 w.Write (indentChars);
612                 }
613
614                 private void WriteEndElementInternal (bool fullEndElement)
615                 {
616                         if (openElementCount == 0)
617                                 throw InvalidOperationError ("There was no XML start tag open.");
618
619                         if (openAttribute)
620                                 WriteEndAttribute ();
621
622                         indentLevel--;
623                         CheckState ();
624                         AddMissingElementXmlns ();
625
626                         if (openStartElement) {
627                                 if (openAttribute)
628                                         WriteEndAttribute ();
629                                 if (fullEndElement) {
630                                         w.Write ('>');
631                                         if (!ParentIndentingOverriden)
632                                                 WriteIndent ();
633                                         w.Write ("</");
634                                         XmlTextWriterOpenElement el = (XmlTextWriterOpenElement) openElements [openElementCount - 1];
635                                         if (el.Prefix != String.Empty) {
636                                                 w.Write (el.Prefix);
637                                                 w.Write (':');
638                                         }
639                                         w.Write (el.LocalName);
640                                         w.Write ('>');
641                                 } else
642                                         w.Write (" />");
643
644                                 openElementCount--;
645                                 openStartElement = false;
646                         } else {
647                                 WriteIndent ();
648                                 w.Write ("</");
649                                 XmlTextWriterOpenElement el = (XmlTextWriterOpenElement) openElements [openElementCount - 1];
650                                 openElementCount--;
651                                 if (el.Prefix != String.Empty) {
652                                         w.Write (el.Prefix);
653                                         w.Write (':');
654                                 }
655                                 w.Write (el.LocalName);
656                                 w.Write ('>');
657                         }
658
659                         namespaceManager.PopScope();
660                 }
661
662                 public override void WriteEntityRef (string name)
663                 {
664                         WriteRaw ("&");
665                         WriteStringInternal (name, true);
666                         WriteRaw (";");
667                 }
668
669                 public override void WriteFullEndElement ()
670                 {
671                         WriteEndElementInternal (true);
672                 }
673
674                 public override void WriteName (string name)
675                 {
676                         WriteNameInternal (name);
677                 }
678
679                 public override void WriteNmToken (string name)
680                 {
681                         WriteNmTokenInternal (name);
682                 }
683
684                 // LAMESPEC: It should reject such name that starts with "x" "m" "l" by XML specification, but
685                 // in fact it is used to write XmlDeclaration in WriteNode() (and it is inevitable since
686                 // WriteStartDocument() cannot specify encoding, while WriteNode() can write it).
687                 public override void WriteProcessingInstruction (string name, string text)
688                 {
689                         if ((name == null) || (name == string.Empty))
690                                 throw ArgumentError ("Argument processing instruction name must not be null or empty.");
691                         if (!XmlChar.IsName (name))
692                                 throw ArgumentError ("Invalid processing instruction name.");
693                         if ((text.IndexOf("?>") > 0))
694                                 throw ArgumentError ("Processing instruction cannot contain \"?>\" as its value.");
695
696                         CheckState ();
697                         CloseStartElement ();
698
699                         WriteIndent ();
700                         w.Write ("<?");
701                         w.Write (name);
702                         w.Write (' ');
703                         w.Write (text);
704                         w.Write ("?>");
705                 }
706
707                 public override void WriteQualifiedName (string localName, string ns)
708                 {
709                         if (!XmlChar.IsNCName (localName))
710                                 throw ArgumentError (String.Format ("Invalid local name '{0}'", localName));
711
712                         CheckState ();
713                         if (!openAttribute)
714                                 CloseStartElement ();
715
716                         // WriteQualifiedName internal will reject such
717                         // qname whose namespace is not declared.
718                         string prefix = null;
719                         if (openAttribute && ns != String.Empty && LookupPrefix (ns) == null) {
720                                 prefix = CheckNewPrefix (true, null, ns);
721                                 namespaceManager.AddNamespace (prefix, ns);
722                         }
723
724                         WriteQualifiedNameInternal (localName, ns);
725
726                         if (prefix != null) {
727                                 namespaceManager.RemoveNamespace (prefix, ns);
728                                 newAttributeNamespaces [prefix] = ns;
729                         }
730                 }
731
732                 public override void WriteRaw (string data)
733                 {
734                         if (ws == WriteState.Start)
735                                 ws = WriteState.Prolog;
736                         WriteStringInternal (data, false);
737                 }
738
739                 public override void WriteRaw (char[] buffer, int index, int count)
740                 {
741                         WriteRaw (new string (buffer, index, count));
742                 }
743
744                 public override void WriteStartAttribute (string prefix, string localName, string ns)
745                 {
746                         if (prefix == "xml") {
747                                 // MS.NET looks to allow other names than 
748                                 // lang and space (e.g. xml:link, xml:hack).
749                                 ns = XmlNamespaceManager.XmlnsXml;
750                                 if (localName == "lang")
751                                         openXmlLang = true;
752                                 else if (localName == "space")
753                                         openXmlSpace = true;
754                         }
755                         if (prefix == null)
756                                 prefix = String.Empty;
757
758                         if (prefix.Length > 0 && (ns == null || ns.Length == 0))
759                                 if (prefix != "xmlns")
760                                         throw ArgumentError ("Cannot use prefix with an empty namespace.");
761
762                         if (prefix == "xmlns") {
763                                 if (localName == null || localName.Length == 0) {
764                                         localName = prefix;
765                                         prefix = String.Empty;
766                                 }
767                         }
768
769                         // Note that null namespace with "xmlns" are allowed.
770 #if NET_1_0
771                         if ((prefix == "xmlns" || localName == "xmlns" && prefix == String.Empty) && ns != XmlnsNamespace)
772 #else
773                         if ((prefix == "xmlns" || localName == "xmlns" && prefix == String.Empty) && ns != null && ns != XmlnsNamespace)
774 #endif
775                                 throw ArgumentError (String.Format ("The 'xmlns' attribute is bound to the reserved namespace '{0}'", XmlnsNamespace));
776
777                         CheckState ();
778
779                         if (ws == WriteState.Content)
780                                 throw InvalidOperationError (String.Format ("Token StartAttribute in state {0} would result in an invalid XML document.", ws));
781
782                         if (prefix == null)
783                                 prefix = String.Empty;
784
785                         if (ns == null)
786                                 ns = String.Empty;
787
788                         string formatPrefix = "";
789
790                         if (ns != String.Empty && prefix != "xmlns") {
791                                 string existingPrefix = namespaceManager.LookupPrefix (ns, false);
792
793                                 if (existingPrefix == null || existingPrefix == "") {
794                                         bool createPrefix = false;
795                                         if (prefix == "")
796                                                 createPrefix = true;
797                                         else {
798                                                 string existingNs = namespaceManager.LookupNamespace (prefix, false);
799                                                 if (existingNs != null) {
800                                                         namespaceManager.RemoveNamespace (prefix, existingNs);
801                                                         if (namespaceManager.LookupNamespace (prefix, false) != existingNs) {
802                                                                 createPrefix = true;
803                                                                 namespaceManager.AddNamespace (prefix, existingNs);
804                                                         }
805                                                 }
806                                         }
807
808                                         prefix = CheckNewPrefix (createPrefix, prefix, ns);
809                                 }
810
811                                 if (prefix == String.Empty && ns != XmlnsNamespace)
812                                         prefix = (existingPrefix == null) ?
813                                                 String.Empty : existingPrefix;
814                         }
815
816                         if (prefix != String.Empty) 
817                         {
818                                 formatPrefix = prefix + ":";
819                         }
820
821                         if (openStartElement || attributeWrittenForElement) {
822                                 if (newLineOnAttributes)
823                                         WriteIndent ();
824                                 else
825                                         w.Write (" ");
826                         }
827
828                         w.Write (formatPrefix);
829                         w.Write (localName);
830                         w.Write ('=');
831                         w.Write (quoteChar);
832
833                         openAttribute = true;
834                         attributeWrittenForElement = true;
835                         ws = WriteState.Attribute;
836
837                         if (prefix == "xmlns" || prefix == String.Empty && localName == "xmlns") {
838                                 if (prefix != openElementPrefix || openElementNS == null)
839                                         shouldAddSavedNsToManager = true; 
840                                 saveAttributeValue = true;
841                                 savedAttributePrefix = (prefix == "xmlns") ? localName : String.Empty;
842                                 savingAttributeValue = String.Empty;
843                         }
844                 }
845
846                 private string CheckNewPrefix (bool createPrefix, string prefix, string ns)
847                 {
848                         do {
849                                 if (createPrefix)
850                                         prefix = "d" + indentLevel + "p" + (++autoCreatedPrefixes);
851                                 createPrefix = false;
852                                 // Check if prefix exists.
853                                 // If yes - check if namespace is the same.
854                                 if (newAttributeNamespaces [prefix] == null)
855                                         newAttributeNamespaces.Add (prefix, ns);
856                                 else if (!newAttributeNamespaces [prefix].Equals (ns))
857                                         createPrefix = true;
858                         } while (createPrefix);
859                         return prefix;
860                 }
861
862                 public override void WriteStartDocument ()
863                 {
864                         WriteStartDocument ("");
865                 }
866
867                 public override void WriteStartDocument (bool standalone)
868                 {
869                         string standaloneFormatting;
870
871                         if (standalone == true)
872                                 standaloneFormatting = String.Format (" standalone={0}yes{0}", quoteChar);
873                         else
874                                 standaloneFormatting = String.Format (" standalone={0}no{0}", quoteChar);
875
876                         WriteStartDocument (standaloneFormatting);
877                 }
878
879                 private void WriteStartDocument (string standaloneFormatting)
880                 {
881                         if (documentStarted == true)
882                                 throw InvalidOperationError ("WriteStartDocument should be the first call.");
883
884                         if (hasRoot)
885                                 throw XmlError ("WriteStartDocument called twice.");
886                         isDocumentEntity = true;
887
888 //                      CheckState ();
889                         CheckOutputState ();
890
891                         string encodingFormatting = "";
892
893                         if (!nullEncoding) 
894                                 encodingFormatting = String.Format (" encoding={0}{1}{0}", quoteChar, w.Encoding.WebName);
895
896                         w.Write("<?xml version={0}1.0{0}{1}{2}?>", quoteChar, encodingFormatting, standaloneFormatting);
897                         ws = WriteState.Prolog;
898                 }
899
900                 public override void WriteStartElement (string prefix, string localName, string ns)
901                 {
902                         if (!Namespaces && (((prefix != null) && (prefix != String.Empty))
903                                 || ((ns != null) && (ns != String.Empty))))
904                                 throw ArgumentError ("Cannot set the namespace if Namespaces is 'false'.");
905                         if ((prefix != null && prefix != String.Empty) && ((ns == null) || (ns == String.Empty)))
906                                 throw ArgumentError ("Cannot use a prefix with an empty namespace.");
907
908                         // ignore non-namespaced node's prefix.
909                         if (ns == null || ns == String.Empty)
910                                 prefix = String.Empty;
911
912
913                         WriteStartElementInternal (prefix, localName, ns);
914                 }
915
916                 private void WriteStartElementInternal (string prefix, string localName, string ns)
917                 {
918                         CheckState ();
919                         hasRoot = true;
920                         CloseStartElement ();
921                         newAttributeNamespaces.Clear ();
922                         userWrittenNamespaces.Clear ();
923                         shouldCheckElementXmlns = false;
924
925                         if (prefix == null && ns != null)
926                                 prefix = namespaceManager.LookupPrefix (ns, false);
927                         if (prefix == null)
928                                 prefix = String.Empty;
929
930                         WriteIndent ();
931                         w.Write ('<');
932                         if (prefix != String.Empty) {
933                                 w.Write (prefix);
934                                 w.Write (':');
935                         }
936                         w.Write (localName);
937
938                         if (openElements.Count == openElementCount)
939                                 openElements.Add (new XmlTextWriterOpenElement (prefix, localName));
940                         else
941                                 ((XmlTextWriterOpenElement) openElements [openElementCount]).Reset (prefix, localName);
942                         openElementCount++;
943                         ws = WriteState.Element;
944                         openStartElement = true;
945                         openElementNS = ns;
946                         openElementPrefix = prefix;
947
948                         namespaceManager.PushScope ();
949                         indentLevel++;
950
951                         if (ns != null) {
952                                 if (ns.Length > 0) {
953                                         string existing = LookupPrefix (ns);
954                                         if (existing != prefix) {
955                                                 shouldCheckElementXmlns = true;
956                                                 namespaceManager.AddNamespace (prefix, ns);
957                                         }
958                                 } else {
959                                         if (ns != namespaceManager.DefaultNamespace) {
960                                                 shouldCheckElementXmlns = true;
961                                                 namespaceManager.AddNamespace ("", ns);
962                                         }
963                                 }
964                         }
965                 }
966
967                 public override void WriteString (string text)
968                 {
969                         switch (ws) {
970                         case WriteState.Start:
971                         case WriteState.Prolog:
972                                 if (isDocumentEntity)
973                                         throw InvalidOperationError ("Token content in state Prolog would result in an invalid XML document.");
974                                 ws = WriteState.Content;
975                                 break;
976                         }
977
978                         WriteStringInternal (text, true);
979
980                         // MS.NET (1.0) saves attribute value only at WriteString.
981                         if (saveAttributeValue)
982                                 // In most cases it will be called one time, so simply use string + string.
983                                 savingAttributeValue += text;
984                 }
985
986                 string [] replacements = new string [] {
987                         "&amp;", "&lt;", "&gt;", "&quot;", "&apos;",
988                         "&#xD;", "&#xA;"};
989
990                 private string EscapeString (string source, bool outsideAttribute)
991                 {
992                         int start = 0;
993                         int pos = 0;
994                         int count = source.Length;
995                         char invalid = ' ';
996                         for (int i = 0; i < count; i++) {
997                                 switch (source [i]) {
998                                 case '&':  pos = 0; break;
999                                 case '<':  pos = 1; break;
1000                                 case '>':  pos = 2; break;
1001                                 case '\"':
1002                                         if (outsideAttribute) continue;
1003                                         if (QuoteChar == '\'') continue;
1004                                         pos = 3; break;
1005                                 case '\'':
1006                                         if (outsideAttribute) continue;
1007                                         if (QuoteChar == '\"') continue;
1008                                         pos = 4; break;
1009                                 case '\r':
1010                                         if (outsideAttribute)
1011                                                 continue;
1012                                         pos = 5; break;
1013                                 case '\n':
1014                                         if (outsideAttribute)
1015                                                 continue;
1016                                         pos = 6; break;
1017                                 default:
1018                                         if (XmlChar.IsInvalid (source [i])) {
1019                                                 if (Char.IsSurrogate (source [i]) && source [i] < 0xDC00 &&
1020                                                     i + 1 < count && Char.IsSurrogate (source [i + 1]) && source [i + 1] >= 0xDC00) {
1021                                                         // A legitimate UTF-16 surrogate pair; let it through.
1022                                                         i++;
1023                                                         continue;
1024                                                 } else {
1025                                                         if (checkCharacters)
1026                                                                 throw ArgumentError (String.Format ("Character hexadecimal value {0:4x} is invalid.", (int) source [i]));
1027                                                         invalid = source [i];
1028                                                         pos = -1;
1029                                                         break;
1030                                                 }
1031                                         }
1032                                         else
1033                                                 continue;
1034                                 }
1035                                 if (cachedStringBuilder == null)
1036                                         cachedStringBuilder = new StringBuilder ();
1037                                 cachedStringBuilder.Append (source.Substring (start, i - start));
1038                                 if (pos < 0) {
1039                                         cachedStringBuilder.Append ("&#x");
1040 //                                      if (invalid < (char) 255)
1041 //                                              cachedStringBuilder.Append (((int) invalid).ToString ("X02", CultureInfo.InvariantCulture));
1042 //                                      else
1043 //                                              cachedStringBuilder.Append (((int) invalid).ToString ("X04", CultureInfo.InvariantCulture));
1044                                         cachedStringBuilder.Append (((int) invalid).ToString ("X", CultureInfo.InvariantCulture));
1045                                         cachedStringBuilder.Append (";");
1046                                 }
1047 #if NET_2_0
1048                                 else if (outsideAttribute && pos >= 5) {
1049                                         cachedStringBuilder.Append (newLineChars);
1050                                         // all \r,\n,\r\n are replaced with
1051                                         // NewLineChars, so \n after \r should
1052                                         // be consumed here.
1053                                         if (pos == 5 && i + 1 < count && source [i + 1] == '\n')
1054                                                 i++;
1055                                 }
1056 #endif
1057                                 else
1058                                         cachedStringBuilder.Append (replacements [pos]);
1059                                 start = i + 1;
1060                         }
1061                         if (start == 0)
1062                                 return source;
1063                         else if (start < count)
1064                                 cachedStringBuilder.Append (source.Substring (start, count - start));
1065                         string s = cachedStringBuilder.ToString ();
1066                         cachedStringBuilder.Length = 0;
1067                         return s;
1068                 }
1069
1070                 private void WriteStringInternal (string text, bool entitize)
1071                 {
1072                         if (text == null)
1073                                 text = String.Empty;
1074
1075                         if (text != String.Empty) {
1076                                 CheckState ();
1077
1078                                 if (entitize)
1079                                         text = EscapeString (text, !openAttribute);
1080
1081                                 if (!openAttribute)
1082                                 {
1083                                         IndentingOverriden = true;
1084                                         CloseStartElement ();
1085                                 }
1086
1087                                 if (!openXmlLang && !openXmlSpace)
1088                                         w.Write (text);
1089                                 else 
1090                                 {
1091                                         if (openXmlLang)
1092                                                 xmlLang = text;
1093                                         else 
1094                                         {
1095                                                 switch (text) 
1096                                                 {
1097                                                         case "default":
1098                                                                 xmlSpace = XmlSpace.Default;
1099                                                                 break;
1100                                                         case "preserve":
1101                                                                 xmlSpace = XmlSpace.Preserve;
1102                                                                 break;
1103                                                         default:
1104                                                                 throw ArgumentError ("'{0}' is an invalid xml:space value.");
1105                                                 }
1106                                         }
1107                                 }
1108                         }
1109                 }
1110
1111                 public override void WriteSurrogateCharEntity (char lowChar, char highChar)
1112                 {
1113                         if (lowChar < '\uDC00' || lowChar > '\uDFFF' ||
1114                                 highChar < '\uD800' || highChar > '\uDBFF')
1115                                 throw ArgumentError ("Invalid (low, high) pair of characters was specified.");
1116
1117                         CheckState ();
1118
1119                         if (!openAttribute) {
1120                                 IndentingOverriden = true;
1121                                 CloseStartElement ();
1122                         }
1123
1124                         w.Write ("&#x");
1125                         w.Write (((int) ((highChar - 0xD800) * 0x400 + (lowChar - 0xDC00) + 0x10000)).ToString ("X", CultureInfo.InvariantCulture));
1126                         w.Write (';');
1127                 }
1128
1129                 public override void WriteWhitespace (string ws)
1130                 {
1131                         if (!XmlChar.IsWhitespace (ws))
1132                                 throw ArgumentError ("Invalid Whitespace");
1133
1134                         CheckState ();
1135
1136                         if (!openAttribute) {
1137                                 IndentingOverriden = true;
1138                                 CloseStartElement ();
1139                         }
1140
1141                         w.Write (ws);
1142                 }
1143
1144                 private Exception ArgumentError (string message)
1145                 {
1146 #if NET_2_0
1147                         ws = WriteState.Error;
1148 #endif
1149                         return new ArgumentException (message);
1150                 }
1151
1152                 private Exception ArgumentError (string message, string name)
1153                 {
1154 #if NET_2_0
1155                         ws = WriteState.Error;
1156 #endif
1157                         return new ArgumentException (message);
1158                 }
1159
1160                 private Exception InvalidOperationError (string message)
1161                 {
1162 #if NET_2_0
1163                         ws = WriteState.Error;
1164 #endif
1165                         return new InvalidOperationException (message);
1166                 }
1167
1168                 private Exception XmlError (string message)
1169                 {
1170 #if NET_2_0
1171                         ws = WriteState.Error;
1172 #endif
1173                         return new XmlException (message);
1174                 }
1175
1176                 #endregion
1177         }
1178 }