2007-09-27 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / System.XML / System.Xml / XmlWriter.cs
1 //
2 // System.Xml.XmlWriter
3 //
4 // Authors:
5 //   Kral Ferch <kral_ferch@hotmail.com>
6 //   Atsushi Enomoto <ginga@kit.hi-ho.ne.jp>
7 //
8 // (C) 2002 Kral Ferch
9 // (C) 2002-2003 Atsushi Enomoto
10 // (C) 2004-2007 Novell, Inc.
11 //
12
13 //
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
21 // 
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
24 // 
25 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 //
33
34 using System;
35 using System.Collections;
36 using System.IO;
37 using System.Text;
38 #if NET_2_0 && !NET_2_1
39 using System.Xml.XPath;
40 #endif
41
42 namespace System.Xml
43 {
44 #if NET_2_0
45         public abstract class XmlWriter : IDisposable
46 #else
47         public abstract class XmlWriter
48 #endif
49         {
50 #if NET_2_0
51                 XmlWriterSettings settings;
52 #endif
53
54                 #region Constructors
55
56                 protected XmlWriter () { }
57
58                 #endregion
59
60                 #region Properties
61
62 #if NET_2_0
63                 public virtual XmlWriterSettings Settings {
64                         get {
65                                 if (settings == null)
66                                         settings = new XmlWriterSettings ();
67                                 return settings;
68                         }
69                 }
70 #endif
71
72                 public abstract WriteState WriteState { get; }
73                 
74
75 #if NET_2_0
76                 public virtual string XmlLang {
77                         get { return null; }
78                 }
79
80                 public virtual XmlSpace XmlSpace {
81                         get { return XmlSpace.None; }
82                 }
83 #else
84                 public abstract string XmlLang { get; }
85
86                 public abstract XmlSpace XmlSpace { get; }
87 #endif
88
89                 #endregion
90
91                 #region Methods
92
93                 public abstract void Close ();
94
95 #if NET_2_0
96                 public static XmlWriter Create (Stream stream)
97                 {
98                         return Create (stream, null);
99                 }
100
101                 public static XmlWriter Create (string file)
102                 {
103                         return Create (file, null);
104                 }
105
106                 public static XmlWriter Create (TextWriter writer)
107                 {
108                         return Create (writer, null);
109                 }
110
111                 public static XmlWriter Create (XmlWriter writer)
112                 {
113                         return Create (writer, null);
114                 }
115
116                 public static XmlWriter Create (StringBuilder builder)
117                 {
118                         return Create (builder, null);
119                 }
120
121                 public static XmlWriter Create (Stream stream, XmlWriterSettings settings)
122                 {
123                         Encoding enc = settings != null ? settings.Encoding : Encoding.UTF8;
124                         return Create (new StreamWriter (stream, enc), settings);
125                 }
126
127                 public static XmlWriter Create (string file, XmlWriterSettings settings)
128                 {
129                         Encoding enc = settings != null ? settings.Encoding : Encoding.UTF8;
130                         return CreateTextWriter (new StreamWriter (file, false, enc), settings, true);
131                 }
132
133                 public static XmlWriter Create (StringBuilder builder, XmlWriterSettings settings)
134                 {
135                         return Create (new StringWriter (builder), settings);
136                 }
137
138                 public static XmlWriter Create (TextWriter writer, XmlWriterSettings settings)
139                 {
140                         if (settings == null)
141                                 settings = new XmlWriterSettings ();
142                         return CreateTextWriter (writer, settings, settings.CloseOutput);
143                 }
144
145                 public static XmlWriter Create (XmlWriter writer, XmlWriterSettings settings)
146                 {
147                         if (settings == null)
148                                 settings = new XmlWriterSettings ();
149                         writer.settings = settings;
150                         return writer;
151                 }
152
153                 private static XmlWriter CreateTextWriter (TextWriter writer, XmlWriterSettings settings, bool closeOutput)
154                 {
155                         if (settings == null)
156                                 settings = new XmlWriterSettings ();
157                         XmlTextWriter xtw = new XmlTextWriter (writer, settings, closeOutput);
158                         return Create (xtw, settings);
159                 }
160
161                 protected virtual void Dispose (bool disposing)
162                 {
163                         Close ();
164                 }
165
166                 void IDisposable.Dispose ()
167                 {
168                         Dispose (false);
169                 }
170 #endif
171
172                 public abstract void Flush ();
173
174                 public abstract string LookupPrefix (string ns);
175
176                 private void WriteAttribute (XmlReader reader, bool defattr)
177                 {
178                         if (!defattr && reader.IsDefault)
179                                 return;
180
181                         WriteStartAttribute (reader.Prefix, reader.LocalName, reader.NamespaceURI);
182 #if NET_2_1
183                         // no ReadAttributeValue() in 2.1 profile.
184                         WriteString (reader.Value);
185 #else
186                         while (reader.ReadAttributeValue ()) {
187                                 switch (reader.NodeType) {
188                                 case XmlNodeType.Text:
189                                         WriteString (reader.Value);
190                                         break;
191                                 case XmlNodeType.EntityReference:
192                                         WriteEntityRef (reader.Name);
193                                         break;
194                                 }
195                         }
196 #endif
197                         WriteEndAttribute ();
198                 }
199
200                 public virtual void WriteAttributes (XmlReader reader, bool defattr)
201                 {
202                         if(reader == null)
203                                 throw new ArgumentException("null XmlReader specified.", "reader");
204
205                         switch (reader.NodeType) {
206                         case XmlNodeType.XmlDeclaration:
207                                 WriteAttributeString ("version", reader ["version"]);
208                                 if (reader ["encoding"] != null)
209                                         WriteAttributeString ("encoding", reader ["encoding"]);
210                                 if (reader ["standalone"] != null)
211                                         WriteAttributeString ("standalone", reader ["standalone"]);
212                                 break;
213                         case XmlNodeType.Element:
214                                 if (reader.MoveToFirstAttribute ())
215                                         goto case XmlNodeType.Attribute;
216                                 break;
217                         case XmlNodeType.Attribute:
218                                 do {
219                                         WriteAttribute (reader, defattr);
220                                 } while (reader.MoveToNextAttribute ());
221                                 reader.MoveToElement ();
222                                 break;
223                         default:
224                                 throw new XmlException("NodeType is not one of Element, Attribute, nor XmlDeclaration.");
225                         }
226                 }
227
228                 public void WriteAttributeString (string localName, string value)
229                 {
230                         WriteAttributeString ("", localName, null, value);
231                 }
232
233                 public void WriteAttributeString (string localName, string ns, string value)
234                 {
235                         WriteAttributeString ("", localName, ns, value);
236                 }
237
238                 public void WriteAttributeString (string prefix, string localName, string ns, string value)
239                 {
240                         // In MS.NET (1.0), this check is done *here*, not at WriteStartAttribute.
241                         // (XmlTextWriter.WriteStartAttribute("xmlns", "anyname", null) throws an exception.
242
243                         WriteStartAttribute (prefix, localName, ns);
244                         if (value != null && value.Length > 0)
245                                 WriteString (value);
246                         WriteEndAttribute ();
247                 }
248
249                 public abstract void WriteBase64 (byte[] buffer, int index, int count);
250
251 #if NET_2_0
252                 public virtual void WriteBinHex (byte [] buffer, int index, int count)
253                 {
254                         StringWriter sw = new StringWriter ();
255                         XmlConvert.WriteBinHex (buffer, index, count, sw);
256                         WriteString (sw.ToString ());
257                 }
258 #else
259                 public abstract void WriteBinHex (byte[] buffer, int index, int count);
260 #endif
261
262                 public abstract void WriteCData (string text);
263
264                 public abstract void WriteCharEntity (char ch);
265
266                 public abstract void WriteChars (char[] buffer, int index, int count);
267
268                 public abstract void WriteComment (string text);
269
270                 public abstract void WriteDocType (string name, string pubid, string sysid, string subset);
271
272                 public void WriteElementString (string localName, string value)
273                 {
274                         WriteStartElement(localName);
275                         if (value != null && value.Length > 0)
276                                 WriteString(value);
277                         WriteEndElement();
278                 }
279
280                 public void WriteElementString (string localName, string ns, string value)
281                 {
282                         WriteStartElement(localName, ns);
283                         if (value != null && value.Length > 0)
284                                 WriteString(value);
285                         WriteEndElement();
286                 }
287
288 #if NET_2_0
289                 public void WriteElementString (string prefix, string localName, string ns, string value)
290                 {
291                         WriteStartElement(prefix, localName, ns);
292                         if (value != null && value.Length > 0)
293                                 WriteString(value);
294                         WriteEndElement();
295                 }
296 #endif
297
298                 public abstract void WriteEndAttribute ();
299
300                 public abstract void WriteEndDocument ();
301
302                 public abstract void WriteEndElement ();
303
304                 public abstract void WriteEntityRef (string name);
305
306                 public abstract void WriteFullEndElement ();
307
308 #if NET_2_0
309                 public virtual void WriteName (string name)
310                 {
311                         WriteNameInternal (name);
312                 }
313
314                 public virtual void WriteNmToken (string name)
315                 {
316                         WriteNmTokenInternal (name);
317                 }
318
319                 public virtual void WriteQualifiedName (string localName, string ns)
320                 {
321                         WriteQualifiedNameInternal (localName, ns);
322                 }
323 #else
324                 public abstract void WriteName (string name);
325
326                 public abstract void WriteNmToken (string name);
327
328                 public abstract void WriteQualifiedName (string localName, string ns);
329 #endif
330
331                 internal void WriteNameInternal (string name)
332                 {
333 #if NET_2_0
334                         switch (Settings.ConformanceLevel) {
335                         case ConformanceLevel.Document:
336                         case ConformanceLevel.Fragment:
337                                 XmlConvert.VerifyName (name);
338                                 break;
339                         }
340 #else
341                         XmlConvert.VerifyName (name);
342 #endif
343                         WriteString (name);
344                 }
345
346                 internal virtual void WriteNmTokenInternal (string name)
347                 {
348                         bool valid = true;
349 #if NET_2_0
350                         switch (Settings.ConformanceLevel) {
351                         case ConformanceLevel.Document:
352                         case ConformanceLevel.Fragment:
353                                 valid = XmlChar.IsNmToken (name);
354                                         break;
355                         }
356 #else
357                         valid = XmlChar.IsNmToken (name);
358 #endif
359                         if (!valid)
360                                 throw new ArgumentException ("Argument name is not a valid NMTOKEN.");
361                         WriteString (name);
362                 }
363
364                 internal void WriteQualifiedNameInternal (string localName, string ns)
365                 {
366                         if (localName == null || localName == String.Empty)
367                                 throw new ArgumentException ();
368                         if (ns == null)
369                                 ns = String.Empty;
370
371 #if NET_2_0
372                         switch (Settings.ConformanceLevel) {
373                         case ConformanceLevel.Document:
374                         case ConformanceLevel.Fragment:
375                                 XmlConvert.VerifyNCName (localName);
376                                 break;
377                         }
378 #else
379                         XmlConvert.VerifyNCName (localName);
380 #endif
381
382                         string prefix = ns.Length > 0 ? LookupPrefix (ns) : String.Empty;
383                         if (prefix == null)
384                                 throw new ArgumentException (String.Format ("Namespace '{0}' is not declared.", ns));
385
386                         if (prefix != String.Empty) {
387                                 WriteString (prefix);
388                                 WriteString (":");
389                                 WriteString (localName);
390                         }
391                         else
392                                 WriteString (localName);
393                 }
394
395 #if NET_2_0 && !NET_2_1
396                 [MonoTODO] // FIXME: test defattr handling
397                 public virtual void WriteNode (XPathNavigator navigator, bool defattr)
398                 {
399                         if (navigator == null)
400                                 throw new ArgumentNullException ("navigator");
401                         switch (navigator.NodeType) {
402                         case XPathNodeType.Attribute:
403                                 if (defattr || navigator.SchemaInfo == null ||
404                                     !navigator.SchemaInfo.IsDefault)
405                                         WriteAttributeString (navigator.Prefix, 
406                                                 navigator.LocalName,
407                                                 navigator.NamespaceURI,
408                                                 navigator.Value);
409                                 break;
410                         case XPathNodeType.Namespace:
411                                 if (defattr || navigator.SchemaInfo == null ||
412                                     !navigator.SchemaInfo.IsDefault)
413                                         WriteAttributeString (navigator.Prefix, 
414                                                 navigator.LocalName == String.Empty ? "xmlns" : navigator.LocalName,
415                                                 "http://www.w3.org/2000/xmlns/",
416                                                 navigator.Value);
417                                 break;
418                         case XPathNodeType.Text:
419                                 WriteString (navigator.Value);
420                                 break;
421                         case XPathNodeType.SignificantWhitespace:
422                                 WriteWhitespace (navigator.Value);
423                                 break;
424                         case XPathNodeType.Whitespace:
425                                 WriteWhitespace (navigator.Value);
426                                 break;
427                         case XPathNodeType.Comment:
428                                 WriteComment (navigator.Value);
429                                 break;
430                         case XPathNodeType.ProcessingInstruction:
431                                 WriteProcessingInstruction (navigator.Name, navigator.Value);
432                                 break;
433                         case XPathNodeType.Root:
434                                 if (navigator.MoveToFirstChild ()) {
435                                         do {
436                                                 WriteNode (navigator, defattr);
437                                         } while (navigator.MoveToNext ());
438                                         navigator.MoveToParent ();
439                                 }
440                                 break;
441                         case XPathNodeType.Element:
442                                 WriteStartElement (navigator.Prefix, navigator.LocalName, navigator.NamespaceURI);
443                                 if (navigator.MoveToFirstNamespace (XPathNamespaceScope.Local)) {
444                                         do {
445                                                 WriteNode (navigator, defattr);
446                                         } while (navigator.MoveToNextNamespace (XPathNamespaceScope.Local));
447                                         navigator.MoveToParent ();
448                                 }
449                                 if (navigator.MoveToFirstAttribute ()) {
450                                         do {
451                                                 WriteNode (navigator, defattr);
452                                         } while (navigator.MoveToNextAttribute ());
453                                         navigator.MoveToParent ();
454                                 }
455                                 if (navigator.MoveToFirstChild ()) {
456                                         do {
457                                                 WriteNode (navigator, defattr);
458                                         } while (navigator.MoveToNext ());
459                                         navigator.MoveToParent ();
460                                 }
461                                 if (navigator.IsEmptyElement)
462                                         WriteEndElement ();
463                                 else
464                                         WriteFullEndElement ();
465                                 break;
466                         default:
467                                 throw new NotSupportedException ();
468                         }
469                 }
470 #endif
471
472                 public virtual void WriteNode (XmlReader reader, bool defattr)
473                 {
474                         if (reader == null)
475                                 throw new ArgumentException ();
476
477                         if (reader.ReadState == ReadState.Initial) {
478                                 reader.Read ();
479                                 do {
480                                         WriteNode (reader, defattr);
481                                 } while (!reader.EOF);
482                                 return;
483                         }
484
485                         switch (reader.NodeType) {
486                         case XmlNodeType.Element:
487                                 WriteStartElement (reader.Prefix, reader.LocalName, reader.NamespaceURI);
488 #if false
489                                 WriteAttributes (reader, defattr);
490                                 reader.MoveToElement ();
491 #else
492                                 // Well, I found that MS.NET took this way, since
493                                 // there was a error-prone SgmlReader that fails
494                                 // MoveToNextAttribute().
495                                 if (reader.HasAttributes) {
496                                         for (int i = 0; i < reader.AttributeCount; i++) {
497                                                 reader.MoveToAttribute (i);
498                                                 WriteAttribute (reader, defattr);
499                                         }
500                                         reader.MoveToElement ();
501                                 }
502 #endif
503                                 if (reader.IsEmptyElement)
504                                         WriteEndElement ();
505                                 else {
506                                         int depth = reader.Depth;
507                                         reader.Read ();
508                                         if (reader.NodeType != XmlNodeType.EndElement) {
509                                                 do {
510                                                         WriteNode (reader, defattr);
511                                                 } while (depth < reader.Depth);
512                                         }
513                                         WriteFullEndElement ();
514                                 }
515                                 break;
516                         // In case of XmlAttribute, don't proceed reader, and it will never be written.
517                         case XmlNodeType.Attribute:
518                                 return;
519                         case XmlNodeType.Text:
520                                 WriteString (reader.Value);
521                                 break;
522                         case XmlNodeType.CDATA:
523                                 WriteCData (reader.Value);
524                                 break;
525                         case XmlNodeType.EntityReference:
526                                 WriteEntityRef (reader.Name);
527                                 break;
528                         case XmlNodeType.XmlDeclaration:
529                                 // LAMESPEC: It means that XmlWriter implementation _must not_ check
530                                 // whether PI name is "xml" (it is XML error) or not.
531                         case XmlNodeType.ProcessingInstruction:
532                                 WriteProcessingInstruction (reader.Name, reader.Value);
533                                 break;
534                         case XmlNodeType.Comment:
535                                 WriteComment (reader.Value);
536                                 break;
537                         case XmlNodeType.DocumentType:
538                                 WriteDocType (reader.Name,
539                                         reader ["PUBLIC"], reader ["SYSTEM"], reader.Value);
540                                 break;
541                         case XmlNodeType.SignificantWhitespace:
542                                 goto case XmlNodeType.Whitespace;
543                         case XmlNodeType.Whitespace:
544                                 WriteWhitespace (reader.Value);
545                                 break;
546                         case XmlNodeType.EndElement:
547                                 WriteFullEndElement ();
548                                 break;
549                         case XmlNodeType.EndEntity:
550                                 break;
551                         case XmlNodeType.None:
552                                 break;  // Do nothing, nor reporting errors.
553                         default:
554                                 throw new XmlException ("Unexpected node " + reader.Name + " of type " + reader.NodeType);
555                         }
556                         reader.Read ();
557                 }
558
559                 public abstract void WriteProcessingInstruction (string name, string text);
560
561                 public abstract void WriteRaw (string data);
562
563                 public abstract void WriteRaw (char[] buffer, int index, int count);
564
565 #if NET_2_0
566                 public void WriteStartAttribute (string localName)
567                 {
568                         WriteStartAttribute (null, localName, null);
569                 }
570 #endif
571
572                 public void WriteStartAttribute (string localName, string ns)
573                 {
574                         WriteStartAttribute (null, localName, ns);
575                 }
576
577                 public abstract void WriteStartAttribute (string prefix, string localName, string ns);
578
579                 public abstract void WriteStartDocument ();
580
581                 public abstract void WriteStartDocument (bool standalone);
582
583                 public void WriteStartElement (string localName)
584                 {
585                         WriteStartElement (null, localName, null);
586                 }
587
588                 public void WriteStartElement (string localName, string ns)
589                 {
590                         WriteStartElement (null, localName, ns);
591                 }
592
593                 public abstract void WriteStartElement (string prefix, string localName, string ns);
594
595                 public abstract void WriteString (string text);
596
597                 public abstract void WriteSurrogateCharEntity (char lowChar, char highChar);
598
599                 public abstract void WriteWhitespace (string ws);
600
601 #if NET_2_0
602                 public virtual void WriteValue (bool value)
603                 {
604                         WriteString (XQueryConvert.BooleanToString (value));
605                 }
606
607                 public virtual void WriteValue (DateTime value)
608                 {
609                         WriteString (XmlConvert.ToString (value));
610                 }
611
612                 public virtual void WriteValue (decimal value)
613                 {
614                         WriteString (XQueryConvert.DecimalToString (value));
615                 }
616
617                 public virtual void WriteValue (double value)
618                 {
619                         WriteString (XQueryConvert.DoubleToString (value));
620                 }
621
622                 public virtual void WriteValue (int value)
623                 {
624                         WriteString (XQueryConvert.IntToString (value));
625                 }
626
627                 public virtual void WriteValue (long value)
628                 {
629                         WriteString (XQueryConvert.IntegerToString (value));
630                 }
631
632                 public virtual void WriteValue (object value)
633                 {
634                         if (value == null)
635                                 throw new ArgumentNullException ("value");
636
637                         if (value is string)
638                                 WriteString ((string) value);
639                         else if (value is bool)
640                                 WriteValue ((bool) value);
641                         else if (value is byte)
642                                 WriteValue ((int) value);
643                         else if (value is byte [])
644                                 WriteBase64 ((byte []) value, 0, ((byte []) value).Length);
645                         else if (value is char [])
646                                 WriteChars ((char []) value, 0, ((char []) value).Length);
647                         else if (value is DateTime)
648                                 WriteValue ((DateTime) value);
649                         else if (value is decimal)
650                                 WriteValue ((decimal) value);
651                         else if (value is double)
652                                 WriteValue ((double) value);
653                         else if (value is short)
654                                 WriteValue ((int) value);
655                         else if (value is int)
656                                 WriteValue ((int) value);
657                         else if (value is long)
658                                 WriteValue ((long) value);
659                         else if (value is float)
660                                 WriteValue ((float) value);
661                         else if (value is TimeSpan) // undocumented
662                                 WriteString (XmlConvert.ToString ((TimeSpan) value));
663                         else if (value is XmlQualifiedName) {
664                                 XmlQualifiedName qname = (XmlQualifiedName) value;
665                                 if (!qname.Equals (XmlQualifiedName.Empty)) {
666                                         if (qname.Namespace.Length > 0 && LookupPrefix (qname.Namespace) == null)
667                                                 throw new InvalidCastException (String.Format ("The QName '{0}' cannot be written. No corresponding prefix is declared", qname));
668                                         WriteQualifiedName (qname.Name, qname.Namespace);
669                                 }
670                                 else
671                                         WriteString (String.Empty);
672                         }
673                         else if (value is IEnumerable) {
674                                 bool follow = false;
675                                 foreach (object obj in (IEnumerable) value) {
676                                         if (follow)
677                                                 WriteString (" ");
678                                         else
679                                                 follow = true;
680                                         WriteValue (obj);
681                                 }
682                         }
683                         else
684                                 throw new InvalidCastException (String.Format ("Type '{0}' cannot be cast to string", value.GetType ()));
685                 }
686
687                 public virtual void WriteValue (float value)
688                 {
689                         WriteString (XQueryConvert.FloatToString (value));
690                 }
691
692                 public virtual void WriteValue (string value)
693                 {
694                         WriteString (value);
695                 }
696 #endif
697
698                 #endregion
699         }
700 }