2002-09-18 Gonzalo Paniagua Javier <gonzalo@ximian.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 //
7 // (C) 2002 Kral Ferch
8 //
9
10 using System;
11 using System.Collections;
12 using System.IO;
13 using System.Text;
14
15 namespace System.Xml
16 {
17         public class XmlTextWriter : XmlWriter
18         {
19                 #region Fields
20
21                 TextWriter w;
22                 bool nullEncoding = false;
23                 bool openWriter = true;
24                 bool openStartElement = false;
25                 bool openStartAttribute = false;
26                 bool documentStarted = false;
27                 bool namespaces = true;
28                 bool openAttribute = false;
29                 bool attributeWrittenForElement = false;
30                 Stack openElements = new Stack ();
31                 Formatting formatting = Formatting.None;
32                 int indentation = 2;
33                 char indentChar = ' ';
34                 string indentChars = "  ";
35                 char quoteChar = '\"';
36                 int indentLevel = 0;
37                 string indentFormatting;
38                 Stream baseStream = null;
39                 string xmlLang = null;
40                 XmlSpace xmlSpace = XmlSpace.None;
41                 bool openXmlLang = false;
42                 bool openXmlSpace = false;
43
44                 #endregion
45
46                 #region Constructors
47
48                 public XmlTextWriter (TextWriter w) : base ()
49                 {
50                         this.w = w;
51                         nullEncoding = (w.Encoding == null);
52                         
53                         try {
54                                 baseStream = ((StreamWriter)w).BaseStream;
55                         }
56                         catch (Exception) { }
57                 }
58
59                 public XmlTextWriter (Stream w, Encoding encoding) : base ()
60                 {
61                         if (encoding == null) {
62                                 nullEncoding = true;
63                                 encoding = new UTF8Encoding ();
64                         }
65
66                         this.w = new StreamWriter(w, encoding);
67                         baseStream = w;
68                 }
69
70                 public XmlTextWriter (string filename, Encoding encoding) :
71                         this (new FileStream (filename, FileMode.Create, FileAccess.Write, FileShare.None), encoding)
72                 {
73                 }
74
75                 #endregion
76
77                 #region Properties
78
79                 public Stream BaseStream {
80                         get { return baseStream; }
81                 }
82
83
84                 public Formatting Formatting {
85                         get { return formatting; }
86                         set { formatting = value; }
87                 }
88
89                 private bool IndentingOverriden 
90                 {
91                         get {
92                                 if (openElements.Count == 0)
93                                         return false;
94                                 else
95                                         return (((XmlTextWriterOpenElement)openElements.Peek()).IndentingOverriden);
96                         }
97                         set {
98                                 if (openElements.Count > 0)
99                                         ((XmlTextWriterOpenElement)openElements.Peek()).IndentingOverriden = value;
100                         }
101                 }
102
103                 public int Indentation {
104                         get { return indentation; }
105                         set {
106                                 indentation = value;
107                                 UpdateIndentChars ();
108                         }
109                 }
110
111                 public char IndentChar {
112                         get { return indentChar; }
113                         set {
114                                 indentChar = value;
115                                 UpdateIndentChars ();
116                         }
117                 }
118
119                 public bool Namespaces {
120                         get { return namespaces; }
121                         set {
122                                 if (ws != WriteState.Start)
123                                         throw new InvalidOperationException ("NotInWriteState.");
124                                 
125                                 namespaces = value;
126                         }
127                 }
128
129                 public char QuoteChar {
130                         get { return quoteChar; }
131                         set {
132                                 if ((value != '\'') && (value != '\"'))
133                                         throw new ArgumentException ("This is an invalid XML attribute quote character. Valid attribute quote characters are ' and \".");
134                                 
135                                 quoteChar = value;
136                         }
137                 }
138
139                 public override WriteState WriteState {
140                         get { return ws; }
141                 }
142                 
143                 public override string XmlLang {
144                         get {
145                                 string xmlLang = null;
146                                 int i;
147
148                                 for (i = 0; i < openElements.Count; i++) 
149                                 {
150                                         xmlLang = ((XmlTextWriterOpenElement)openElements.ToArray().GetValue(i)).XmlLang;
151                                         if (xmlLang != null)
152                                                 break;
153                                 }
154
155                                 return xmlLang;
156                         }
157                 }
158
159                 public override XmlSpace XmlSpace {
160                         get {
161                                 XmlSpace xmlSpace = XmlSpace.None;
162                                 int i;
163
164                                 for (i = 0; i < openElements.Count; i++) 
165                                 {
166                                         xmlSpace = ((XmlTextWriterOpenElement)openElements.ToArray().GetValue(i)).XmlSpace;
167                                         if (xmlSpace != XmlSpace.None)
168                                                 break;
169                                 }
170
171                                 return xmlSpace;
172                         }
173                 }
174
175                 #endregion
176
177                 #region Methods
178
179                 private void CheckState ()
180                 {
181                         if (!openWriter) {
182                                 throw new InvalidOperationException ("The Writer is closed.");
183                         }
184                         if ((documentStarted == true) && (formatting == Formatting.Indented) && (!IndentingOverriden)) {
185                                 indentFormatting = "\r\n";
186                                 if (indentLevel > 0) {
187                                         for (int i = 0; i < indentLevel; i++)
188                                                 indentFormatting += indentChars;
189                                 }
190                         }
191                         else
192                                 indentFormatting = "";
193
194                         documentStarted = true;
195                 }
196
197                 public override void Close ()
198                 {
199                         CloseOpenAttributeAndElements ();
200
201                         w.Close();
202                         ws = WriteState.Closed;
203                         openWriter = false;
204                 }
205
206                 private void CloseOpenAttributeAndElements ()
207                 {
208                         if (openAttribute)
209                                 WriteEndAttribute ();
210
211                         while (openElements.Count > 0) {
212                                 WriteEndElement();
213                         }
214                 }
215
216                 private void CloseStartElement ()
217                 {
218                         if (openStartElement) {
219                                 w.Write(">");
220                                 ws = WriteState.Content;
221                                 openStartElement = false;
222                                 attributeWrittenForElement = false;
223                         }
224                 }
225
226                 public override void Flush ()
227                 {
228                         w.Flush ();
229                 }
230
231                 public override string LookupPrefix (string ns)
232                 {
233                         string prefix = namespaceManager.LookupPrefix (ns);
234
235                         if (prefix == String.Empty)
236                                 prefix = null;
237
238                         return prefix;
239                 }
240
241                 private void UpdateIndentChars ()
242                 {
243                         indentChars = "";
244                         for (int i = 0; i < indentation; i++)
245                                 indentChars += indentChar;
246                 }
247
248                 public override void WriteBase64 (byte[] buffer, int index, int count)
249                 {
250                         w.Write (Convert.ToBase64String (buffer, index, count));
251                 }
252
253                 [MonoTODO]
254                 public override void WriteBinHex (byte[] buffer, int index, int count)
255                 {
256                         throw new NotImplementedException ();
257                 }
258
259                 public override void WriteCData (string text)
260                 {
261                         if (text.IndexOf("]]>") > 0)
262                                 throw new ArgumentException ();
263
264                         CheckState ();
265                         CloseStartElement ();
266
267                         w.Write("<![CDATA[{0}]]>", text);
268                 }
269
270                 public override void WriteCharEntity (char ch)
271                 {
272                         Int16   intCh = (Int16)ch;
273
274                         // Make sure the character is not in the surrogate pair
275                         // character range, 0xd800- 0xdfff
276                         if ((intCh >= -10240) && (intCh <= -8193))
277                                 throw new ArgumentException ("Surrogate Pair is invalid.");
278
279                         w.Write("&#x{0:X};", intCh);
280                 }
281
282                 [MonoTODO]
283                 public override void WriteChars (char[] buffer, int index, int count)
284                 {
285                         throw new NotImplementedException ();
286                 }
287
288                 public override void WriteComment (string text)
289                 {
290                         if ((text.EndsWith("-")) || (text.IndexOf("-->") > 0)) {
291                                 throw new ArgumentException ();
292                         }
293
294                         CheckState ();
295                         CloseStartElement ();
296
297                         w.Write ("<!--{0}-->", text);
298                 }
299
300                 [MonoTODO]
301                 public override void WriteDocType (string name, string pubid, string sysid, string subset)
302                 {
303                         throw new NotImplementedException ();
304                 }
305
306                 public override void WriteEndAttribute ()
307                 {
308                         if (!openAttribute)
309                                 throw new InvalidOperationException("Token EndAttribute in state Start would result in an invalid XML document.");
310
311                         CheckState ();
312
313                         if (openXmlLang) {
314                                 w.Write (xmlLang);
315                                 openXmlLang = false;
316                                 ((XmlTextWriterOpenElement)openElements.Peek()).XmlLang = xmlLang;
317                         }
318
319                         if (openXmlSpace) 
320                         {
321                                 w.Write (xmlSpace.ToString ().ToLower ());
322                                 openXmlSpace = false;
323                                 ((XmlTextWriterOpenElement)openElements.Peek()).XmlSpace = xmlSpace;
324                         }
325
326                         w.Write ("{0}", quoteChar);
327
328                         openAttribute = false;
329                 }
330
331                 public override void WriteEndDocument ()
332                 {
333                         CloseOpenAttributeAndElements ();
334
335                         if ((ws == WriteState.Start) || (ws == WriteState.Prolog))
336                                 throw new ArgumentException ("This document does not have a root element.");
337
338                         ws = WriteState.Start;
339                 }
340
341                 public override void WriteEndElement ()
342                 {
343                         WriteEndElementInternal (false);
344                 }
345
346                 private void WriteEndElementInternal (bool fullEndElement)
347                 {
348                         if (openElements.Count == 0)
349                                 throw new InvalidOperationException("There was no XML start tag open.");
350
351                         indentLevel--;
352                         CheckState ();
353
354                         if (openStartElement) {
355                                 if (openAttribute)
356                                         WriteEndAttribute ();
357                                 if (fullEndElement)
358                                         w.Write ("></{0}>", ((XmlTextWriterOpenElement)openElements.Peek ()).Name);
359                                 else
360                                         w.Write (" />");
361
362                                 openElements.Pop ();
363                                 openStartElement = false;
364                         } else {
365                                 w.Write ("{0}</{1}>", indentFormatting, openElements.Pop ());
366                         }
367
368                         namespaceManager.PopScope();
369                 }
370
371                 [MonoTODO]
372                 public override void WriteEntityRef (string name)
373                 {
374                         throw new NotImplementedException ();
375                 }
376
377                 public override void WriteFullEndElement ()
378                 {
379                         WriteEndElementInternal (true);
380                 }
381
382                 [MonoTODO]
383                 public override void WriteName (string name)
384                 {
385                         throw new NotImplementedException ();
386                 }
387
388                 [MonoTODO]
389                 public override void WriteNmToken (string name)
390                 {
391                         throw new NotImplementedException ();
392                 }
393
394                 public override void WriteProcessingInstruction (string name, string text)
395                 {
396                         if ((name == null) || (name == string.Empty) || (name.IndexOf("?>") > 0) || (text.IndexOf("?>") > 0)) {
397                                 throw new ArgumentException ();
398                         }
399
400                         CheckState ();
401                         CloseStartElement ();
402
403                         w.Write ("{0}<?{1} {2}?>", indentFormatting, name, text);
404                 }
405
406                 [MonoTODO]
407                 public override void WriteQualifiedName (string localName, string ns)
408                 {
409                         if (localName == null || localName == String.Empty)
410                                 throw new ArgumentException ();
411
412                         CheckState ();
413                         w.Write ("{0}:{1}", ns, localName);
414                 }
415
416                 public override void WriteRaw (string data)
417                 {
418                         WriteStringInternal (data, false);
419                 }
420
421                 [MonoTODO]
422                 public override void WriteRaw (char[] buffer, int index, int count)
423                 {
424                         throw new NotImplementedException ();
425                 }
426
427                 public override void WriteStartAttribute (string prefix, string localName, string ns)
428                 {
429                         if ((prefix == "xml") && (localName == "lang"))
430                                 openXmlLang = true;
431
432                         if ((prefix == "xml") && (localName == "space"))
433                                 openXmlSpace = true;
434
435                         if ((prefix == "xmlns") && (localName == "xmlns"))
436                                 throw new ArgumentException ("Prefixes beginning with \"xml\" (regardless of whether the characters are uppercase, lowercase, or some combination thereof) are reserved for use by XML.");
437
438                         CheckState ();
439
440                         if (ws == WriteState.Content)
441                                 throw new InvalidOperationException ("Token StartAttribute in state " + WriteState + " would result in an invalid XML document.");
442
443                         if (prefix == null)
444                                 prefix = String.Empty;
445
446                         if (ns == null)
447                                 ns = String.Empty;
448
449                         string formatPrefix = "";
450                         string formatSpace = "";
451
452                         if (ns != String.Empty) 
453                         {
454                                 string existingPrefix = namespaceManager.LookupPrefix (ns);
455
456                                 if (prefix == String.Empty)
457                                         prefix = existingPrefix;
458                         }
459
460                         if (prefix != String.Empty) 
461                         {
462                                 formatPrefix = prefix + ":";
463                         }
464
465                         if (openStartElement || attributeWrittenForElement)
466                                 formatSpace = " ";
467
468                         w.Write ("{0}{1}{2}={3}", formatSpace, formatPrefix, localName, quoteChar);
469
470                         openAttribute = true;
471                         attributeWrittenForElement = true;
472                         ws = WriteState.Attribute;
473                 }
474
475                 public override void WriteStartDocument ()
476                 {
477                         WriteStartDocument ("");
478                 }
479
480                 public override void WriteStartDocument (bool standalone)
481                 {
482                         string standaloneFormatting;
483
484                         if (standalone == true)
485                                 standaloneFormatting = String.Format (" standalone={0}yes{0}", quoteChar);
486                         else
487                                 standaloneFormatting = String.Format (" standalone={0}no{0}", quoteChar);
488
489                         WriteStartDocument (standaloneFormatting);
490                 }
491
492                 private void WriteStartDocument (string standaloneFormatting)
493                 {
494                         if (documentStarted == true)
495                                 throw new InvalidOperationException("WriteStartDocument should be the first call.");
496
497                         CheckState ();
498
499                         string encodingFormatting = "";
500
501                         if (!nullEncoding) 
502                                 encodingFormatting = String.Format (" encoding={0}{1}{0}", quoteChar, w.Encoding.HeaderName);
503
504                         w.Write("<?xml version={0}1.0{0}{1}{2}?>", quoteChar, encodingFormatting, standaloneFormatting);
505                         ws = WriteState.Prolog;
506                 }
507
508                 public override void WriteStartElement (string prefix, string localName, string ns)
509                 {
510                         if (!Namespaces && (((prefix != null) && (prefix != String.Empty))
511                                 || ((ns != null) && (ns != String.Empty))))
512                                 throw new ArgumentException ("Cannot set the namespace if Namespaces is 'false'.");
513
514                         WriteStartElementInternal (prefix, localName, ns);
515                 }
516
517                 private void WriteStartElementInternal (string prefix, string localName, string ns)
518                 {
519                         if (prefix == null)
520                                 prefix = String.Empty;
521
522                         if ((prefix != String.Empty) && ((ns == null) || (ns == String.Empty)))
523                                 throw new ArgumentException ("Cannot use a prefix with an empty namespace.");
524
525                         CheckState ();
526                         CloseStartElement ();
527                         
528                         string formatXmlns = "";
529                         string formatPrefix = "";
530
531                         if(ns != null)
532                         {
533                                 if (ns != String.Empty) 
534                                 {
535                                         string existingPrefix = namespaceManager.LookupPrefix (ns);
536
537                                         if (prefix == String.Empty)
538                                                 prefix = existingPrefix;
539
540                                         if (prefix != existingPrefix)
541                                                 formatXmlns = String.Format (" xmlns:{0}={1}{2}{1}", prefix, quoteChar, ns);
542                                         else if (existingPrefix == String.Empty)
543                                                 formatXmlns = String.Format (" xmlns={0}{1}{0}", quoteChar, ns);
544                                 }
545                                 else if ((prefix == String.Empty) && (namespaceManager.LookupNamespace(prefix) != String.Empty)) {
546                                         formatXmlns = String.Format (" xmlns={0}{0}", quoteChar);
547                                 }
548
549                                 if (prefix != String.Empty) {
550                                         formatPrefix = prefix + ":";
551                                 }
552                         }
553
554                         w.Write ("{0}<{1}{2}{3}", indentFormatting, formatPrefix, localName, formatXmlns);
555         
556
557                         openElements.Push (new XmlTextWriterOpenElement (formatPrefix + localName));
558                         ws = WriteState.Element;
559                         openStartElement = true;
560
561                         namespaceManager.PushScope ();
562                         if(ns != null)
563                         {
564                                 namespaceManager.AddNamespace (prefix, ns);
565                         }
566                         indentLevel++;
567                 }
568
569                 public override void WriteString (string text)
570                 {
571                         if (ws == WriteState.Prolog)
572                                 throw new InvalidOperationException ("Token content in state Prolog would result in an invalid XML document.");
573
574                         WriteStringInternal (text, true);
575                 }
576
577                 private void WriteStringInternal (string text, bool entitize)
578                 {
579                         if (text == null)
580                                 text = String.Empty;
581
582                         if (text != String.Empty) 
583                         {
584                                 CheckState ();
585
586                                 if (entitize)
587                                 {
588                                         text = text.Replace ("&", "&amp;");
589                                         text = text.Replace ("<", "&lt;");
590                                         text = text.Replace (">", "&gt;");
591                                         
592                                         if (openAttribute) 
593                                         {
594                                                 if (quoteChar == '"')
595                                                         text = text.Replace ("\"", "&quot;");
596                                                 else
597                                                         text = text.Replace ("'", "&apos;");
598                                         }
599                                 }
600
601                                 if (!openAttribute)
602                                 {
603                                         IndentingOverriden = true;
604                                         CloseStartElement ();
605                                 }
606                                 if (!openXmlLang && !openXmlSpace)
607                                         w.Write (text);
608                                 else 
609                                 {
610                                         if (openXmlLang)
611                                                 xmlLang = text;
612                                         else 
613                                         {
614                                                 switch (text) 
615                                                 {
616                                                         case "default":
617                                                                 xmlSpace = XmlSpace.Default;
618                                                                 break;
619                                                         case "preserve":
620                                                                 xmlSpace = XmlSpace.Preserve;
621                                                                 break;
622                                                         default:
623                                                                 throw new ArgumentException ("'{0}' is an invalid xml:space value.");
624                                                 }
625                                         }
626                                 }
627                         }
628                 }
629
630                 [MonoTODO]
631                 public override void WriteSurrogateCharEntity (char lowChar, char highChar)
632                 {
633                         throw new NotImplementedException ();
634                 }
635
636                 public override void WriteWhitespace (string ws)
637                 {
638                         foreach (char c in ws) {
639                                 if ((c != ' ') && (c != '\t') && (c != '\r') && (c != '\n'))
640                                         throw new ArgumentException ();
641                         }
642
643                         w.Write (ws);
644                 }
645
646                 #endregion
647         }
648 }