4df230e6b4d4bdd2b23c7033d8663a019ddcbfe6
[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                 protected TextWriter w;
22                 protected bool nullEncoding = false;
23                 protected bool openWriter = true;
24                 protected bool openStartElement = false;
25                 protected bool openStartAttribute = false;
26                 protected bool documentStarted = false;
27                 protected bool namespaces = true;
28                 protected bool openAttribute = false;
29                 protected bool attributeWrittenForElement = false;
30                 protected Stack openElements = new Stack ();
31                 protected Formatting formatting = Formatting.None;
32                 protected int indentation = 2;
33                 protected char indentChar = ' ';
34                 protected string indentChars = "  ";
35                 protected char quoteChar = '\"';
36                 protected int indentLevel = 0;
37                 protected string indentFormatting;
38                 protected Stream baseStream = null;
39                 protected string xmlLang = null;
40                 protected XmlSpace xmlSpace = XmlSpace.None;
41                 protected bool openXmlLang = false;
42                 protected bool openXmlSpace = false;
43
44                 #endregion
45
46                 #region Constructors
47
48                 public XmlTextWriter (TextWriter w) : base ()
49                 {
50                         this.w = w;
51                         
52                         try {
53                                 baseStream = ((StreamWriter)w).BaseStream;
54                         }
55                         catch (Exception) { }
56                 }
57
58                 public XmlTextWriter (Stream w, Encoding encoding) : base ()
59                 {
60                         if (encoding == null) {
61                                 nullEncoding = true;
62                                 encoding = new UTF8Encoding ();
63                         }
64
65                         this.w = new StreamWriter(w, encoding);
66                         baseStream = w;
67                 }
68
69                 public XmlTextWriter (string filename, Encoding encoding) : base ()
70                 {
71                         this.w = new StreamWriter(filename, false, encoding);
72                         baseStream = ((StreamWriter)w).BaseStream;
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                 public 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
185                         if ((documentStarted == true) && (formatting == Formatting.Indented) && (!IndentingOverriden)) {
186                                 indentFormatting = "\r\n";
187                                 if (indentLevel > 0) {
188                                         for (int i = 0; i < indentLevel; i++)
189                                                 indentFormatting += indentChars;
190                                 }
191                         }
192                         else
193                                 indentFormatting = "";
194
195                         documentStarted = true;
196                 }
197
198                 public override void Close ()
199                 {
200                         while (openElements.Count > 0) {
201                                 WriteEndElement();
202                         }
203
204                         w.Close();
205                         ws = WriteState.Closed;
206                         openWriter = false;
207                 }
208
209                 private void CloseStartElement ()
210                 {
211                         if (openStartElement) 
212                         {
213                                 w.Write(">");
214                                 ws = WriteState.Content;
215                                 openStartElement = false;
216                                 attributeWrittenForElement = false;
217                         }
218                 }
219
220                 public override void Flush ()
221                 {
222                         w.Flush ();
223                 }
224
225                 public override string LookupPrefix (string ns)
226                 {
227                         string prefix = namespaceManager.LookupPrefix (ns);
228
229                         if (prefix == String.Empty)
230                                 prefix = null;
231
232                         return prefix;
233                 }
234
235                 private void UpdateIndentChars ()
236                 {
237                         indentChars = "";
238                         for (int i = 0; i < indentation; i++)
239                                 indentChars += indentChar;
240                 }
241
242                 public override void WriteBase64 (byte[] buffer, int index, int count)
243                 {
244                         w.Write (Convert.ToBase64String (buffer, index, count));
245                 }
246
247                 [MonoTODO]
248                 public override void WriteBinHex (byte[] buffer, int index, int count)
249                 {
250                         throw new NotImplementedException ();
251                 }
252
253                 public override void WriteCData (string text)
254                 {
255                         if (text.IndexOf("]]>") > 0)
256                                 throw new ArgumentException ();
257
258                         CheckState ();
259                         CloseStartElement ();
260
261                         w.Write("<![CDATA[{0}]]>", text);
262                 }
263
264                 public override void WriteCharEntity (char ch)
265                 {
266                         Int16   intCh = (Int16)ch;
267
268                         // Make sure the character is not in the surrogate pair
269                         // character range, 0xd800- 0xdfff
270                         if ((intCh >= -10240) && (intCh <= -8193))
271                                 throw new ArgumentException ("Surrogate Pair is invalid.");
272
273                         w.Write("&#x{0:X};", intCh);
274                 }
275
276                 [MonoTODO]
277                 public override void WriteChars (char[] buffer, int index, int count)
278                 {
279                         throw new NotImplementedException ();
280                 }
281
282                 public override void WriteComment (string text)
283                 {
284                         if ((text.EndsWith("-")) || (text.IndexOf("-->") > 0)) {
285                                 throw new ArgumentException ();
286                         }
287
288                         CheckState ();
289                         CloseStartElement ();
290
291                         w.Write ("<!--{0}-->", text);
292                 }
293
294                 [MonoTODO]
295                 public override void WriteDocType (string name, string pubid, string sysid, string subset)
296                 {
297                         throw new NotImplementedException ();
298                 }
299
300                 public override void WriteEndAttribute ()
301                 {
302                         if (!openAttribute)
303                                 throw new InvalidOperationException("Token EndAttribute in state Start would result in an invalid XML document.");
304
305                         CheckState ();
306
307                         if (openXmlLang) {
308                                 w.Write (xmlLang);
309                                 openXmlLang = false;
310                                 ((XmlTextWriterOpenElement)openElements.Peek()).XmlLang = xmlLang;
311                         }
312
313                         if (openXmlSpace) 
314                         {
315                                 w.Write (xmlSpace.ToString ().ToLower ());
316                                 openXmlSpace = false;
317                                 ((XmlTextWriterOpenElement)openElements.Peek()).XmlSpace = xmlSpace;
318                         }
319
320                         w.Write ("{0}", quoteChar);
321
322                         openAttribute = false;
323                 }
324
325                 [MonoTODO]
326                 public override void WriteEndDocument ()
327                 {
328                         throw new NotImplementedException ();
329                 }
330
331                 public override void WriteEndElement ()
332                 {
333                         if (openElements.Count == 0)
334                                 throw new InvalidOperationException("There was no XML start tag open.");
335
336                         indentLevel--;
337
338                         CheckState ();
339
340                         if (openStartElement) {
341                                 w.Write (" />");
342                                 openElements.Pop ();
343                                 openStartElement = false;
344                         }
345                         else {
346                                 w.Write ("{0}</{1}>", indentFormatting, openElements.Pop ());
347                         }
348
349                         namespaceManager.PopScope();
350                 }
351
352                 [MonoTODO]
353                 public override void WriteEntityRef (string name)
354                 {
355                         throw new NotImplementedException ();
356                 }
357
358                 [MonoTODO]
359                 public override void WriteFullEndElement ()
360                 {
361                         throw new NotImplementedException ();
362                 }
363
364                 [MonoTODO]
365                 public override void WriteName (string name)
366                 {
367                         throw new NotImplementedException ();
368                 }
369
370                 [MonoTODO]
371                 public override void WriteNmToken (string name)
372                 {
373                         throw new NotImplementedException ();
374                 }
375
376                 public override void WriteProcessingInstruction (string name, string text)
377                 {
378                         if ((name == null) || (name == string.Empty) || (name.IndexOf("?>") > 0) || (text.IndexOf("?>") > 0)) {
379                                 throw new ArgumentException ();
380                         }
381
382                         CheckState ();
383                         CloseStartElement ();
384
385                         w.Write ("{0}<?{1} {2}?>", indentFormatting, name, text);
386                 }
387
388                 [MonoTODO]
389                 public override void WriteQualifiedName (string localName, string ns)
390                 {
391                         throw new NotImplementedException ();
392                 }
393
394                 [MonoTODO]
395                 public override void WriteRaw (string data)
396                 {
397                         throw new NotImplementedException ();
398                 }
399
400                 [MonoTODO]
401                 public override void WriteRaw (char[] buffer, int index, int count)
402                 {
403                         throw new NotImplementedException ();
404                 }
405
406                 public override void WriteStartAttribute (string prefix, string localName, string ns)
407                 {
408                         if ((prefix == "xml") && (localName == "lang"))
409                                 openXmlLang = true;
410
411                         if ((prefix == "xml") && (localName == "space"))
412                                 openXmlSpace = true;
413
414                         if ((prefix == "xmlns") && (localName == "xmlns"))
415                                 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.");
416
417                         CheckState ();
418
419                         if (ws == WriteState.Content)
420                                 throw new InvalidOperationException ("Token StartAttribute in state " + WriteState + " would result in an invalid XML document.");
421
422                         if (prefix == null)
423                                 prefix = String.Empty;
424
425                         if (ns == null)
426                                 ns = String.Empty;
427
428                         string formatPrefix = "";
429                         string formatSpace = "";
430
431                         if (ns != String.Empty) 
432                         {
433                                 string existingPrefix = namespaceManager.LookupPrefix (ns);
434
435                                 if (prefix == String.Empty)
436                                         prefix = existingPrefix;
437                         }
438
439                         if (prefix != String.Empty) 
440                         {
441                                 formatPrefix = prefix + ":";
442                         }
443
444                         if (openStartElement || attributeWrittenForElement)
445                                 formatSpace = " ";
446
447                         w.Write ("{0}{1}{2}={3}", formatSpace, formatPrefix, localName, quoteChar);
448
449                         openAttribute = true;
450                         attributeWrittenForElement = true;
451                         ws = WriteState.Attribute;
452                 }
453
454                 public override void WriteStartDocument ()
455                 {
456                         WriteStartDocument ("");
457                 }
458
459                 public override void WriteStartDocument (bool standalone)
460                 {
461                         string standaloneFormatting;
462
463                         if (standalone == true)
464                                 standaloneFormatting = String.Format (" standalone={0}yes{0}", quoteChar);
465                         else
466                                 standaloneFormatting = String.Format (" standalone={0}no{0}", quoteChar);
467
468                         WriteStartDocument (standaloneFormatting);
469                 }
470
471                 private void WriteStartDocument (string standaloneFormatting)
472                 {
473                         if (documentStarted == true)
474                                 throw new InvalidOperationException("WriteStartDocument should be the first call.");
475
476                         CheckState ();
477
478                         string encodingFormatting = "";
479
480                         if (!nullEncoding)
481                                 encodingFormatting = String.Format (" encoding={0}{1}{0}", quoteChar, w.Encoding.HeaderName);
482
483                         w.Write("<?xml version={0}1.0{0}{1}{2}?>", quoteChar, encodingFormatting, standaloneFormatting);
484                         ws = WriteState.Prolog;
485                 }
486
487                 public override void WriteStartElement (string prefix, string localName, string ns)
488                 {
489                         if (!Namespaces && (((prefix != null) && (prefix != String.Empty))
490                                 || ((ns != null) && (ns != String.Empty))))
491                                 throw new ArgumentException ("Cannot set the namespace if Namespaces is 'false'.");
492
493                         WriteStartElementInternal (prefix, localName, ns);
494                 }
495
496                 protected override void WriteStartElementInternal (string prefix, string localName, string ns)
497                 {
498                         if (prefix == null)
499                                 prefix = String.Empty;
500
501                         if (ns == null)
502                                 ns = String.Empty;
503
504                         if ((prefix != String.Empty) && ((ns == null) || (ns == String.Empty)))
505                                 throw new ArgumentException ("Cannot use a prefix with an empty namespace.");
506
507                         CheckState ();
508                         CloseStartElement ();
509
510                         string formatXmlns = "";
511                         string formatPrefix = "";
512
513                         if (ns != String.Empty) 
514                         {
515                                 string existingPrefix = namespaceManager.LookupPrefix (ns);
516
517                                 if (prefix == String.Empty)
518                                         prefix = existingPrefix;
519
520                                 if (prefix != existingPrefix)
521                                         formatXmlns = String.Format (" xmlns:{0}={1}{2}{1}", prefix, quoteChar, ns);
522                                 else if (existingPrefix == String.Empty)
523                                         formatXmlns = String.Format (" xmlns={0}{1}{0}", quoteChar, ns);
524                         }
525                         else if ((prefix == String.Empty) && (namespaceManager.LookupNamespace(prefix) != String.Empty)) {
526                                 formatXmlns = String.Format (" xmlns={0}{0}", quoteChar);
527                         }
528
529                         if (prefix != String.Empty) {
530                                 formatPrefix = prefix + ":";
531                         }
532
533                         w.Write ("{0}<{1}{2}{3}", indentFormatting, formatPrefix, localName, formatXmlns);
534
535                         openElements.Push (new XmlTextWriterOpenElement (formatPrefix + localName));
536                         ws = WriteState.Element;
537                         openStartElement = true;
538
539                         namespaceManager.PushScope ();
540                         namespaceManager.AddNamespace (prefix, ns);
541
542                         indentLevel++;
543                 }
544
545                 public override void WriteString (string text)
546                 {
547                         if (ws == WriteState.Prolog)
548                                 throw new InvalidOperationException ("Token content in state Prolog would result in an invalid XML document.");
549
550                         if (text == null)
551                                 text = String.Empty;
552
553                         if (text != String.Empty) {
554                                 CheckState ();
555
556                                 text = text.Replace ("&", "&amp;");
557                                 text = text.Replace ("<", "&lt;");
558                                 text = text.Replace (">", "&gt;");
559                                 
560                                 if (openAttribute) {
561                                         if (quoteChar == '"')
562                                                 text = text.Replace ("\"", "&quot;");
563                                         else
564                                                 text = text.Replace ("'", "&apos;");
565                                 }
566
567                                 if (!openAttribute)
568                                         CloseStartElement ();
569
570                                 if (!openXmlLang && !openXmlSpace)
571                                         w.Write (text);
572                                 else {
573                                         if (openXmlLang)
574                                                 xmlLang = text;
575                                         else {
576                                                 switch (text) {
577                                                         case "default":
578                                                                 xmlSpace = XmlSpace.Default;
579                                                                 break;
580                                                         case "preserve":
581                                                                 xmlSpace = XmlSpace.Preserve;
582                                                                 break;
583                                                         default:
584                                                                 throw new ArgumentException ("'{0}' is an invalid xml:space value.");
585                                                 }
586                                         }
587                                 }
588                         }
589
590                         IndentingOverriden = true;
591                 }
592
593                 [MonoTODO]
594                 public override void WriteSurrogateCharEntity (char lowChar, char highChar)
595                 {
596                         throw new NotImplementedException ();
597                 }
598
599                 public override void WriteWhitespace (string ws)
600                 {
601                         foreach (char c in ws) {
602                                 if ((c != ' ') && (c != '\t') && (c != '\r') && (c != '\n'))
603                                         throw new ArgumentException ();
604                         }
605
606                         w.Write (ws);
607                 }
608
609                 #endregion
610         }
611 }