2010-04-07 Jb Evain <jbevain@novell.com>
[mcs.git] / class / System.XML / System.Xml / XmlTextWriter2.cs
bloba47d0edc80b5fb13c25696cb5891d83240d5f2d2
1 //
2 // XmlTextWriter.cs
3 //
4 // Author:
5 // Atsushi Enomoto <atsushi@ximian.com>
6 //
7 // Copyright (C) 2006 Novell, Inc.
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
17 //
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
20 //
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 using System;
31 using System.Collections;
32 using System.Globalization;
33 using System.IO;
34 using System.Text;
35 using System.Xml;
39 This is a fresh implementation of XmlTextWriter since Mono 1.1.14.
41 Here are some implementation notes (mostly common to previous module):
43 - WriteProcessingInstruction() does not reject 'X' 'M' 'L'
45 XmlWriter violates section 2.6 of W3C XML 1.0 specification (3rd.
46 edition) since it incorrectly allows such PI target that consists of
47 case-insensitive sequence of 'X' - 'M' - 'L'. This is XmlWriter API
48 design failure which does not provide perfect WriteStartDocument().
50 - XmlTextWriter does not escape trailing ']' in internal subset.
52 The fact is as this subsection title shows. It means, to make an
53 XmlWriter compatible with other XmlWriters, it should always escape
54 the trailing ']' of the input, but XmlTextWriter runs no check.
56 - Prefix autogeneration for global attributes
58 When an attribute has a non-empty namespace URI, the prefix must be
59 non-empty string (since if the prefix is empty it is regarded as a
60 local attribute). In such case, a dummy prefix must be created.
62 Since attributes are written to TextWriter almost immediately, the
63 same prefix might appear in the later attributes.
65 - Namespace context
67 Namespace handling in XmlTextWriter is pretty nasty.
69 First of all, if WriteStartElement() takes null namespaceURI, then
70 the element has no explicit namespace and it is treated as if
71 Namespaces property were set as false.
73 Namespace context is structured by some writer methods:
75 - WriteStartElement() : If it has a non-empty argument prefix, then
76 the new prefix is bound to the argument namespaceURI. If prefix
77 is "" and namespaceURI is not empty, then it consists of a
78 default namespace.
80 - WriteStartAttribute() : there are two namespace provisions here:
81 1) like WriteStartElement() prefix and namespaceURI are not empty
82 2) prefix is "xmlns", or localName is "xmlns" and prefix is ""
83 If prefix is "" and namespaceURI is not empty, then the prefix is
84 "mocked up" (since an empty prefix is not possible for attributes).
86 - WriteQualifiedName() : the argument name and namespaceURI creates
87 a new namespace mapping. Note that default namespace (prefix "")
88 is not constructed at the state of WriteState.Attribute.
90 Note that WriteElementString() internally calls WriteStartElement()
91 and WriteAttributeString() internally calls WriteStartAttribute().
93 Sometimes those namespace outputs are in conflict. For example, if
95 w.WriteStartElement ("p", "foo", "urn:foo");
96 w.WriteStartAttribute ("xmlns", "p", "urn:bar");
97 w.WriteEndElement ();
99 urn:foo will be lost.
101 Here are the rules:
103 - If either prefix or localName is explicitly "xmlns" in
104 WriteStartAttribute(), it takes the highest precedence.
105 - For WriteStartElement(), prefix is always preserved, but
106 namespaceURI context might not (because of the rule above).
107 - For WriteStartAttribute(), prefix is preserved only if there is
108 no previous mapping in the local element. If it is in conflict,
109 a new prefix is "mocked up" like an empty prefix.
111 - DetermineAttributePrefix(): local mapping overwrite
113 (do not change this section title unless you also change cross
114 references in this file.)
116 Even if the prefix is already mapped to another namespace, it might
117 be overridable because the conflicting mapping might reside in one
118 of the ancestors.
120 To check it, we once try to remove existing mapping. If it is
121 successfully removed, then the mapping is locally added. In that
122 case, we cannot override it, so mock another prefix up.
125 - Attribute value preservation
127 Since xmlns and xml:* attributes are used to determine some public
128 behaviors such as XmlLang, XmlSpace and LookupPrefix(), it must
129 preserve what value is being written. At the same time, users might
130 call WriteString(), WhiteEntityRef() etc. separately, in such cases
131 we must preserve what is output to the stream.
133 This preservation is done using a "temporary preservation buffer",
134 the output Flush() behavior is different from MS. In such case that
135 XmlTextWriter uses that buffer, it won't be write anything until
136 XmlTextWriter.WriteEndAttribute() is called. If we implement it like
137 MS, it results in meaningless performance loss (it is not something
138 people should expect. There is no solid behavior on when start tag
139 closing '>' is written).
144 #if NET_1_1
145 namespace System.Xml
146 #else
147 namespace Mono.Xml
148 #endif
150 public class XmlTextWriter : XmlWriter
152 // Static/constant members.
154 const string XmlNamespace = "http://www.w3.org/XML/1998/namespace";
155 const string XmlnsNamespace = "http://www.w3.org/2000/xmlns/";
157 static readonly Encoding unmarked_utf8encoding =
158 new UTF8Encoding (false, false);
159 static char [] escaped_text_chars;
160 static char [] escaped_attr_chars;
162 // Internal classes
164 class XmlNodeInfo
166 public string Prefix;
167 public string LocalName;
168 public string NS;
169 public bool HasSimple;
170 public bool HasElements;
171 public string XmlLang;
172 public XmlSpace XmlSpace;
175 internal class StringUtil
177 static CultureInfo cul = CultureInfo.InvariantCulture;
178 static CompareInfo cmp =
179 CultureInfo.InvariantCulture.CompareInfo;
181 public static int IndexOf (string src, string target)
183 return cmp.IndexOf (src, target);
186 public static int Compare (string s1, string s2)
188 return cmp.Compare (s1, s2);
191 public static string Format (
192 string format, params object [] args)
194 return String.Format (cul, format, args);
198 enum XmlDeclState {
199 Allow,
200 Ignore,
201 Auto,
202 Prohibit,
205 // Instance fields
207 Stream base_stream;
208 TextWriter source; // the input TextWriter to .ctor().
209 TextWriter writer;
210 // It is used for storing xml:space, xml:lang and xmlns values.
211 StringWriter preserver;
212 string preserved_name;
213 bool is_preserved_xmlns;
215 bool allow_doc_fragment;
216 bool close_output_stream = true;
217 bool ignore_encoding;
218 bool namespaces = true;
219 XmlDeclState xmldecl_state = XmlDeclState.Allow;
221 bool check_character_validity;
222 NewLineHandling newline_handling = NewLineHandling.None;
224 bool is_document_entity;
225 WriteState state = WriteState.Start;
226 XmlNodeType node_state = XmlNodeType.None;
227 XmlNamespaceManager nsmanager;
228 int open_count;
229 XmlNodeInfo [] elements = new XmlNodeInfo [10];
230 Stack new_local_namespaces = new Stack ();
231 ArrayList explicit_nsdecls = new ArrayList ();
232 NamespaceHandling namespace_handling;
234 bool indent;
235 int indent_count = 2;
236 char indent_char = ' ';
237 string indent_string = " ";
238 string newline;
239 bool indent_attributes;
241 char quote_char = '"';
243 bool v2;
245 // Constructors
247 public XmlTextWriter (string filename, Encoding encoding)
248 : this (new FileStream (filename, FileMode.Create, FileAccess.Write, FileShare.None), encoding)
252 public XmlTextWriter (Stream stream, Encoding encoding)
253 : this (new StreamWriter (stream,
254 encoding == null ? unmarked_utf8encoding : encoding))
256 ignore_encoding = (encoding == null);
257 Initialize (writer);
258 allow_doc_fragment = true;
261 public XmlTextWriter (TextWriter writer)
263 if (writer == null)
264 throw new ArgumentNullException ("writer");
265 ignore_encoding = (writer.Encoding == null);
266 Initialize (writer);
267 allow_doc_fragment = true;
270 #if NET_2_0
271 internal XmlTextWriter (
272 TextWriter writer, XmlWriterSettings settings, bool closeOutput)
274 v2 = true;
276 if (settings == null)
277 settings = new XmlWriterSettings ();
279 Initialize (writer);
281 close_output_stream = closeOutput;
282 allow_doc_fragment =
283 settings.ConformanceLevel != ConformanceLevel.Document;
284 switch (settings.ConformanceLevel) {
285 case ConformanceLevel.Auto:
286 xmldecl_state = settings.OmitXmlDeclaration ? XmlDeclState.Ignore : XmlDeclState.Allow;
287 break;
288 case ConformanceLevel.Document:
289 // LAMESPEC:
290 // On MSDN, XmlWriterSettings.OmitXmlDeclaration is documented as:
291 // "The XML declaration is always written if
292 // ConformanceLevel is set to Document, even
293 // if OmitXmlDeclaration is set to true. "
294 // but it is incorrect. It does consider
295 // OmitXmlDeclaration property.
296 xmldecl_state = settings.OmitXmlDeclaration ? XmlDeclState.Ignore : XmlDeclState.Auto;
297 break;
298 case ConformanceLevel.Fragment:
299 xmldecl_state = XmlDeclState.Prohibit;
300 break;
302 if (settings.Indent)
303 Formatting = Formatting.Indented;
304 indent_string = settings.IndentChars == null ?
305 String.Empty : settings.IndentChars;
306 if (settings.NewLineChars != null)
307 newline = settings.NewLineChars;
308 indent_attributes = settings.NewLineOnAttributes;
310 check_character_validity = settings.CheckCharacters;
311 newline_handling = settings.NewLineHandling;
312 namespace_handling = settings.NamespaceHandling;
314 #endif
316 void Initialize (TextWriter writer)
318 if (writer == null)
319 throw new ArgumentNullException ("writer");
320 XmlNameTable name_table = new NameTable ();
321 this.writer = writer;
322 if (writer is StreamWriter)
323 base_stream = ((StreamWriter) writer).BaseStream;
324 source = writer;
325 nsmanager = new XmlNamespaceManager (name_table);
326 newline = writer.NewLine;
328 escaped_text_chars =
329 newline_handling != NewLineHandling.None ?
330 new char [] {'&', '<', '>', '\r', '\n'} :
331 new char [] {'&', '<', '>'};
332 escaped_attr_chars =
333 new char [] {'"', '&', '<', '>', '\r', '\n'};
336 #if NET_2_0
337 // 2.0 XmlWriterSettings support
339 // As for ConformanceLevel, MS.NET is inconsistent with
340 // MSDN documentation. For example, even if ConformanceLevel
341 // is set as .Auto, multiple WriteStartDocument() calls
342 // result in an error.
343 // ms-help://MS.NETFramework.v20.en/wd_xml/html/7db8802b-53d8-4735-a637-4d2d2158d643.htm
345 #endif
347 // Literal Output Control
349 public Formatting Formatting {
350 get { return indent ? Formatting.Indented : Formatting.None; }
351 set {
352 // Someone thinks it should be settable even
353 // after writing some content (bug #78148).
354 // I totally disagree but here is the fix.
356 //if (state != WriteState.Start)
357 // throw InvalidOperation ("Formatting must be set before it is actually used to write output.");
358 indent = (value == Formatting.Indented);
362 public int Indentation {
363 get { return indent_count; }
364 set {
365 if (value < 0)
366 throw ArgumentError ("Indentation must be non-negative integer.");
367 indent_count = value;
368 indent_string = value == 0 ? String.Empty :
369 new string (indent_char, indent_count);
373 public char IndentChar {
374 get { return indent_char; }
375 set {
376 indent_char = value;
377 indent_string = new string (indent_char, indent_count);
381 public char QuoteChar {
382 get { return quote_char; }
383 set {
384 if (state == WriteState.Attribute)
385 throw InvalidOperation ("QuoteChar must not be changed inside attribute value.");
386 if ((value != '\'') && (value != '\"'))
387 throw ArgumentError ("Only ' and \" are allowed as an attribute quote character.");
388 quote_char = value;
389 escaped_attr_chars [0] = quote_char;
393 // Context Retriever
395 public override string XmlLang {
396 get { return open_count == 0 ? null : elements [open_count - 1].XmlLang; }
399 public override XmlSpace XmlSpace {
400 get { return open_count == 0 ? XmlSpace.None : elements [open_count - 1].XmlSpace; }
403 public override WriteState WriteState {
404 get { return state; }
407 public override string LookupPrefix (string namespaceUri)
409 if (namespaceUri == null || namespaceUri == String.Empty)
410 throw ArgumentError ("The Namespace cannot be empty.");
412 if (namespaceUri == nsmanager.DefaultNamespace)
413 return String.Empty;
415 string prefix = nsmanager.LookupPrefixExclusive (
416 namespaceUri, false);
418 // XmlNamespaceManager has changed to return null
419 // when NSURI not found.
420 // (Contradiction to the ECMA documentation.)
421 return prefix;
424 // Stream Control
426 public Stream BaseStream {
427 get { return base_stream; }
430 public override void Close ()
432 #if NET_2_0
433 if (state != WriteState.Error) {
434 #endif
435 if (state == WriteState.Attribute)
436 WriteEndAttribute ();
437 while (open_count > 0)
438 WriteEndElement ();
439 #if NET_2_0
441 #endif
443 if (close_output_stream)
444 writer.Close ();
445 else
446 writer.Flush ();
447 state = WriteState.Closed;
450 public override void Flush ()
452 writer.Flush ();
455 // Misc Control
456 public bool Namespaces {
457 get { return namespaces; }
458 set {
459 if (state != WriteState.Start)
460 throw InvalidOperation ("This property must be set before writing output.");
461 namespaces = value;
465 // XML Declaration
467 public override void WriteStartDocument ()
469 WriteStartDocumentCore (false, false);
470 is_document_entity = true;
473 public override void WriteStartDocument (bool standalone)
475 WriteStartDocumentCore (true, standalone);
476 is_document_entity = true;
479 void WriteStartDocumentCore (bool outputStd, bool standalone)
481 if (state != WriteState.Start)
482 throw StateError ("XmlDeclaration");
484 switch (xmldecl_state) {
485 case XmlDeclState.Ignore:
486 return;
487 case XmlDeclState.Prohibit:
488 throw InvalidOperation ("WriteStartDocument cannot be called when ConformanceLevel is Fragment.");
491 state = WriteState.Prolog;
493 writer.Write ("<?xml version=");
494 writer.Write (quote_char);
495 writer.Write ("1.0");
496 writer.Write (quote_char);
497 if (!ignore_encoding) {
498 writer.Write (" encoding=");
499 writer.Write (quote_char);
500 writer.Write (writer.Encoding.WebName);
501 writer.Write (quote_char);
503 if (outputStd) {
504 writer.Write (" standalone=");
505 writer.Write (quote_char);
506 writer.Write (standalone ? "yes" : "no");
507 writer.Write (quote_char);
509 writer.Write ("?>");
511 xmldecl_state = XmlDeclState.Ignore;
514 public override void WriteEndDocument ()
516 switch (state) {
517 #if NET_2_0
518 case WriteState.Error:
519 #endif
520 case WriteState.Closed:
521 case WriteState.Start:
522 throw StateError ("EndDocument");
525 if (state == WriteState.Attribute)
526 WriteEndAttribute ();
527 while (open_count > 0)
528 WriteEndElement ();
530 state = WriteState.Start;
531 is_document_entity = false;
534 // DocType Declaration
536 public override void WriteDocType (string name,
537 string pubid, string sysid, string subset)
539 if (name == null)
540 throw ArgumentError ("name");
541 if (!XmlChar.IsName (name))
542 throw ArgumentError ("name");
544 if (node_state != XmlNodeType.None)
545 throw StateError ("DocType");
546 node_state = XmlNodeType.DocumentType;
548 if (xmldecl_state == XmlDeclState.Auto)
549 OutputAutoStartDocument ();
551 WriteIndent ();
553 writer.Write ("<!DOCTYPE ");
554 writer.Write (name);
555 if (pubid != null) {
556 writer.Write (" PUBLIC ");
557 writer.Write (quote_char);
558 writer.Write (pubid);
559 writer.Write (quote_char);
560 writer.Write (' ');
561 writer.Write (quote_char);
562 if (sysid != null)
563 writer.Write (sysid);
564 writer.Write (quote_char);
566 else if (sysid != null) {
567 writer.Write (" SYSTEM ");
568 writer.Write (quote_char);
569 writer.Write (sysid);
570 writer.Write (quote_char);
573 if (subset != null) {
574 writer.Write ("[");
575 // LAMESPEC: see the top of this source.
576 writer.Write (subset);
577 writer.Write ("]");
579 writer.Write ('>');
581 state = WriteState.Prolog;
584 // StartElement
586 public override void WriteStartElement (
587 string prefix, string localName, string namespaceUri)
589 #if NET_2_0
590 if (state == WriteState.Error || state == WriteState.Closed)
591 #else
592 if (state == WriteState.Closed)
593 #endif
594 throw StateError ("StartTag");
595 node_state = XmlNodeType.Element;
597 bool anonPrefix = (prefix == null);
598 if (prefix == null)
599 prefix = String.Empty;
601 // Crazy namespace check goes here.
603 // 1. if Namespaces is false, then any significant
604 // namespace indication is not allowed.
605 // 2. if Prefix is non-empty and NamespaceURI is
606 // empty, it is an error in 1.x, or it is reset to
607 // an empty string in 2.0.
608 // 3. null NamespaceURI indicates that namespace is
609 // not considered.
610 // 4. prefix must not be equivalent to "XML" in
611 // case-insensitive comparison.
612 if (!namespaces && namespaceUri != null && namespaceUri.Length > 0)
613 throw ArgumentError ("Namespace is disabled in this XmlTextWriter.");
614 if (!namespaces && prefix.Length > 0)
615 throw ArgumentError ("Namespace prefix is disabled in this XmlTextWriter.");
617 // If namespace URI is empty, then either prefix
618 // must be empty as well, or there is an
619 // existing namespace mapping for the prefix.
620 if (prefix.Length > 0 && namespaceUri == null) {
621 namespaceUri = nsmanager.LookupNamespace (prefix, false);
622 if (namespaceUri == null || namespaceUri.Length == 0)
623 throw ArgumentError ("Namespace URI must not be null when prefix is not an empty string.");
625 // Considering the fact that WriteStartAttribute()
626 // automatically changes argument namespaceURI, this
627 // is kind of silly implementation. See bug #77094.
628 if (namespaces &&
629 prefix != null && prefix.Length == 3 &&
630 namespaceUri != XmlNamespace &&
631 (prefix [0] == 'x' || prefix [0] == 'X') &&
632 (prefix [1] == 'm' || prefix [1] == 'M') &&
633 (prefix [2] == 'l' || prefix [2] == 'L'))
634 throw new ArgumentException ("A prefix cannot be equivalent to \"xml\" in case-insensitive match.");
637 if (xmldecl_state == XmlDeclState.Auto)
638 OutputAutoStartDocument ();
639 if (state == WriteState.Element)
640 CloseStartElement ();
641 if (open_count > 0)
642 elements [open_count - 1].HasElements = true;
644 nsmanager.PushScope ();
646 if (namespaces && namespaceUri != null) {
647 // If namespace URI is empty, then prefix must
648 // be empty as well.
649 if (anonPrefix && namespaceUri.Length > 0)
650 prefix = LookupPrefix (namespaceUri);
651 if (prefix == null || namespaceUri.Length == 0)
652 prefix = String.Empty;
655 WriteIndent ();
657 writer.Write ("<");
659 if (prefix.Length > 0) {
660 writer.Write (prefix);
661 writer.Write (':');
663 writer.Write (localName);
665 if (elements.Length == open_count) {
666 XmlNodeInfo [] tmp = new XmlNodeInfo [open_count << 1];
667 Array.Copy (elements, tmp, open_count);
668 elements = tmp;
670 if (elements [open_count] == null)
671 elements [open_count] =
672 new XmlNodeInfo ();
673 XmlNodeInfo info = elements [open_count];
674 info.Prefix = prefix;
675 info.LocalName = localName;
676 info.NS = namespaceUri;
677 info.HasSimple = false;
678 info.HasElements = false;
679 info.XmlLang = XmlLang;
680 info.XmlSpace = XmlSpace;
681 open_count++;
683 if (namespaces && namespaceUri != null) {
684 string oldns = nsmanager.LookupNamespace (prefix, false);
685 if (oldns != namespaceUri) {
686 nsmanager.AddNamespace (prefix, namespaceUri);
687 new_local_namespaces.Push (prefix);
691 state = WriteState.Element;
694 void CloseStartElement ()
696 CloseStartElementCore ();
698 if (state == WriteState.Element)
699 writer.Write ('>');
700 state = WriteState.Content;
703 void CloseStartElementCore ()
705 if (state == WriteState.Attribute)
706 WriteEndAttribute ();
708 if (new_local_namespaces.Count == 0) {
709 if (explicit_nsdecls.Count > 0)
710 explicit_nsdecls.Clear ();
711 return;
714 // Missing xmlns attributes are added to
715 // explicit_nsdecls (it is cleared but this way
716 // I save another array creation).
717 int idx = explicit_nsdecls.Count;
718 while (new_local_namespaces.Count > 0) {
719 string p = (string) new_local_namespaces.Pop ();
720 bool match = false;
721 for (int i = 0; i < explicit_nsdecls.Count; i++) {
722 if ((string) explicit_nsdecls [i] == p) {
723 match = true;
724 break;
727 if (match)
728 continue;
729 explicit_nsdecls.Add (p);
732 for (int i = idx; i < explicit_nsdecls.Count; i++) {
733 string prefix = (string) explicit_nsdecls [i];
734 string ns = nsmanager.LookupNamespace (prefix, false);
735 if (ns == null)
736 continue; // superceded
737 if (prefix.Length > 0) {
738 writer.Write (" xmlns:");
739 writer.Write (prefix);
740 } else {
741 writer.Write (" xmlns");
743 writer.Write ('=');
744 writer.Write (quote_char);
745 WriteEscapedString (ns, true);
746 writer.Write (quote_char);
748 explicit_nsdecls.Clear ();
751 // EndElement
753 public override void WriteEndElement ()
755 WriteEndElementCore (false);
758 public override void WriteFullEndElement ()
760 WriteEndElementCore (true);
763 void WriteEndElementCore (bool full)
765 #if NET_2_0
766 if (state == WriteState.Error || state == WriteState.Closed)
767 #else
768 if (state == WriteState.Closed)
769 #endif
770 throw StateError ("EndElement");
771 if (open_count == 0)
772 throw InvalidOperation ("There is no more open element.");
774 // bool isEmpty = state != WriteState.Content;
776 CloseStartElementCore ();
778 nsmanager.PopScope ();
780 if (state == WriteState.Element) {
781 if (full)
782 writer.Write ('>');
783 else
784 writer.Write (" />");
787 if (full || state == WriteState.Content)
788 WriteIndentEndElement ();
790 XmlNodeInfo info = elements [--open_count];
792 if (full || state == WriteState.Content) {
793 writer.Write ("</");
794 if (info.Prefix.Length > 0) {
795 writer.Write (info.Prefix);
796 writer.Write (':');
798 writer.Write (info.LocalName);
799 writer.Write ('>');
802 state = WriteState.Content;
803 if (open_count == 0)
804 node_state = XmlNodeType.EndElement;
807 // Attribute
809 public override void WriteStartAttribute (
810 string prefix, string localName, string namespaceUri)
812 // LAMESPEC: this violates the expected behavior of
813 // this method, as it incorrectly allows unbalanced
814 // output of attributes. Microfot changes description
815 // on its behavior at their will, regardless of
816 // ECMA description.
817 if (state == WriteState.Attribute)
818 WriteEndAttribute ();
820 if (state != WriteState.Element && state != WriteState.Start)
821 throw StateError ("Attribute");
823 if ((object) prefix == null)
824 prefix = String.Empty;
826 // For xmlns URI, prefix is forced to be "xmlns"
827 bool isNSDecl = false;
828 if (namespaceUri == XmlnsNamespace) {
829 isNSDecl = true;
830 if (prefix.Length == 0 && localName != "xmlns")
831 prefix = "xmlns";
833 else
834 isNSDecl = (prefix == "xmlns" ||
835 localName == "xmlns" && prefix.Length == 0);
837 if (namespaces) {
838 // MS implementation is pretty hacky here.
839 // Regardless of namespace URI it is regarded
840 // as NS URI for "xml".
841 if (prefix == "xml")
842 namespaceUri = XmlNamespace;
843 // infer namespace URI.
844 else if ((object) namespaceUri == null || (v2 && namespaceUri.Length == 0)) {
845 if (isNSDecl)
846 namespaceUri = XmlnsNamespace;
847 else
848 namespaceUri = String.Empty;
851 // It is silly design - null namespace with
852 // "xmlns" are allowed (for namespace-less
853 // output; while there is Namespaces property)
854 // On the other hand, namespace "" is not
855 // allowed.
856 if (isNSDecl && namespaceUri != XmlnsNamespace)
857 throw ArgumentError (String.Format ("The 'xmlns' attribute is bound to the reserved namespace '{0}'", XmlnsNamespace));
859 // If namespace URI is empty, then either prefix
860 // must be empty as well, or there is an
861 // existing namespace mapping for the prefix.
862 if (prefix.Length > 0 && namespaceUri.Length == 0) {
863 namespaceUri = nsmanager.LookupNamespace (prefix, false);
864 if (namespaceUri == null || namespaceUri.Length == 0)
865 throw ArgumentError ("Namespace URI must not be null when prefix is not an empty string.");
868 // Dive into extremely complex procedure.
869 if (!isNSDecl && namespaceUri.Length > 0)
870 prefix = DetermineAttributePrefix (
871 prefix, localName, namespaceUri);
874 if (indent_attributes)
875 WriteIndentAttribute ();
876 else if (state != WriteState.Start)
877 writer.Write (' ');
879 if (prefix.Length > 0) {
880 writer.Write (prefix);
881 writer.Write (':');
883 writer.Write (localName);
884 writer.Write ('=');
885 writer.Write (quote_char);
887 if (isNSDecl || prefix == "xml") {
888 if (preserver == null)
889 preserver = new StringWriter ();
890 else
891 preserver.GetStringBuilder ().Length = 0;
892 writer = preserver;
894 if (!isNSDecl) {
895 is_preserved_xmlns = false;
896 preserved_name = localName;
897 } else {
898 is_preserved_xmlns = true;
899 preserved_name = localName == "xmlns" ?
900 String.Empty : localName;
904 state = WriteState.Attribute;
907 // See also:
908 // "DetermineAttributePrefix(): local mapping overwrite"
909 string DetermineAttributePrefix (
910 string prefix, string local, string ns)
912 bool mockup = false;
913 if (prefix.Length == 0) {
914 prefix = LookupPrefix (ns);
915 if (prefix != null && prefix.Length > 0)
916 return prefix;
917 mockup = true;
918 } else {
919 prefix = nsmanager.NameTable.Add (prefix);
920 string existing = nsmanager.LookupNamespace (prefix, true);
921 if (existing == ns)
922 return prefix;
923 if (existing != null) {
924 // See code comment on the head of
925 // this source file.
926 nsmanager.RemoveNamespace (prefix, existing);
927 if (nsmanager.LookupNamespace (prefix, true) != existing) {
928 mockup = true;
929 nsmanager.AddNamespace (prefix, existing);
934 if (mockup)
935 prefix = MockupPrefix (ns, true);
936 new_local_namespaces.Push (prefix);
937 nsmanager.AddNamespace (prefix, ns);
939 return prefix;
942 string MockupPrefix (string ns, bool skipLookup)
944 string prefix = skipLookup ? null :
945 LookupPrefix (ns);
946 if (prefix != null && prefix.Length > 0)
947 return prefix;
948 for (int p = 1; ; p++) {
949 prefix = StringUtil.Format ("d{0}p{1}", open_count, p);
950 if (new_local_namespaces.Contains (prefix))
951 continue;
952 if (null != nsmanager.LookupNamespace (
953 nsmanager.NameTable.Get (prefix)))
954 continue;
955 nsmanager.AddNamespace (prefix, ns);
956 new_local_namespaces.Push (prefix);
957 return prefix;
961 public override void WriteEndAttribute ()
963 if (state != WriteState.Attribute)
964 throw StateError ("End of attribute");
966 if (writer == preserver) {
967 writer = source;
968 string value = preserver.ToString ();
969 if (is_preserved_xmlns) {
970 if (preserved_name.Length > 0 &&
971 value.Length == 0)
972 throw ArgumentError ("Non-empty prefix must be mapped to non-empty namespace URI.");
973 string existing = nsmanager.LookupNamespace (preserved_name, false);
975 // consider OmitDuplicates here.
976 if ((namespace_handling & NamespaceHandling.OmitDuplicates) == 0 || existing != value)
977 explicit_nsdecls.Add (preserved_name);
979 if (open_count > 0) {
981 if (v2 &&
982 elements [open_count - 1].Prefix == preserved_name &&
983 elements [open_count - 1].NS != value)
984 throw new XmlException (String.Format ("Cannot redefine the namespace for prefix '{0}' used at current element", preserved_name));
986 if (elements [open_count - 1].NS == String.Empty &&
987 elements [open_count - 1].Prefix == preserved_name)
988 ; // do nothing
989 else if (existing != value)
990 nsmanager.AddNamespace (preserved_name, value);
992 } else {
993 switch (preserved_name) {
994 case "lang":
995 if (open_count > 0)
996 elements [open_count - 1].XmlLang = value;
997 break;
998 case "space":
999 switch (value) {
1000 case "default":
1001 if (open_count > 0)
1002 elements [open_count - 1].XmlSpace = XmlSpace.Default;
1003 break;
1004 case "preserve":
1005 if (open_count > 0)
1006 elements [open_count - 1].XmlSpace = XmlSpace.Preserve;
1007 break;
1008 default:
1009 throw ArgumentError ("Invalid value for xml:space.");
1011 break;
1014 writer.Write (value);
1017 writer.Write (quote_char);
1018 state = WriteState.Element;
1021 // Non-Text Content
1023 public override void WriteComment (string text)
1025 if (text == null)
1026 throw ArgumentError ("text");
1028 if (text.Length > 0 && text [text.Length - 1] == '-')
1029 throw ArgumentError ("An input string to WriteComment method must not end with '-'. Escape it with '&#2D;'.");
1030 if (StringUtil.IndexOf (text, "--") > 0)
1031 throw ArgumentError ("An XML comment cannot end with \"-\".");
1033 if (state == WriteState.Attribute || state == WriteState.Element)
1034 CloseStartElement ();
1036 WriteIndent ();
1038 ShiftStateTopLevel ("Comment", false, false, false);
1040 writer.Write ("<!--");
1041 writer.Write (text);
1042 writer.Write ("-->");
1045 // LAMESPEC: see comments on the top of this source.
1046 public override void WriteProcessingInstruction (string name, string text)
1048 if (name == null)
1049 throw ArgumentError ("name");
1050 if (text == null)
1051 throw ArgumentError ("text");
1053 WriteIndent ();
1055 if (!XmlChar.IsName (name))
1056 throw ArgumentError ("A processing instruction name must be a valid XML name.");
1058 if (StringUtil.IndexOf (text, "?>") > 0)
1059 throw ArgumentError ("Processing instruction cannot contain \"?>\" as its value.");
1061 ShiftStateTopLevel ("ProcessingInstruction", false, name == "xml", false);
1063 writer.Write ("<?");
1064 writer.Write (name);
1065 writer.Write (' ');
1066 writer.Write (text);
1067 writer.Write ("?>");
1069 if (state == WriteState.Start)
1070 state = WriteState.Prolog;
1073 // Text Content
1075 public override void WriteWhitespace (string text)
1077 if (text == null)
1078 throw ArgumentError ("text");
1080 // huh? Shouldn't it accept an empty string???
1081 if (text.Length == 0 ||
1082 XmlChar.IndexOfNonWhitespace (text) >= 0)
1083 throw ArgumentError ("WriteWhitespace method accepts only whitespaces.");
1085 ShiftStateTopLevel ("Whitespace", true, false, true);
1087 writer.Write (text);
1090 public override void WriteCData (string text)
1092 if (text == null)
1093 text = String.Empty;
1094 ShiftStateContent ("CData", false);
1096 if (StringUtil.IndexOf (text, "]]>") >= 0)
1097 throw ArgumentError ("CDATA section must not contain ']]>'.");
1098 writer.Write ("<![CDATA[");
1099 WriteCheckedString (text);
1100 writer.Write ("]]>");
1103 public override void WriteString (string text)
1105 if (text == null || (text.Length == 0 && !v2))
1106 return; // do nothing, including state transition.
1107 ShiftStateContent ("Text", true);
1109 WriteEscapedString (text, state == WriteState.Attribute);
1112 public override void WriteRaw (string raw)
1114 if (raw == null)
1115 return; // do nothing, including state transition.
1117 //WriteIndent ();
1119 // LAMESPEC: It rejects XMLDecl while it allows
1120 // DocType which could consist of non well-formed XML.
1121 ShiftStateTopLevel ("Raw string", true, true, true);
1123 writer.Write (raw);
1126 public override void WriteCharEntity (char ch)
1128 WriteCharacterEntity (ch, '\0', false);
1131 public override void WriteSurrogateCharEntity (char low, char high)
1133 WriteCharacterEntity (low, high, true);
1136 void WriteCharacterEntity (char ch, char high, bool surrogate)
1138 if (surrogate &&
1139 ('\uD800' > high || high > '\uDC00' ||
1140 '\uDC00' > ch || ch > '\uDFFF'))
1141 throw ArgumentError (String.Format ("Invalid surrogate pair was found. Low: &#x{0:X}; High: &#x{0:X};", (int) ch, (int) high));
1142 else if (check_character_validity && XmlChar.IsInvalid (ch))
1143 throw ArgumentError (String.Format ("Invalid character &#x{0:X};", (int) ch));
1145 ShiftStateContent ("Character", true);
1147 int v = surrogate ? (high - 0xD800) * 0x400 + ch - 0xDC00 + 0x10000 : (int) ch;
1148 writer.Write ("&#x");
1149 writer.Write (v.ToString ("X", CultureInfo.InvariantCulture));
1150 writer.Write (';');
1153 public override void WriteEntityRef (string name)
1155 if (name == null)
1156 throw ArgumentError ("name");
1157 if (!XmlChar.IsName (name))
1158 throw ArgumentError ("Argument name must be a valid XML name.");
1160 ShiftStateContent ("Entity reference", true);
1162 writer.Write ('&');
1163 writer.Write (name);
1164 writer.Write (';');
1167 // Applied methods
1169 public override void WriteName (string name)
1171 if (name == null)
1172 throw ArgumentError ("name");
1173 if (!XmlChar.IsName (name))
1174 throw ArgumentError ("Not a valid name string.");
1175 WriteString (name);
1178 public override void WriteNmToken (string nmtoken)
1180 if (nmtoken == null)
1181 throw ArgumentError ("nmtoken");
1182 if (!XmlChar.IsNmToken (nmtoken))
1183 throw ArgumentError ("Not a valid NMTOKEN string.");
1184 WriteString (nmtoken);
1187 public override void WriteQualifiedName (
1188 string localName, string ns)
1190 if (localName == null)
1191 throw ArgumentError ("localName");
1192 if (ns == null)
1193 ns = String.Empty;
1195 if (ns == XmlnsNamespace)
1196 throw ArgumentError ("Prefix 'xmlns' is reserved and cannot be overriden.");
1197 if (!XmlChar.IsNCName (localName))
1198 throw ArgumentError ("localName must be a valid NCName.");
1200 ShiftStateContent ("QName", true);
1202 string prefix = ns.Length > 0 ? LookupPrefix (ns) : String.Empty;
1203 if (prefix == null) {
1204 if (state == WriteState.Attribute)
1205 prefix = MockupPrefix (ns, false);
1206 else
1207 throw ArgumentError (String.Format ("Namespace '{0}' is not declared.", ns));
1210 if (prefix != String.Empty) {
1211 writer.Write (prefix);
1212 writer.Write (":");
1214 writer.Write (localName);
1217 // Chunk data
1219 void CheckChunkRange (Array buffer, int index, int count)
1221 if (buffer == null)
1222 throw new ArgumentNullException ("buffer");
1223 if (index < 0 || buffer.Length < index)
1224 throw ArgumentOutOfRangeError ("index");
1225 if (count < 0 || buffer.Length < index + count)
1226 throw ArgumentOutOfRangeError ("count");
1229 public override void WriteBase64 (byte [] buffer, int index, int count)
1231 CheckChunkRange (buffer, index, count);
1233 WriteString (Convert.ToBase64String (buffer, index, count));
1236 public override void WriteBinHex (byte [] buffer, int index, int count)
1238 CheckChunkRange (buffer, index, count);
1240 ShiftStateContent ("BinHex", true);
1242 XmlConvert.WriteBinHex (buffer, index, count, writer);
1245 public override void WriteChars (char [] buffer, int index, int count)
1247 CheckChunkRange (buffer, index, count);
1249 ShiftStateContent ("Chars", true);
1251 WriteEscapedBuffer (buffer, index, count,
1252 state == WriteState.Attribute);
1255 public override void WriteRaw (char [] buffer, int index, int count)
1257 CheckChunkRange (buffer, index, count);
1259 ShiftStateContent ("Raw text", false);
1261 writer.Write (buffer, index, count);
1264 // Utilities
1266 void WriteIndent ()
1268 WriteIndentCore (0, false);
1271 void WriteIndentEndElement ()
1273 WriteIndentCore (-1, false);
1276 void WriteIndentAttribute ()
1278 if (!WriteIndentCore (0, true))
1279 writer.Write (' '); // space is required instead.
1282 bool WriteIndentCore (int nestFix, bool attribute)
1284 if (!indent)
1285 return false;
1286 for (int i = open_count - 1; i >= 0; i--)
1287 if (!attribute && elements [i].HasSimple)
1288 return false;
1290 if (state != WriteState.Start)
1291 writer.Write (newline);
1292 for (int i = 0; i < open_count + nestFix; i++)
1293 writer.Write (indent_string);
1294 return true;
1297 void OutputAutoStartDocument ()
1299 if (state != WriteState.Start)
1300 return;
1301 WriteStartDocumentCore (false, false);
1304 void ShiftStateTopLevel (string occured, bool allowAttribute, bool dontCheckXmlDecl, bool isCharacter)
1306 switch (state) {
1307 #if NET_2_0
1308 case WriteState.Error:
1309 #endif
1310 case WriteState.Closed:
1311 throw StateError (occured);
1312 case WriteState.Start:
1313 if (isCharacter)
1314 CheckMixedContentState ();
1315 if (xmldecl_state == XmlDeclState.Auto && !dontCheckXmlDecl)
1316 OutputAutoStartDocument ();
1317 state = WriteState.Prolog;
1318 break;
1319 case WriteState.Attribute:
1320 if (allowAttribute)
1321 break;
1322 goto case WriteState.Closed;
1323 case WriteState.Element:
1324 if (isCharacter)
1325 CheckMixedContentState ();
1326 CloseStartElement ();
1327 break;
1328 case WriteState.Content:
1329 if (isCharacter)
1330 CheckMixedContentState ();
1331 break;
1336 void CheckMixedContentState ()
1338 // if (open_count > 0 &&
1339 // state != WriteState.Attribute)
1340 // elements [open_count - 1].HasSimple = true;
1341 if (open_count > 0)
1342 elements [open_count - 1].HasSimple = true;
1345 void ShiftStateContent (string occured, bool allowAttribute)
1347 switch (state) {
1348 #if NET_2_0
1349 case WriteState.Error:
1350 #endif
1351 case WriteState.Closed:
1352 throw StateError (occured);
1353 case WriteState.Prolog:
1354 case WriteState.Start:
1355 if (!allow_doc_fragment || is_document_entity)
1356 goto case WriteState.Closed;
1357 if (xmldecl_state == XmlDeclState.Auto)
1358 OutputAutoStartDocument ();
1359 CheckMixedContentState ();
1360 state = WriteState.Content;
1361 break;
1362 case WriteState.Attribute:
1363 if (allowAttribute)
1364 break;
1365 goto case WriteState.Closed;
1366 case WriteState.Element:
1367 CloseStartElement ();
1368 CheckMixedContentState ();
1369 break;
1370 case WriteState.Content:
1371 CheckMixedContentState ();
1372 break;
1376 void WriteEscapedString (string text, bool isAttribute)
1378 char [] escaped = isAttribute ?
1379 escaped_attr_chars : escaped_text_chars;
1381 int idx = text.IndexOfAny (escaped);
1382 if (idx >= 0) {
1383 char [] arr = text.ToCharArray ();
1384 WriteCheckedBuffer (arr, 0, idx);
1385 WriteEscapedBuffer (
1386 arr, idx, arr.Length - idx, isAttribute);
1387 } else {
1388 WriteCheckedString (text);
1392 void WriteCheckedString (string s)
1394 int i = XmlChar.IndexOfInvalid (s, true);
1395 if (i >= 0) {
1396 char [] arr = s.ToCharArray ();
1397 writer.Write (arr, 0, i);
1398 WriteCheckedBuffer (arr, i, arr.Length - i);
1399 } else {
1400 // no invalid character.
1401 writer.Write (s);
1405 void WriteCheckedBuffer (char [] text, int idx, int length)
1407 int start = idx;
1408 int end = idx + length;
1409 while ((idx = XmlChar.IndexOfInvalid (text, start, length, true)) >= 0) {
1410 if (check_character_validity) // actually this is one time pass.
1411 throw ArgumentError (String.Format ("Input contains invalid character at {0} : &#x{1:X};", idx, (int) text [idx]));
1412 if (start < idx)
1413 writer.Write (text, start, idx - start);
1414 writer.Write ("&#x");
1415 writer.Write (((int) text [idx]).ToString (
1416 "X",
1417 CultureInfo.InvariantCulture));
1418 writer.Write (';');
1419 length -= idx - start + 1;
1420 start = idx + 1;
1422 if (start < end)
1423 writer.Write (text, start, end - start);
1426 void WriteEscapedBuffer (char [] text, int index, int length,
1427 bool isAttribute)
1429 int start = index;
1430 int end = index + length;
1431 for (int i = start; i < end; i++) {
1432 switch (text [i]) {
1433 default:
1434 continue;
1435 case '&':
1436 case '<':
1437 case '>':
1438 if (start < i)
1439 WriteCheckedBuffer (text, start, i - start);
1440 writer.Write ('&');
1441 switch (text [i]) {
1442 case '&': writer.Write ("amp;"); break;
1443 case '<': writer.Write ("lt;"); break;
1444 case '>': writer.Write ("gt;"); break;
1445 case '\'': writer.Write ("apos;"); break;
1446 case '"': writer.Write ("quot;"); break;
1448 break;
1449 case '"':
1450 case '\'':
1451 if (isAttribute && text [i] == quote_char)
1452 goto case '&';
1453 continue;
1454 case '\r':
1455 if (i + 1 < end && text [i] == '\n')
1456 i++; // CRLF
1457 goto case '\n';
1458 case '\n':
1459 if (start < i)
1460 WriteCheckedBuffer (text, start, i - start);
1461 if (isAttribute) {
1462 writer.Write (text [i] == '\r' ?
1463 "&#xD;" : "&#xA;");
1464 break;
1466 switch (newline_handling) {
1467 case NewLineHandling.Entitize:
1468 writer.Write (text [i] == '\r' ?
1469 "&#xD;" : "&#xA;");
1470 break;
1471 case NewLineHandling.Replace:
1472 writer.Write (newline);
1473 break;
1474 default:
1475 writer.Write (text [i]);
1476 break;
1478 break;
1480 start = i + 1;
1482 if (start < end)
1483 WriteCheckedBuffer (text, start, end - start);
1486 // Exceptions
1488 Exception ArgumentOutOfRangeError (string name)
1490 #if NET_2_0
1491 state = WriteState.Error;
1492 #endif
1493 return new ArgumentOutOfRangeException (name);
1496 Exception ArgumentError (string msg)
1498 #if NET_2_0
1499 state = WriteState.Error;
1500 #endif
1501 return new ArgumentException (msg);
1504 Exception InvalidOperation (string msg)
1506 #if NET_2_0
1507 state = WriteState.Error;
1508 #endif
1509 return new InvalidOperationException (msg);
1512 Exception StateError (string occured)
1514 return InvalidOperation (String.Format ("This XmlWriter does not accept {0} at this state {1}.", occured, state));