a513b06f83f045449308246072224881ab1f2fe8
[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                 [MonoTODO]
226                 public override string LookupPrefix (string ns)
227                 {
228                         throw new NotImplementedException ();
229                 }
230
231                 private void UpdateIndentChars ()
232                 {
233                         indentChars = "";
234                         for (int i = 0; i < indentation; i++)
235                                 indentChars += indentChar;
236                 }
237
238                 [MonoTODO]
239                 public override void WriteBase64 (byte[] buffer, int index, int count)
240                 {
241                         throw new NotImplementedException ();
242                 }
243
244                 [MonoTODO]
245                 public override void WriteBinHex (byte[] buffer, int index, int count)
246                 {
247                         throw new NotImplementedException ();
248                 }
249
250                 public override void WriteCData (string text)
251                 {
252                         if (text.IndexOf("]]>") > 0) 
253                         {
254                                 throw new ArgumentException ();
255                         }
256
257                         CheckState ();
258                         CloseStartElement ();
259
260                         w.Write("<![CDATA[{0}]]>", text);
261                 }
262
263                 [MonoTODO]
264                 public override void WriteCharEntity (char ch)
265                 {
266                         throw new NotImplementedException ();
267                 }
268
269                 [MonoTODO]
270                 public override void WriteChars (char[] buffer, int index, int count)
271                 {
272                         throw new NotImplementedException ();
273                 }
274
275                 public override void WriteComment (string text)
276                 {
277                         if ((text.EndsWith("-")) || (text.IndexOf("-->") > 0)) {
278                                 throw new ArgumentException ();
279                         }
280
281                         CheckState ();
282                         CloseStartElement ();
283
284                         w.Write ("<!--{0}-->", text);
285                 }
286
287                 [MonoTODO]
288                 public override void WriteDocType (string name, string pubid, string sysid, string subset)
289                 {
290                         throw new NotImplementedException ();
291                 }
292
293                 public override void WriteEndAttribute ()
294                 {
295                         if (!openAttribute)
296                                 throw new InvalidOperationException("Token EndAttribute in state Start would result in an invalid XML document.");
297
298                         CheckState ();
299
300                         if (openXmlLang) {
301                                 w.Write (xmlLang);
302                                 openXmlLang = false;
303                                 ((XmlTextWriterOpenElement)openElements.Peek()).XmlLang = xmlLang;
304                         }
305
306                         if (openXmlSpace) 
307                         {
308                                 w.Write (xmlSpace.ToString ().ToLower ());
309                                 openXmlSpace = false;
310                                 ((XmlTextWriterOpenElement)openElements.Peek()).XmlSpace = xmlSpace;
311                         }
312
313                         w.Write ("{0}", quoteChar);
314
315                         openAttribute = false;
316                 }
317
318                 [MonoTODO]
319                 public override void WriteEndDocument ()
320                 {
321                         throw new NotImplementedException ();
322                 }
323
324                 public override void WriteEndElement ()
325                 {
326                         if (openElements.Count == 0)
327                                 throw new InvalidOperationException("There was no XML start tag open.");
328
329                         indentLevel--;
330
331                         CheckState ();
332
333                         if (openStartElement) {
334                                 w.Write (" />");
335                                 openElements.Pop ();
336                                 openStartElement = false;
337                         }
338                         else {
339                                 w.Write ("{0}</{1}>", indentFormatting, openElements.Pop ());
340                                 namespaceManager.PopScope();
341                         }
342                 }
343
344                 [MonoTODO]
345                 public override void WriteEntityRef (string name)
346                 {
347                         throw new NotImplementedException ();
348                 }
349
350                 [MonoTODO]
351                 public override void WriteFullEndElement ()
352                 {
353                         throw new NotImplementedException ();
354                 }
355
356                 [MonoTODO]
357                 public override void WriteName (string name)
358                 {
359                         throw new NotImplementedException ();
360                 }
361
362                 [MonoTODO]
363                 public override void WriteNmToken (string name)
364                 {
365                         throw new NotImplementedException ();
366                 }
367
368                 public override void WriteProcessingInstruction (string name, string text)
369                 {
370                         if ((name == null) || (name == string.Empty) || (name.IndexOf("?>") > 0) || (text.IndexOf("?>") > 0)) {
371                                 throw new ArgumentException ();
372                         }
373
374                         CheckState ();
375                         CloseStartElement ();
376
377                         w.Write ("{0}<?{1} {2}?>", indentFormatting, name, text);
378                 }
379
380                 [MonoTODO]
381                 public override void WriteQualifiedName (string localName, string ns)
382                 {
383                         throw new NotImplementedException ();
384                 }
385
386                 [MonoTODO]
387                 public override void WriteRaw (string data)
388                 {
389                         throw new NotImplementedException ();
390                 }
391
392                 [MonoTODO]
393                 public override void WriteRaw (char[] buffer, int index, int count)
394                 {
395                         throw new NotImplementedException ();
396                 }
397
398                 [MonoTODO("haven't tested namespaces on attributes code yet.")]
399                 public override void WriteStartAttribute (string prefix, string localName, string ns)
400                 {
401                         if ((prefix == "xml") && (localName == "lang"))
402                                 openXmlLang = true;
403
404                         if ((prefix == "xml") && (localName == "space"))
405                                 openXmlSpace = true;
406
407                         if ((prefix == "xmlns") && (localName == "xmlns"))
408                                 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.");
409
410                         CheckState ();
411
412                         if (ws == WriteState.Content)
413                                 throw new InvalidOperationException ("Token StartAttribute in state " + WriteState + " would result in an invalid XML document.");
414
415                         if (prefix == null)
416                                 prefix = String.Empty;
417
418                         if (ns == null)
419                                 ns = String.Empty;
420
421                         string formatPrefix = "";
422                         string formatSpace = "";
423
424                         if (ns != String.Empty) 
425                         {
426                                 string existingPrefix = namespaceManager.LookupPrefix (ns);
427
428                                 if (prefix == String.Empty)
429                                         prefix = existingPrefix;
430                         }
431
432                         if (prefix != String.Empty) 
433                         {
434                                 formatPrefix = prefix + ":";
435                         }
436
437                         if (openStartElement || attributeWrittenForElement)
438                                 formatSpace = " ";
439
440                         w.Write ("{0}{1}{2}={3}", formatSpace, formatPrefix, localName, quoteChar);
441
442                         openAttribute = true;
443                         attributeWrittenForElement = true;
444                         ws = WriteState.Attribute;
445                 }
446
447                 public override void WriteStartDocument ()
448                 {
449                         WriteStartDocument ("");
450                 }
451
452                 public override void WriteStartDocument (bool standalone)
453                 {
454                         string standaloneFormatting;
455
456                         if (standalone == true)
457                                 standaloneFormatting = String.Format (" standalone={0}yes{0}", quoteChar);
458                         else
459                                 standaloneFormatting = String.Format (" standalone={0}no{0}", quoteChar);
460
461                         WriteStartDocument (standaloneFormatting);
462                 }
463
464                 private void WriteStartDocument (string standaloneFormatting)
465                 {
466                         if (documentStarted == true)
467                                 throw new InvalidOperationException("WriteStartDocument should be the first call.");
468
469                         CheckState ();
470
471                         string encodingFormatting = "";
472
473                         if (!nullEncoding)
474                                 encodingFormatting = String.Format (" encoding={0}{1}{0}", quoteChar, w.Encoding.HeaderName);
475
476                         w.Write("<?xml version={0}1.0{0}{1}{2}?>", quoteChar, encodingFormatting, standaloneFormatting);
477                         ws = WriteState.Prolog;
478                 }
479
480                 public override void WriteStartElement (string prefix, string localName, string ns)
481                 {
482                         if (!Namespaces && (((prefix != null) && (prefix != String.Empty))
483                                 || ((ns != null) && (ns != String.Empty))))
484                                 throw new ArgumentException ("Cannot set the namespace if Namespaces is 'false'.");
485
486                         WriteStartElementInternal (prefix, localName, ns);
487                 }
488
489                 protected override void WriteStartElementInternal (string prefix, string localName, string ns)
490                 {
491                         if (prefix == null)
492                                 prefix = String.Empty;
493
494                         if (ns == null)
495                                 ns = String.Empty;
496
497                         if ((prefix != String.Empty) && ((ns == null) || (ns == String.Empty)))
498                                 throw new ArgumentException ("Cannot use a prefix with an empty namespace.");
499
500                         CheckState ();
501                         CloseStartElement ();
502
503                         string formatXmlns = "";
504                         string formatPrefix = "";
505
506                         if (ns != String.Empty) 
507                         {
508                                 string existingPrefix = namespaceManager.LookupPrefix (ns);
509
510                                 if (prefix == String.Empty)
511                                         prefix = existingPrefix;
512
513                                 if (prefix != existingPrefix)
514                                         formatXmlns = String.Format (" xmlns:{0}={1}{2}{1}", prefix, quoteChar, ns);
515                                 else if (existingPrefix == String.Empty)
516                                         formatXmlns = String.Format (" xmlns={0}{1}{0}", quoteChar, ns);
517                         }
518                         else if ((prefix == String.Empty) && (namespaceManager.LookupNamespace(prefix) != String.Empty)) {
519                                 formatXmlns = String.Format (" xmlns={0}{0}", quoteChar);
520                         }
521
522                         if (prefix != String.Empty) {
523                                 formatPrefix = prefix + ":";
524                         }
525
526                         w.Write ("{0}<{1}{2}{3}", indentFormatting, formatPrefix, localName, formatXmlns);
527
528                         openElements.Push (new XmlTextWriterOpenElement (formatPrefix + localName));
529                         ws = WriteState.Element;
530                         openStartElement = true;
531
532                         namespaceManager.PushScope ();
533                         namespaceManager.AddNamespace (prefix, ns);
534
535                         indentLevel++;
536                 }
537
538                 public override void WriteString (string text)
539                 {
540                         if (ws == WriteState.Prolog)
541                                 throw new InvalidOperationException ("Token content in state Prolog would result in an invalid XML document.");
542
543                         if (text == null)
544                                 text = String.Empty;
545
546                         if (text != String.Empty) {
547                                 CheckState ();
548
549                                 text = text.Replace ("&", "&amp;");
550                                 text = text.Replace ("<", "&lt;");
551                                 text = text.Replace (">", "&gt;");
552                                 
553                                 if (openAttribute) {
554                                         if (quoteChar == '"')
555                                                 text = text.Replace ("\"", "&quot;");
556                                         else
557                                                 text = text.Replace ("'", "&apos;");
558                                 }
559
560                                 if (!openAttribute)
561                                         CloseStartElement ();
562
563                                 if (!openXmlLang && !openXmlSpace)
564                                         w.Write (text);
565                                 else {
566                                         if (openXmlLang)
567                                                 xmlLang = text;
568                                         else {
569                                                 switch (text) {
570                                                         case "default":
571                                                                 xmlSpace = XmlSpace.Default;
572                                                                 break;
573                                                         case "preserve":
574                                                                 xmlSpace = XmlSpace.Preserve;
575                                                                 break;
576                                                         default:
577                                                                 throw new ArgumentException ("'{0}' is an invalid xml:space value.");
578                                                 }
579                                         }
580                                 }
581                         }
582
583                         IndentingOverriden = true;
584                 }
585
586                 [MonoTODO]
587                 public override void WriteSurrogateCharEntity (char lowChar, char highChar)
588                 {
589                         throw new NotImplementedException ();
590                 }
591
592                 public override void WriteWhitespace (string ws)
593                 {
594                         foreach (char c in ws) {
595                                 if ((c != ' ') && (c != '\t') && (c != '\r') && (c != '\n'))
596                                         throw new ArgumentException ();
597                         }
598
599                         w.Write (ws);
600                 }
601
602                 #endregion
603         }
604 }