5 // Atsushi Enomoto <atsushi@ximian.com>
7 // Copyright (C) 2006 Novell, Inc.
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:
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
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.
31 using System.Collections;
32 using System.Globalization;
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.
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
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");
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
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).
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;
166 public string Prefix;
167 public string LocalName;
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);
201 TextWriter source; // the input TextWriter to .ctor().
203 // It is used for storing xml:space, xml:lang and xmlns values.
204 StringWriter preserver;
205 string preserved_name;
206 bool is_preserved_xmlns;
208 bool allow_doc_fragment;
209 bool close_output_stream = true;
210 bool ignore_encoding;
211 bool namespaces = true;
212 bool output_xmldecl = false;
214 bool check_character_validity;
215 NewLineHandling newline_handling = NewLineHandling.None;
217 bool is_document_entity;
218 WriteState state = WriteState.Start;
219 XmlNodeType node_state = XmlNodeType.None;
220 XmlNamespaceManager nsmanager;
222 XmlNodeInfo [] elements = new XmlNodeInfo [10];
223 Stack new_local_namespaces = new Stack ();
224 ArrayList explicit_nsdecls = new ArrayList ();
227 int indent_count = 2;
228 char indent_char = ' ';
229 string indent_string = " ";
231 bool indent_attributes;
233 char quote_char = '"';
237 public XmlTextWriter (string filename, Encoding encoding)
238 : this (new FileStream (filename, FileMode.Create, FileAccess.Write, FileShare.None), encoding)
242 public XmlTextWriter (Stream stream, Encoding encoding)
243 : this (new StreamWriter (stream,
244 encoding == null ? unmarked_utf8encoding : encoding))
246 ignore_encoding = (encoding == null);
248 allow_doc_fragment = true;
251 public XmlTextWriter (TextWriter writer)
254 allow_doc_fragment = true;
259 TextWriter writer, XmlWriterSettings settings)
261 if (settings == null)
262 settings = new XmlWriterSettings ();
266 close_output_stream = settings.CloseOutput;
268 settings.ConformanceLevel != System.Xml.ConformanceLevel.Document;
269 indent_string = settings.IndentChars == null ?
270 String.Empty : settings.IndentChars;
271 if (settings.NewLineChars != null)
272 newline = settings.NewLineChars;
273 indent_attributes = settings.NewLineOnAttributes;
275 check_character_validity = settings.CheckCharacters;
276 newline_handling = settings.NewLineHandling;
277 if (settings.OmitXmlDeclaration)
278 output_xmldecl = false;
282 void Initialize (TextWriter writer)
285 throw new ArgumentNullException ("writer");
286 XmlNameTable name_table = new NameTable ();
287 this.writer = writer;
288 if (writer is StreamWriter)
289 base_stream = ((StreamWriter) writer).BaseStream;
291 nsmanager = new XmlNamespaceManager (name_table);
292 newline = writer.NewLine;
295 newline_handling != NewLineHandling.None ?
296 new char [] {'&', '<', '>', '\r', '\n'} :
297 new char [] {'&', '<', '>'};
299 new char [] {'"', '&', '<', '>', '\r', '\n'};
303 // 2.0 XmlWriterSettings support
305 internal bool CheckCharacters {
306 set { check_character_validity = value; }
309 internal bool CloseOutput {
310 set { close_output_stream = value; }
313 // As for ConformanceLevel, MS.NET is inconsistent with
314 // MSDN documentation. For example, even if ConformanceLevel
315 // is set as .Auto, multiple WriteStartDocument() calls
316 // result in an error.
317 // ms-help://MS.NETFramework.v20.en/wd_xml/html/7db8802b-53d8-4735-a637-4d2d2158d643.htm
319 internal ConformanceLevel ConformanceLevel {
321 allow_doc_fragment = (value == System.Xml.ConformanceLevel.Fragment);
325 internal string IndentChars {
326 set { indent_string = (value == null) ? String.Empty : value; }
329 internal string NewLineChars {
330 set { newline = (value == null) ? String.Empty : value; }
333 internal bool NewLineOnAttributes {
334 set { indent_attributes = value; }
337 internal bool OmitXmlDeclaration {
338 set { output_xmldecl = !value; }
342 // Literal Output Control
344 public Formatting Formatting {
345 get { return indent ? Formatting.Indented : Formatting.None; }
347 // Someone thinks it should be settable even
348 // after writing some content (bug #78148).
349 // I totally disagree but here is the fix.
351 //if (state != WriteState.Start)
352 // throw InvalidOperation ("Formatting must be set before it is actually used to write output.");
353 indent = (value == Formatting.Indented);
357 public int Indentation {
358 get { return indent_count; }
361 throw ArgumentError ("Indentation must be non-negative integer.");
362 indent_count = value;
363 indent_string = value == 0 ? String.Empty :
364 new string (indent_char, indent_count);
368 public char IndentChar {
369 get { return indent_char; }
372 indent_string = new string (indent_char, indent_count);
376 public char QuoteChar {
377 get { return quote_char; }
379 if (state == WriteState.Attribute)
380 throw InvalidOperation ("QuoteChar must not be changed inside attribute value.");
381 if ((value != '\'') && (value != '\"'))
382 throw ArgumentError ("Only ' and \" are allowed as an attribute quote character.");
384 escaped_attr_chars [0] = quote_char;
390 public override string XmlLang {
391 get { return open_count == 0 ? null : elements [open_count - 1].XmlLang; }
394 public override XmlSpace XmlSpace {
395 get { return open_count == 0 ? XmlSpace.None : elements [open_count - 1].XmlSpace; }
398 public override WriteState WriteState {
399 get { return state; }
402 public override string LookupPrefix (string namespaceUri)
404 if (namespaceUri == null || namespaceUri == String.Empty)
405 throw ArgumentError ("The Namespace cannot be empty.");
407 if (namespaceUri == nsmanager.DefaultNamespace)
410 string prefix = nsmanager.LookupPrefixExclusive (
411 namespaceUri, false);
413 // XmlNamespaceManager has changed to return null
414 // when NSURI not found.
415 // (Contradiction to the ECMA documentation.)
421 public Stream BaseStream {
422 get { return base_stream; }
425 public override void Close ()
427 if (state == WriteState.Attribute)
428 WriteEndAttribute ();
429 while (open_count > 0)
432 if (close_output_stream)
436 state = WriteState.Closed;
439 public override void Flush ()
445 public bool Namespaces {
446 get { return namespaces; }
448 if (state != WriteState.Start)
449 throw InvalidOperation ("This property must be set before writing output.");
456 public override void WriteStartDocument ()
458 WriteStartDocumentCore (false, false);
459 is_document_entity = true;
462 public override void WriteStartDocument (bool standalone)
464 WriteStartDocumentCore (true, standalone);
465 is_document_entity = true;
468 void WriteStartDocumentCore (bool outputStd, bool standalone)
470 if (state != WriteState.Start)
471 throw StateError ("XmlDeclaration");
473 writer.Write ("<?xml version=");
474 writer.Write (quote_char);
475 writer.Write ("1.0");
476 writer.Write (quote_char);
477 if (!ignore_encoding) {
478 writer.Write (" encoding=");
479 writer.Write (quote_char);
480 writer.Write (writer.Encoding.WebName);
481 writer.Write (quote_char);
484 writer.Write (" standalone=");
485 writer.Write (quote_char);
486 writer.Write (standalone ? "yes" : "no");
487 writer.Write (quote_char);
491 output_xmldecl = false;
492 state = WriteState.Prolog;
495 public override void WriteEndDocument ()
499 case WriteState.Error:
501 case WriteState.Closed:
502 case WriteState.Start:
503 throw StateError ("EndDocument");
506 if (state == WriteState.Attribute)
507 WriteEndAttribute ();
508 while (open_count > 0)
511 state = WriteState.Start;
512 is_document_entity = false;
515 // DocType Declaration
517 public override void WriteDocType (string name,
518 string pubid, string sysid, string subset)
521 throw ArgumentError ("name");
522 if (!XmlChar.IsName (name))
523 throw ArgumentError ("name");
525 if (node_state != XmlNodeType.None)
526 throw StateError ("DocType");
527 node_state = XmlNodeType.DocumentType;
530 OutputAutoStartDocument ();
534 writer.Write ("<!DOCTYPE ");
537 writer.Write (" PUBLIC ");
538 writer.Write (quote_char);
539 writer.Write (pubid);
540 writer.Write (quote_char);
542 writer.Write (quote_char);
544 writer.Write (sysid);
545 writer.Write (quote_char);
547 else if (sysid != null) {
548 writer.Write (" SYSTEM ");
549 writer.Write (quote_char);
550 writer.Write (sysid);
551 writer.Write (quote_char);
554 if (subset != null) {
556 // LAMESPEC: see the top of this source.
557 writer.Write (subset);
562 state = WriteState.Prolog;
567 public override void WriteStartElement (
568 string prefix, string localName, string namespaceUri)
571 if (state == WriteState.Error || state == WriteState.Closed)
573 if (state == WriteState.Closed)
575 throw StateError ("StartTag");
576 node_state = XmlNodeType.Element;
578 bool anonPrefix = (prefix == null);
580 prefix = String.Empty;
582 // Crazy namespace check goes here.
584 // 1. if Namespaces is false, then any significant
585 // namespace indication is not allowed.
586 // 2. if Prefix is non-empty and NamespaceURI is
587 // empty, it is an error in 1.x, or it is reset to
588 // an empty string in 2.0.
589 // 3. null NamespaceURI indicates that namespace is
591 // 4. prefix must not be equivalent to "XML" in
592 // case-insensitive comparison.
593 if (!namespaces && namespaceUri != null && namespaceUri.Length > 0)
594 throw ArgumentError ("Namespace is disabled in this XmlTextWriter.");
595 if (!namespaces && prefix.Length > 0)
596 throw ArgumentError ("Namespace prefix is disabled in this XmlTextWriter.");
598 if (prefix.Length > 0 && namespaceUri == null)
599 throw ArgumentError ("Namespace URI must not be null when prefix is not an empty string.");
600 // Considering the fact that WriteStartAttribute()
601 // automatically changes argument namespaceURI, this
602 // is kind of silly implementation. See bug #77094.
604 prefix != null && prefix.Length == 3 &&
605 namespaceUri != XmlNamespace &&
606 (prefix [0] == 'x' || prefix [0] == 'X') &&
607 (prefix [1] == 'm' || prefix [1] == 'M') &&
608 (prefix [2] == 'l' || prefix [2] == 'L'))
609 throw new ArgumentException ("A prefix cannot be equivalent to \"xml\" in case-insensitive match.");
613 OutputAutoStartDocument ();
614 if (state == WriteState.Element)
615 CloseStartElement ();
617 elements [open_count - 1].HasElements = true;
619 nsmanager.PushScope ();
621 if (namespaces && namespaceUri != null) {
622 // If namespace URI is empty, then prefix must
624 if (anonPrefix && namespaceUri.Length > 0)
625 prefix = LookupPrefix (namespaceUri);
626 if (prefix == null || namespaceUri.Length == 0)
627 prefix = String.Empty;
634 if (prefix.Length > 0) {
635 writer.Write (prefix);
638 writer.Write (localName);
640 if (elements.Length == open_count) {
641 XmlNodeInfo [] tmp = new XmlNodeInfo [open_count << 1];
642 Array.Copy (elements, tmp, open_count);
645 if (elements [open_count] == null)
646 elements [open_count] =
648 XmlNodeInfo info = elements [open_count];
649 info.Prefix = prefix;
650 info.LocalName = localName;
651 info.NS = namespaceUri;
652 info.HasSimple = false;
653 info.HasElements = false;
654 info.XmlLang = XmlLang;
655 info.XmlSpace = XmlSpace;
658 if (namespaces && namespaceUri != null) {
659 string oldns = nsmanager.LookupNamespace (prefix, false);
660 if (oldns != namespaceUri) {
661 nsmanager.AddNamespace (prefix, namespaceUri);
662 new_local_namespaces.Push (prefix);
666 state = WriteState.Element;
669 void CloseStartElement ()
671 CloseStartElementCore ();
673 if (state == WriteState.Element)
675 state = WriteState.Content;
678 void CloseStartElementCore ()
680 if (state == WriteState.Attribute)
681 WriteEndAttribute ();
683 if (new_local_namespaces.Count == 0) {
684 if (explicit_nsdecls.Count > 0)
685 explicit_nsdecls.Clear ();
689 // Missing xmlns attributes are added to
690 // explicit_nsdecls (it is cleared but this way
691 // I save another array creation).
692 int idx = explicit_nsdecls.Count;
693 while (new_local_namespaces.Count > 0) {
694 string p = (string) new_local_namespaces.Pop ();
696 for (int i = 0; i < explicit_nsdecls.Count; i++) {
697 if ((string) explicit_nsdecls [i] == p) {
704 explicit_nsdecls.Add (p);
707 for (int i = idx; i < explicit_nsdecls.Count; i++) {
708 string prefix = (string) explicit_nsdecls [i];
709 string ns = nsmanager.LookupNamespace (prefix, false);
711 continue; // superceded
712 if (prefix.Length > 0) {
713 writer.Write (" xmlns:");
714 writer.Write (prefix);
716 writer.Write (" xmlns");
719 writer.Write (quote_char);
720 WriteEscapedString (ns, true);
721 writer.Write (quote_char);
723 explicit_nsdecls.Clear ();
728 public override void WriteEndElement ()
730 WriteEndElementCore (false);
733 public override void WriteFullEndElement ()
735 WriteEndElementCore (true);
738 void WriteEndElementCore (bool full)
741 if (state == WriteState.Error || state == WriteState.Closed)
743 if (state == WriteState.Closed)
745 throw StateError ("EndElement");
747 throw InvalidOperation ("There is no more open element.");
749 bool isEmpty = state != WriteState.Content;
751 CloseStartElementCore ();
753 nsmanager.PopScope ();
755 if (state == WriteState.Element) {
759 writer.Write (" />");
762 if (full || state == WriteState.Content)
763 WriteIndentEndElement ();
765 XmlNodeInfo info = elements [--open_count];
767 if (full || state == WriteState.Content) {
769 if (info.Prefix.Length > 0) {
770 writer.Write (info.Prefix);
773 writer.Write (info.LocalName);
777 state = WriteState.Content;
779 node_state = XmlNodeType.EndElement;
784 public override void WriteStartAttribute (
785 string prefix, string localName, string namespaceUri)
787 if (state != WriteState.Element && state != WriteState.Start)
788 throw StateError ("Attribute");
790 if ((object) prefix == null)
791 prefix = String.Empty;
793 // For xmlns URI, prefix is forced to be "xmlns"
794 bool isNSDecl = false;
795 if (namespaceUri == XmlnsNamespace) {
797 if (prefix.Length == 0 && localName != "xmlns")
801 isNSDecl = (prefix == "xmlns" ||
802 localName == "xmlns" && prefix.Length == 0);
805 // MS implementation is pretty hacky here.
806 // Regardless of namespace URI it is regarded
807 // as NS URI for "xml".
809 namespaceUri = XmlNamespace;
810 // infer namespace URI.
811 else if ((object) namespaceUri == null) {
813 namespaceUri = XmlnsNamespace;
815 namespaceUri = String.Empty;
818 // It is silly design - null namespace with
819 // "xmlns" are allowed (for namespace-less
820 // output; while there is Namespaces property)
821 // On the other hand, namespace "" is not
823 if (isNSDecl && namespaceUri != XmlnsNamespace)
824 throw ArgumentError (String.Format ("The 'xmlns' attribute is bound to the reserved namespace '{0}'", XmlnsNamespace));
826 // If namespace URI is empty, then prefix
827 // must be empty as well.
828 if (prefix.Length > 0 && namespaceUri.Length == 0)
829 throw ArgumentError ("Namespace URI must not be null when prefix is not an empty string.");
831 // Dive into extremely complex procedure.
832 if (!isNSDecl && namespaceUri.Length > 0)
833 prefix = DetermineAttributePrefix (
834 prefix, localName, namespaceUri);
837 if (indent_attributes)
839 else if (state != WriteState.Start)
842 if (prefix.Length > 0) {
843 writer.Write (prefix);
846 writer.Write (localName);
848 writer.Write (quote_char);
850 if (isNSDecl || prefix == "xml") {
851 if (preserver == null)
852 preserver = new StringWriter ();
854 preserver.GetStringBuilder ().Length = 0;
858 is_preserved_xmlns = false;
859 preserved_name = localName;
861 is_preserved_xmlns = true;
862 preserved_name = localName == "xmlns" ?
863 String.Empty : localName;
867 state = WriteState.Attribute;
871 // "DetermineAttributePrefix(): local mapping overwrite"
872 string DetermineAttributePrefix (
873 string prefix, string local, string ns)
876 if (prefix.Length == 0) {
877 prefix = LookupPrefix (ns);
878 if (prefix != null && prefix.Length > 0)
882 prefix = nsmanager.NameTable.Add (prefix);
883 string existing = nsmanager.LookupNamespace (prefix, true);
886 if (existing != null) {
887 // See code comment on the head of
889 nsmanager.RemoveNamespace (prefix, existing);
890 if (nsmanager.LookupNamespace (prefix, true) != existing) {
892 nsmanager.AddNamespace (prefix, existing);
898 prefix = MockupPrefix (ns, true);
899 new_local_namespaces.Push (prefix);
900 nsmanager.AddNamespace (prefix, ns);
905 string MockupPrefix (string ns, bool skipLookup)
907 string prefix = skipLookup ? null :
909 if (prefix != null && prefix.Length > 0)
911 for (int p = 1; ; p++) {
912 prefix = StringUtil.Format ("d{0}p{1}", open_count, p);
913 if (new_local_namespaces.Contains (prefix))
915 if (null != nsmanager.LookupNamespace (
916 nsmanager.NameTable.Get (prefix)))
918 nsmanager.AddNamespace (prefix, ns);
919 new_local_namespaces.Push (prefix);
924 public override void WriteEndAttribute ()
926 if (state != WriteState.Attribute)
927 throw StateError ("End of attribute");
929 if (writer == preserver) {
931 string value = preserver.ToString ();
932 if (is_preserved_xmlns) {
933 if (preserved_name.Length > 0 &&
935 throw ArgumentError ("Non-empty prefix must be mapped to non-empty namespace URI.");
936 string existing = nsmanager.LookupNamespace (preserved_name, false);
937 explicit_nsdecls.Add (preserved_name);
938 if (open_count > 0 &&
939 elements [open_count - 1].NS == String.Empty &&
940 elements [open_count - 1].Prefix == preserved_name)
942 else if (existing != value)
943 nsmanager.AddNamespace (preserved_name, value);
945 switch (preserved_name) {
948 elements [open_count - 1].XmlLang = value;
954 elements [open_count - 1].XmlSpace = XmlSpace.Default;
958 elements [open_count - 1].XmlSpace = XmlSpace.Preserve;
961 throw ArgumentError ("Invalid value for xml:space.");
966 writer.Write (value);
969 writer.Write (quote_char);
970 state = WriteState.Element;
975 public override void WriteComment (string text)
978 throw ArgumentError ("text");
980 if (text.Length > 0 && text [text.Length - 1] == '-')
981 throw ArgumentError ("An input string to WriteComment method must not end with '-'. Escape it with 'D;'.");
982 if (StringUtil.IndexOf (text, "--") > 0)
983 throw ArgumentError ("An XML comment cannot end with \"-\".");
985 if (state == WriteState.Attribute || state == WriteState.Element)
986 CloseStartElement ();
990 ShiftStateTopLevel ("Comment", false, false, false);
992 writer.Write ("<!--");
994 writer.Write ("-->");
997 // LAMESPEC: see comments on the top of this source.
998 public override void WriteProcessingInstruction (string name, string text)
1001 throw ArgumentError ("name");
1003 throw ArgumentError ("text");
1007 if (!XmlChar.IsName (name))
1008 throw ArgumentError ("A processing instruction name must be a valid XML name.");
1010 if (StringUtil.IndexOf (text, "?>") > 0)
1011 throw ArgumentError ("Processing instruction cannot contain \"?>\" as its value.");
1013 ShiftStateTopLevel ("ProcessingInstruction", false, name == "xml", false);
1015 writer.Write ("<?");
1016 writer.Write (name);
1018 writer.Write (text);
1019 writer.Write ("?>");
1021 if (state == WriteState.Start)
1022 state = WriteState.Prolog;
1027 public override void WriteWhitespace (string text)
1030 throw ArgumentError ("text");
1032 // huh? Shouldn't it accept an empty string???
1033 if (text.Length == 0 ||
1034 XmlChar.IndexOfNonWhitespace (text) >= 0)
1035 throw ArgumentError ("WriteWhitespace method accepts only whitespaces.");
1037 ShiftStateTopLevel ("Whitespace", true, false, true);
1039 writer.Write (text);
1042 public override void WriteCData (string text)
1045 text = String.Empty;
1046 ShiftStateContent ("CData", false);
1048 if (StringUtil.IndexOf (text, "]]>") >= 0)
1049 throw ArgumentError ("CDATA section must not contain ']]>'.");
1050 writer.Write ("<![CDATA[");
1051 WriteCheckedString (text);
1052 writer.Write ("]]>");
1055 public override void WriteString (string text)
1057 if (text == null || text.Length == 0)
1058 return; // do nothing, including state transition.
1059 ShiftStateContent ("Text", true);
1061 WriteEscapedString (text, state == WriteState.Attribute);
1064 public override void WriteRaw (string raw)
1067 return; // do nothing, including state transition.
1071 // LAMESPEC: It rejects XMLDecl while it allows
1072 // DocType which could consist of non well-formed XML.
1073 ShiftStateTopLevel ("Raw string", true, true, true);
1078 public override void WriteCharEntity (char ch)
1080 WriteCharacterEntity (ch, '\0', false);
1083 public override void WriteSurrogateCharEntity (char low, char high)
1085 WriteCharacterEntity (low, high, true);
1088 void WriteCharacterEntity (char ch, char high, bool surrogate)
1091 ('\uD800' > high || high > '\uDC00' ||
1092 '\uDC00' > ch || ch > '\uDFFF'))
1093 throw ArgumentError (String.Format ("Invalid surrogate pair was found. Low: &#x{0:X}; High: &#x{0:X};", (int) ch, (int) high));
1094 else if (check_character_validity && XmlChar.IsInvalid (ch))
1095 throw ArgumentError (String.Format ("Invalid character &#x{0:X};", (int) ch));
1097 ShiftStateContent ("Character", true);
1099 int v = surrogate ? (high - 0xD800) * 0x400 + ch - 0xDC00 + 0x10000 : (int) ch;
1100 writer.Write ("&#x");
1101 writer.Write (v.ToString ("X", CultureInfo.InvariantCulture));
1105 public override void WriteEntityRef (string name)
1108 throw ArgumentError ("name");
1109 if (!XmlChar.IsName (name))
1110 throw ArgumentError ("Argument name must be a valid XML name.");
1112 ShiftStateContent ("Entity reference", true);
1115 writer.Write (name);
1121 public override void WriteName (string name)
1124 throw ArgumentError ("name");
1125 if (!XmlChar.IsName (name))
1126 throw ArgumentError ("Not a valid name string.");
1130 public override void WriteNmToken (string nmtoken)
1132 if (nmtoken == null)
1133 throw ArgumentError ("nmtoken");
1134 if (!XmlChar.IsNmToken (nmtoken))
1135 throw ArgumentError ("Not a valid NMTOKEN string.");
1136 WriteString (nmtoken);
1139 public override void WriteQualifiedName (
1140 string localName, string ns)
1142 if (localName == null)
1143 throw ArgumentError ("localName");
1147 if (ns == XmlnsNamespace)
1148 throw ArgumentError ("Prefix 'xmlns' is reserved and cannot be overriden.");
1149 if (!XmlChar.IsNCName (localName))
1150 throw ArgumentError ("localName must be a valid NCName.");
1152 ShiftStateContent ("QName", true);
1155 state == WriteState.Content || ns.Length > 0 ?
1156 LookupPrefix (ns) : String.Empty;
1157 if (prefix == null) {
1158 if (state == WriteState.Attribute)
1159 prefix = MockupPrefix (ns, false);
1161 throw ArgumentError (String.Format ("Namespace '{0}' is not declared.", ns));
1164 if (prefix != String.Empty) {
1165 writer.Write (prefix);
1168 writer.Write (localName);
1173 void CheckChunkRange (Array buffer, int index, int count)
1176 throw new ArgumentNullException ("buffer");
1177 if (index < 0 || buffer.Length < index)
1178 throw ArgumentOutOfRangeError ("index");
1179 if (count < 0 || buffer.Length < index + count)
1180 throw ArgumentOutOfRangeError ("count");
1183 public override void WriteBase64 (byte [] buffer, int index, int count)
1185 CheckChunkRange (buffer, index, count);
1187 WriteString (Convert.ToBase64String (buffer, index, count));
1190 public override void WriteBinHex (byte [] buffer, int index, int count)
1192 CheckChunkRange (buffer, index, count);
1194 ShiftStateContent ("BinHex", true);
1196 XmlConvert.WriteBinHex (buffer, index, count, writer);
1199 public override void WriteChars (char [] buffer, int index, int count)
1201 CheckChunkRange (buffer, index, count);
1203 ShiftStateContent ("Chars", true);
1205 WriteEscapedBuffer (buffer, index, count,
1206 state == WriteState.Attribute);
1209 public override void WriteRaw (char [] buffer, int index, int count)
1211 CheckChunkRange (buffer, index, count);
1213 ShiftStateContent ("Raw text", false);
1215 writer.Write (buffer, index, count);
1222 WriteIndentCore (0);
1225 void WriteIndentEndElement ()
1227 WriteIndentCore (-1);
1230 void WriteIndentCore (int nestFix)
1234 for (int i = open_count - 1; i >= 0; i--)
1235 if (elements [i].HasSimple)
1238 if (state != WriteState.Start)
1239 writer.Write (newline);
1240 for (int i = 0; i < open_count + nestFix; i++)
1241 writer.Write (indent_string);
1244 void OutputAutoStartDocument ()
1246 if (state != WriteState.Start)
1248 WriteStartDocumentCore (false, false);
1251 void ShiftStateTopLevel (string occured, bool allowAttribute, bool dontCheckXmlDecl, bool isCharacter)
1255 case WriteState.Error:
1257 case WriteState.Closed:
1258 throw StateError (occured);
1259 case WriteState.Start:
1261 CheckMixedContentState ();
1262 if (output_xmldecl && !dontCheckXmlDecl)
1263 OutputAutoStartDocument ();
1264 state = WriteState.Prolog;
1266 case WriteState.Attribute:
1269 goto case WriteState.Closed;
1270 case WriteState.Element:
1272 CheckMixedContentState ();
1273 CloseStartElement ();
1275 case WriteState.Content:
1277 CheckMixedContentState ();
1283 void CheckMixedContentState ()
1285 // if (open_count > 0 &&
1286 // state != WriteState.Attribute)
1287 // elements [open_count - 1].HasSimple = true;
1289 elements [open_count - 1].HasSimple = true;
1292 void ShiftStateContent (string occured, bool allowAttribute)
1296 case WriteState.Error:
1298 case WriteState.Closed:
1299 throw StateError (occured);
1300 case WriteState.Prolog:
1301 case WriteState.Start:
1302 if (!allow_doc_fragment || is_document_entity)
1303 goto case WriteState.Closed;
1305 OutputAutoStartDocument ();
1306 CheckMixedContentState ();
1307 state = WriteState.Content;
1309 case WriteState.Attribute:
1312 goto case WriteState.Closed;
1313 case WriteState.Element:
1314 CloseStartElement ();
1315 CheckMixedContentState ();
1317 case WriteState.Content:
1318 CheckMixedContentState ();
1323 void WriteEscapedString (string text, bool isAttribute)
1325 char [] escaped = isAttribute ?
1326 escaped_attr_chars : escaped_text_chars;
1328 int idx = text.IndexOfAny (escaped);
1330 char [] arr = text.ToCharArray ();
1331 WriteCheckedBuffer (arr, 0, idx);
1332 WriteEscapedBuffer (
1333 arr, idx, arr.Length - idx, isAttribute);
1335 WriteCheckedString (text);
1339 void WriteCheckedString (string s)
1341 int i = XmlChar.IndexOfInvalid (s, true);
1343 char [] arr = s.ToCharArray ();
1344 writer.Write (arr, 0, i);
1345 WriteCheckedBuffer (arr, i, arr.Length - i);
1347 // no invalid character.
1352 void WriteCheckedBuffer (char [] text, int idx, int length)
1355 int end = idx + length;
1356 while ((idx = XmlChar.IndexOfInvalid (text, start, length, true)) >= 0) {
1357 if (check_character_validity) // actually this is one time pass.
1358 throw ArgumentError (String.Format ("Input contains invalid character at {0} : &#x{1:X};", idx, (int) text [idx]));
1360 writer.Write (text, start, idx - start);
1361 writer.Write ("&#x");
1362 writer.Write (((int) text [idx]).ToString (
1364 CultureInfo.InvariantCulture));
1366 length -= idx - start + 1;
1370 writer.Write (text, start, end - start);
1373 void WriteEscapedBuffer (char [] text, int index, int length,
1377 int end = index + length;
1378 for (int i = start; i < end; i++) {
1386 WriteCheckedBuffer (text, start, i - start);
1389 case '&': writer.Write ("amp;"); break;
1390 case '<': writer.Write ("lt;"); break;
1391 case '>': writer.Write ("gt;"); break;
1392 case '\'': writer.Write ("apos;"); break;
1393 case '"': writer.Write ("quot;"); break;
1398 if (isAttribute && text [i] == quote_char)
1402 if (i + 1 < end && text [i] == '\n')
1407 WriteCheckedBuffer (text, start, i - start);
1409 writer.Write (text [i] == '\r' ?
1413 switch (newline_handling) {
1414 case NewLineHandling.Entitize:
1415 writer.Write (text [i] == '\r' ?
1418 case NewLineHandling.Replace:
1419 writer.Write (newline);
1422 writer.Write (text [i]);
1430 WriteCheckedBuffer (text, start, end - start);
1435 Exception ArgumentOutOfRangeError (string name)
1438 state = WriteState.Error;
1440 return new ArgumentOutOfRangeException (name);
1443 Exception ArgumentError (string msg)
1446 state = WriteState.Error;
1448 return new ArgumentException (msg);
1451 Exception InvalidOperation (string msg)
1454 state = WriteState.Error;
1456 return new InvalidOperationException (msg);
1459 Exception StateError (string occured)
1461 return InvalidOperation (String.Format ("This XmlWriter does not accept {0} at this state {1}.", occured, state));