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)
284 XmlNameTable name_table = new NameTable ();
285 this.writer = writer;
286 if (writer is StreamWriter)
287 base_stream = ((StreamWriter) writer).BaseStream;
289 nsmanager = new XmlNamespaceManager (name_table);
290 newline = writer.NewLine;
293 newline_handling != NewLineHandling.None ?
294 new char [] {'&', '<', '>', '\r', '\n'} :
295 new char [] {'&', '<', '>'};
297 new char [] {'"', '&', '<', '>', '\r', '\n'};
301 // 2.0 XmlWriterSettings support
303 internal bool CheckCharacters {
304 set { check_character_validity = value; }
307 internal bool CloseOutput {
308 set { close_output_stream = value; }
311 // As for ConformanceLevel, MS.NET is inconsistent with
312 // MSDN documentation. For example, even if ConformanceLevel
313 // is set as .Auto, multiple WriteStartDocument() calls
314 // result in an error.
315 // ms-help://MS.NETFramework.v20.en/wd_xml/html/7db8802b-53d8-4735-a637-4d2d2158d643.htm
317 internal ConformanceLevel ConformanceLevel {
319 allow_doc_fragment = (value == System.Xml.ConformanceLevel.Fragment);
323 internal string IndentChars {
324 set { indent_string = (value == null) ? String.Empty : value; }
327 internal string NewLineChars {
328 set { newline = (value == null) ? String.Empty : value; }
331 internal bool NewLineOnAttributes {
332 set { indent_attributes = value; }
335 internal bool OmitXmlDeclaration {
336 set { output_xmldecl = !value; }
340 // Literal Output Control
342 public Formatting Formatting {
343 get { return indent ? Formatting.Indented : Formatting.None; }
345 // Someone thinks it should be settable even
346 // after writing some content (bug #78148).
347 // I totally disagree but here is the fix.
349 //if (state != WriteState.Start)
350 // throw InvalidOperation ("Formatting must be set before it is actually used to write output.");
351 indent = (value == Formatting.Indented);
355 public int Indentation {
356 get { return indent_count; }
359 throw ArgumentError ("Indentation must be non-negative integer.");
360 indent_count = value;
361 indent_string = value == 0 ? String.Empty :
362 new string (indent_char, indent_count);
366 public char IndentChar {
367 get { return indent_char; }
370 indent_string = new string (indent_char, indent_count);
374 public char QuoteChar {
375 get { return quote_char; }
377 if (state == WriteState.Attribute)
378 throw InvalidOperation ("QuoteChar must not be changed inside attribute value.");
379 if ((value != '\'') && (value != '\"'))
380 throw ArgumentError ("Only ' and \" are allowed as an attribute quote character.");
382 escaped_attr_chars [0] = quote_char;
388 public override string XmlLang {
389 get { return open_count == 0 ? null : elements [open_count - 1].XmlLang; }
392 public override XmlSpace XmlSpace {
393 get { return open_count == 0 ? XmlSpace.None : elements [open_count - 1].XmlSpace; }
396 public override WriteState WriteState {
397 get { return state; }
400 public override string LookupPrefix (string namespaceUri)
402 if (namespaceUri == null || namespaceUri == String.Empty)
403 throw ArgumentError ("The Namespace cannot be empty.");
405 if (namespaceUri == nsmanager.DefaultNamespace)
408 string prefix = nsmanager.LookupPrefixExclusive (
409 namespaceUri, false);
411 // XmlNamespaceManager has changed to return null
412 // when NSURI not found.
413 // (Contradiction to the ECMA documentation.)
419 public Stream BaseStream {
420 get { return base_stream; }
423 public override void Close ()
425 if (state == WriteState.Attribute)
426 WriteEndAttribute ();
427 while (open_count > 0)
430 if (close_output_stream)
434 state = WriteState.Closed;
437 public override void Flush ()
443 public bool Namespaces {
444 get { return namespaces; }
446 if (state != WriteState.Start)
447 throw InvalidOperation ("This property must be set before writing output.");
454 public override void WriteStartDocument ()
456 WriteStartDocumentCore (false, false);
457 is_document_entity = true;
460 public override void WriteStartDocument (bool standalone)
462 WriteStartDocumentCore (true, standalone);
463 is_document_entity = true;
466 void WriteStartDocumentCore (bool outputStd, bool standalone)
468 if (state != WriteState.Start)
469 throw StateError ("XmlDeclaration");
471 writer.Write ("<?xml version=");
472 writer.Write (quote_char);
473 writer.Write ("1.0");
474 writer.Write (quote_char);
475 if (!ignore_encoding) {
476 writer.Write (" encoding=");
477 writer.Write (quote_char);
478 writer.Write (writer.Encoding.WebName);
479 writer.Write (quote_char);
482 writer.Write (" standalone=");
483 writer.Write (quote_char);
484 writer.Write (standalone ? "yes" : "no");
485 writer.Write (quote_char);
489 output_xmldecl = false;
490 state = WriteState.Prolog;
493 public override void WriteEndDocument ()
497 case WriteState.Error:
499 case WriteState.Closed:
500 case WriteState.Start:
501 throw StateError ("EndDocument");
504 if (state == WriteState.Attribute)
505 WriteEndAttribute ();
506 while (open_count > 0)
509 state = WriteState.Start;
510 is_document_entity = false;
513 // DocType Declaration
515 public override void WriteDocType (string name,
516 string pubid, string sysid, string subset)
519 throw ArgumentError ("name");
520 if (!XmlChar.IsName (name))
521 throw ArgumentError ("name");
523 if (node_state != XmlNodeType.None)
524 throw StateError ("DocType");
525 node_state = XmlNodeType.DocumentType;
528 OutputAutoStartDocument ();
532 writer.Write ("<!DOCTYPE ");
535 writer.Write (" PUBLIC ");
536 writer.Write (quote_char);
537 writer.Write (pubid);
538 writer.Write (quote_char);
540 writer.Write (quote_char);
542 writer.Write (sysid);
543 writer.Write (quote_char);
545 else if (sysid != null) {
546 writer.Write (" SYSTEM ");
547 writer.Write (quote_char);
548 writer.Write (sysid);
549 writer.Write (quote_char);
552 if (subset != null) {
554 // LAMESPEC: see the top of this source.
555 writer.Write (subset);
560 state = WriteState.Prolog;
565 public override void WriteStartElement (
566 string prefix, string localName, string namespaceUri)
569 if (state == WriteState.Error || state == WriteState.Closed)
571 if (state == WriteState.Closed)
573 throw StateError ("StartTag");
574 node_state = XmlNodeType.Element;
576 bool anonPrefix = (prefix == null);
578 prefix = String.Empty;
580 // Crazy namespace check goes here.
582 // 1. if Namespaces is false, then any significant
583 // namespace indication is not allowed.
584 // 2. if Prefix is non-empty and NamespaceURI is
585 // empty, it is an error in 1.x, or it is reset to
586 // an empty string in 2.0.
587 // 3. null NamespaceURI indicates that namespace is
589 // 4. prefix must not be equivalent to "XML" in
590 // case-insensitive comparison.
591 if (!namespaces && namespaceUri != null && namespaceUri.Length > 0)
592 throw ArgumentError ("Namespace is disabled in this XmlTextWriter.");
593 if (!namespaces && prefix.Length > 0)
594 throw ArgumentError ("Namespace prefix is disabled in this XmlTextWriter.");
596 if (prefix.Length > 0 && namespaceUri == null)
597 throw ArgumentError ("Namespace URI must not be null when prefix is not an empty string.");
598 // Considering the fact that WriteStartAttribute()
599 // automatically changes argument namespaceURI, this
600 // is kind of silly implementation. See bug #77094.
602 prefix != null && prefix.Length == 3 &&
603 namespaceUri != XmlNamespace &&
604 (prefix [0] == 'x' || prefix [0] == 'X') &&
605 (prefix [1] == 'm' || prefix [1] == 'M') &&
606 (prefix [2] == 'l' || prefix [2] == 'L'))
607 throw new ArgumentException ("A prefix cannot be equivalent to \"xml\" in case-insensitive match.");
611 OutputAutoStartDocument ();
612 if (state == WriteState.Element)
613 CloseStartElement ();
615 elements [open_count - 1].HasElements = true;
617 nsmanager.PushScope ();
619 if (namespaces && namespaceUri != null) {
620 // If namespace URI is empty, then prefix must
622 if (anonPrefix && namespaceUri.Length > 0)
623 prefix = LookupPrefix (namespaceUri);
624 if (prefix == null || namespaceUri.Length == 0)
625 prefix = String.Empty;
632 if (prefix.Length > 0) {
633 writer.Write (prefix);
636 writer.Write (localName);
638 if (elements.Length == open_count) {
639 XmlNodeInfo [] tmp = new XmlNodeInfo [open_count << 1];
640 Array.Copy (elements, tmp, open_count);
643 if (elements [open_count] == null)
644 elements [open_count] =
646 XmlNodeInfo info = elements [open_count];
647 info.Prefix = prefix;
648 info.LocalName = localName;
649 info.NS = namespaceUri;
650 info.HasSimple = false;
651 info.HasElements = false;
652 info.XmlLang = XmlLang;
653 info.XmlSpace = XmlSpace;
656 if (namespaces && namespaceUri != null) {
657 string oldns = nsmanager.LookupNamespace (prefix, false);
658 if (oldns != namespaceUri) {
659 nsmanager.AddNamespace (prefix, namespaceUri);
660 new_local_namespaces.Push (prefix);
664 state = WriteState.Element;
667 void CloseStartElement ()
669 CloseStartElementCore ();
671 if (state == WriteState.Element)
673 state = WriteState.Content;
676 void CloseStartElementCore ()
678 if (state == WriteState.Attribute)
679 WriteEndAttribute ();
681 if (new_local_namespaces.Count == 0) {
682 if (explicit_nsdecls.Count > 0)
683 explicit_nsdecls.Clear ();
687 // Missing xmlns attributes are added to
688 // explicit_nsdecls (it is cleared but this way
689 // I save another array creation).
690 int idx = explicit_nsdecls.Count;
691 while (new_local_namespaces.Count > 0) {
692 string p = (string) new_local_namespaces.Pop ();
694 for (int i = 0; i < explicit_nsdecls.Count; i++) {
695 if ((string) explicit_nsdecls [i] == p) {
702 explicit_nsdecls.Add (p);
705 for (int i = idx; i < explicit_nsdecls.Count; i++) {
706 string prefix = (string) explicit_nsdecls [i];
707 string ns = nsmanager.LookupNamespace (prefix, false);
709 continue; // superceded
710 if (prefix.Length > 0) {
711 writer.Write (" xmlns:");
712 writer.Write (prefix);
714 writer.Write (" xmlns");
717 writer.Write (quote_char);
718 WriteEscapedString (ns, true);
719 writer.Write (quote_char);
721 explicit_nsdecls.Clear ();
726 public override void WriteEndElement ()
728 WriteEndElementCore (false);
731 public override void WriteFullEndElement ()
733 WriteEndElementCore (true);
736 void WriteEndElementCore (bool full)
739 if (state == WriteState.Error || state == WriteState.Closed)
741 if (state == WriteState.Closed)
743 throw StateError ("EndElement");
745 throw InvalidOperation ("There is no more open element.");
747 bool isEmpty = state != WriteState.Content;
749 CloseStartElementCore ();
751 nsmanager.PopScope ();
753 if (state == WriteState.Element) {
757 writer.Write (" />");
760 if (full || state == WriteState.Content)
761 WriteIndentEndElement ();
763 XmlNodeInfo info = elements [--open_count];
765 if (full || state == WriteState.Content) {
767 if (info.Prefix.Length > 0) {
768 writer.Write (info.Prefix);
771 writer.Write (info.LocalName);
775 state = WriteState.Content;
777 node_state = XmlNodeType.EndElement;
782 public override void WriteStartAttribute (
783 string prefix, string localName, string namespaceUri)
785 if (state != WriteState.Element && state != WriteState.Start)
786 throw StateError ("Attribute");
788 if ((object) prefix == null)
789 prefix = String.Empty;
791 // For xmlns URI, prefix is forced to be "xmlns"
792 bool isNSDecl = false;
793 if (namespaceUri == XmlnsNamespace) {
795 if (prefix.Length == 0 && localName != "xmlns")
799 isNSDecl = (prefix == "xmlns" ||
800 localName == "xmlns" && prefix.Length == 0);
803 // MS implementation is pretty hacky here.
804 // Regardless of namespace URI it is regarded
805 // as NS URI for "xml".
807 namespaceUri = XmlNamespace;
808 // infer namespace URI.
809 else if ((object) namespaceUri == null) {
811 namespaceUri = XmlnsNamespace;
813 namespaceUri = String.Empty;
816 // It is silly design - null namespace with
817 // "xmlns" are allowed (for namespace-less
818 // output; while there is Namespaces property)
819 // On the other hand, namespace "" is not
821 if (isNSDecl && namespaceUri != XmlnsNamespace)
822 throw ArgumentError (String.Format ("The 'xmlns' attribute is bound to the reserved namespace '{0}'", XmlnsNamespace));
824 // If namespace URI is empty, then prefix
825 // must be empty as well.
826 if (prefix.Length > 0 && namespaceUri.Length == 0)
827 throw ArgumentError ("Namespace URI must not be null when prefix is not an empty string.");
829 // Dive into extremely complex procedure.
830 if (!isNSDecl && namespaceUri.Length > 0)
831 prefix = DetermineAttributePrefix (
832 prefix, localName, namespaceUri);
835 if (indent_attributes)
837 else if (state != WriteState.Start)
840 if (prefix.Length > 0) {
841 writer.Write (prefix);
844 writer.Write (localName);
846 writer.Write (quote_char);
848 if (isNSDecl || prefix == "xml") {
849 if (preserver == null)
850 preserver = new StringWriter ();
852 preserver.GetStringBuilder ().Length = 0;
856 is_preserved_xmlns = false;
857 preserved_name = localName;
859 is_preserved_xmlns = true;
860 preserved_name = localName == "xmlns" ?
861 String.Empty : localName;
865 state = WriteState.Attribute;
869 // "DetermineAttributePrefix(): local mapping overwrite"
870 string DetermineAttributePrefix (
871 string prefix, string local, string ns)
874 if (prefix.Length == 0) {
875 prefix = LookupPrefix (ns);
876 if (prefix != null && prefix.Length > 0)
880 prefix = nsmanager.NameTable.Add (prefix);
881 string existing = nsmanager.LookupNamespace (prefix, true);
884 if (existing != null) {
885 // See code comment on the head of
887 nsmanager.RemoveNamespace (prefix, existing);
888 if (nsmanager.LookupNamespace (prefix, true) != existing) {
890 nsmanager.AddNamespace (prefix, existing);
896 prefix = MockupPrefix (ns, true);
897 new_local_namespaces.Push (prefix);
898 nsmanager.AddNamespace (prefix, ns);
903 string MockupPrefix (string ns, bool skipLookup)
905 string prefix = skipLookup ? null :
907 if (prefix != null && prefix.Length > 0)
909 for (int p = 1; ; p++) {
910 prefix = StringUtil.Format ("d{0}p{1}", open_count, p);
911 if (new_local_namespaces.Contains (prefix))
913 if (null != nsmanager.LookupNamespace (
914 nsmanager.NameTable.Get (prefix)))
916 nsmanager.AddNamespace (prefix, ns);
917 new_local_namespaces.Push (prefix);
922 public override void WriteEndAttribute ()
924 if (state != WriteState.Attribute)
925 throw StateError ("End of attribute");
927 if (writer == preserver) {
929 string value = preserver.ToString ();
930 if (is_preserved_xmlns) {
931 if (preserved_name.Length > 0 &&
933 throw ArgumentError ("Non-empty prefix must be mapped to non-empty namespace URI.");
934 string existing = nsmanager.LookupNamespace (preserved_name, false);
935 explicit_nsdecls.Add (preserved_name);
936 if (open_count > 0 &&
937 elements [open_count - 1].NS == String.Empty &&
938 elements [open_count - 1].Prefix == preserved_name)
940 else if (existing != value)
941 nsmanager.AddNamespace (preserved_name, value);
943 switch (preserved_name) {
946 elements [open_count - 1].XmlLang = value;
952 elements [open_count - 1].XmlSpace = XmlSpace.Default;
956 elements [open_count - 1].XmlSpace = XmlSpace.Preserve;
959 throw ArgumentError ("Invalid value for xml:space.");
964 writer.Write (value);
967 writer.Write (quote_char);
968 state = WriteState.Element;
973 public override void WriteComment (string text)
976 throw ArgumentError ("text");
978 if (text.Length > 0 && text [text.Length - 1] == '-')
979 throw ArgumentError ("An input string to WriteComment method must not end with '-'. Escape it with 'D;'.");
980 if (StringUtil.IndexOf (text, "--") > 0)
981 throw ArgumentError ("An XML comment cannot end with \"-\".");
983 if (state == WriteState.Attribute || state == WriteState.Element)
984 CloseStartElement ();
988 ShiftStateTopLevel ("Comment", false, false, false);
990 writer.Write ("<!--");
992 writer.Write ("-->");
995 // LAMESPEC: see comments on the top of this source.
996 public override void WriteProcessingInstruction (string name, string text)
999 throw ArgumentError ("name");
1001 throw ArgumentError ("text");
1005 if (!XmlChar.IsName (name))
1006 throw ArgumentError ("A processing instruction name must be a valid XML name.");
1008 if (StringUtil.IndexOf (text, "?>") > 0)
1009 throw ArgumentError ("Processing instruction cannot contain \"?>\" as its value.");
1011 ShiftStateTopLevel ("ProcessingInstruction", false, name == "xml", false);
1013 writer.Write ("<?");
1014 writer.Write (name);
1016 writer.Write (text);
1017 writer.Write ("?>");
1019 if (state == WriteState.Start)
1020 state = WriteState.Prolog;
1025 public override void WriteWhitespace (string text)
1028 throw ArgumentError ("text");
1030 // huh? Shouldn't it accept an empty string???
1031 if (text.Length == 0 ||
1032 XmlChar.IndexOfNonWhitespace (text) >= 0)
1033 throw ArgumentError ("WriteWhitespace method accepts only whitespaces.");
1035 ShiftStateTopLevel ("Whitespace", true, false, true);
1037 writer.Write (text);
1040 public override void WriteCData (string text)
1043 text = String.Empty;
1044 ShiftStateContent ("CData", false);
1046 if (StringUtil.IndexOf (text, "]]>") >= 0)
1047 throw ArgumentError ("CDATA section must not contain ']]>'.");
1048 writer.Write ("<![CDATA[");
1049 WriteCheckedString (text);
1050 writer.Write ("]]>");
1053 public override void WriteString (string text)
1055 if (text == null || text.Length == 0)
1056 return; // do nothing, including state transition.
1057 ShiftStateContent ("Text", true);
1059 WriteEscapedString (text, state == WriteState.Attribute);
1062 public override void WriteRaw (string raw)
1065 return; // do nothing, including state transition.
1069 // LAMESPEC: It rejects XMLDecl while it allows
1070 // DocType which could consist of non well-formed XML.
1071 ShiftStateTopLevel ("Raw string", true, true, true);
1076 public override void WriteCharEntity (char ch)
1078 WriteCharacterEntity (ch, '\0', false);
1081 public override void WriteSurrogateCharEntity (char low, char high)
1083 WriteCharacterEntity (low, high, true);
1086 void WriteCharacterEntity (char ch, char high, bool surrogate)
1089 ('\uD800' > high || high > '\uDC00' ||
1090 '\uDC00' > ch || ch > '\uDFFF'))
1091 throw ArgumentError (String.Format ("Invalid surrogate pair was found. Low: &#x{0:X}; High: &#x{0:X};", (int) ch, (int) high));
1092 else if (check_character_validity && XmlChar.IsInvalid (ch))
1093 throw ArgumentError (String.Format ("Invalid character &#x{0:X};", (int) ch));
1095 ShiftStateContent ("Character", true);
1097 int v = surrogate ? (high - 0xD800) * 0x400 + ch - 0xDC00 + 0x10000 : (int) ch;
1098 writer.Write ("&#x");
1099 writer.Write (v.ToString ("X", CultureInfo.InvariantCulture));
1103 public override void WriteEntityRef (string name)
1106 throw ArgumentError ("name");
1107 if (!XmlChar.IsName (name))
1108 throw ArgumentError ("Argument name must be a valid XML name.");
1110 ShiftStateContent ("Entity reference", true);
1113 writer.Write (name);
1119 public override void WriteName (string name)
1122 throw ArgumentError ("name");
1123 if (!XmlChar.IsName (name))
1124 throw ArgumentError ("Not a valid name string.");
1128 public override void WriteNmToken (string nmtoken)
1130 if (nmtoken == null)
1131 throw ArgumentError ("nmtoken");
1132 if (!XmlChar.IsNmToken (nmtoken))
1133 throw ArgumentError ("Not a valid NMTOKEN string.");
1134 WriteString (nmtoken);
1137 public override void WriteQualifiedName (
1138 string localName, string ns)
1140 if (localName == null)
1141 throw ArgumentError ("localName");
1145 if (ns == XmlnsNamespace)
1146 throw ArgumentError ("Prefix 'xmlns' is reserved and cannot be overriden.");
1147 if (!XmlChar.IsNCName (localName))
1148 throw ArgumentError ("localName must be a valid NCName.");
1150 ShiftStateContent ("QName", true);
1153 state == WriteState.Content || ns.Length > 0 ?
1154 LookupPrefix (ns) : String.Empty;
1155 if (prefix == null) {
1156 if (state == WriteState.Attribute)
1157 prefix = MockupPrefix (ns, false);
1159 throw ArgumentError (String.Format ("Namespace '{0}' is not declared.", ns));
1162 if (prefix != String.Empty) {
1163 writer.Write (prefix);
1166 writer.Write (localName);
1171 void CheckChunkRange (Array buffer, int index, int count)
1174 throw new ArgumentNullException ("buffer");
1175 if (index < 0 || buffer.Length < index)
1176 throw ArgumentOutOfRangeError ("index");
1177 if (count < 0 || buffer.Length < index + count)
1178 throw ArgumentOutOfRangeError ("count");
1181 public override void WriteBase64 (byte [] buffer, int index, int count)
1183 CheckChunkRange (buffer, index, count);
1185 WriteString (Convert.ToBase64String (buffer, index, count));
1188 public override void WriteBinHex (byte [] buffer, int index, int count)
1190 CheckChunkRange (buffer, index, count);
1192 ShiftStateContent ("BinHex", true);
1194 XmlConvert.WriteBinHex (buffer, index, count, writer);
1197 public override void WriteChars (char [] buffer, int index, int count)
1199 CheckChunkRange (buffer, index, count);
1201 ShiftStateContent ("Chars", true);
1203 WriteEscapedBuffer (buffer, index, count,
1204 state == WriteState.Attribute);
1207 public override void WriteRaw (char [] buffer, int index, int count)
1209 CheckChunkRange (buffer, index, count);
1211 ShiftStateContent ("Raw text", false);
1213 writer.Write (buffer, index, count);
1220 WriteIndentCore (0);
1223 void WriteIndentEndElement ()
1225 WriteIndentCore (-1);
1228 void WriteIndentCore (int nestFix)
1232 for (int i = open_count - 1; i >= 0; i--)
1233 if (elements [i].HasSimple)
1236 if (state != WriteState.Start)
1237 writer.Write (newline);
1238 for (int i = 0; i < open_count + nestFix; i++)
1239 writer.Write (indent_string);
1242 void OutputAutoStartDocument ()
1244 if (state != WriteState.Start)
1246 WriteStartDocumentCore (false, false);
1249 void ShiftStateTopLevel (string occured, bool allowAttribute, bool dontCheckXmlDecl, bool isCharacter)
1253 case WriteState.Error:
1255 case WriteState.Closed:
1256 throw StateError (occured);
1257 case WriteState.Start:
1259 CheckMixedContentState ();
1260 if (output_xmldecl && !dontCheckXmlDecl)
1261 OutputAutoStartDocument ();
1262 state = WriteState.Prolog;
1264 case WriteState.Attribute:
1267 goto case WriteState.Closed;
1268 case WriteState.Element:
1270 CheckMixedContentState ();
1271 CloseStartElement ();
1273 case WriteState.Content:
1275 CheckMixedContentState ();
1281 void CheckMixedContentState ()
1283 // if (open_count > 0 &&
1284 // state != WriteState.Attribute)
1285 // elements [open_count - 1].HasSimple = true;
1287 elements [open_count - 1].HasSimple = true;
1290 void ShiftStateContent (string occured, bool allowAttribute)
1294 case WriteState.Error:
1296 case WriteState.Closed:
1297 throw StateError (occured);
1298 case WriteState.Prolog:
1299 case WriteState.Start:
1300 if (!allow_doc_fragment || is_document_entity)
1301 goto case WriteState.Closed;
1303 OutputAutoStartDocument ();
1304 CheckMixedContentState ();
1305 state = WriteState.Content;
1307 case WriteState.Attribute:
1310 goto case WriteState.Closed;
1311 case WriteState.Element:
1312 CloseStartElement ();
1313 CheckMixedContentState ();
1315 case WriteState.Content:
1316 CheckMixedContentState ();
1321 void WriteEscapedString (string text, bool isAttribute)
1323 char [] escaped = isAttribute ?
1324 escaped_attr_chars : escaped_text_chars;
1326 int idx = text.IndexOfAny (escaped);
1328 char [] arr = text.ToCharArray ();
1329 WriteCheckedBuffer (arr, 0, idx);
1330 WriteEscapedBuffer (
1331 arr, idx, arr.Length - idx, isAttribute);
1333 WriteCheckedString (text);
1337 void WriteCheckedString (string s)
1339 int i = XmlChar.IndexOfInvalid (s, true);
1341 char [] arr = s.ToCharArray ();
1342 writer.Write (arr, 0, i);
1343 WriteCheckedBuffer (arr, i, arr.Length - i);
1345 // no invalid character.
1350 void WriteCheckedBuffer (char [] text, int idx, int length)
1353 int end = idx + length;
1354 while ((idx = XmlChar.IndexOfInvalid (text, start, length, true)) >= 0) {
1355 if (check_character_validity) // actually this is one time pass.
1356 throw ArgumentError (String.Format ("Input contains invalid character at {0} : &#x{1:X};", idx, (int) text [idx]));
1358 writer.Write (text, start, idx - start);
1359 writer.Write ("&#x");
1360 writer.Write (((int) text [idx]).ToString (
1362 CultureInfo.InvariantCulture));
1364 length -= idx - start + 1;
1368 writer.Write (text, start, end - start);
1371 void WriteEscapedBuffer (char [] text, int index, int length,
1375 int end = index + length;
1376 for (int i = start; i < end; i++) {
1384 WriteCheckedBuffer (text, start, i - start);
1387 case '&': writer.Write ("amp;"); break;
1388 case '<': writer.Write ("lt;"); break;
1389 case '>': writer.Write ("gt;"); break;
1390 case '\'': writer.Write ("apos;"); break;
1391 case '"': writer.Write ("quot;"); break;
1396 if (isAttribute && text [i] == quote_char)
1400 if (i + 1 < end && text [i] == '\n')
1405 WriteCheckedBuffer (text, start, i - start);
1407 writer.Write (text [i] == '\r' ?
1411 switch (newline_handling) {
1412 case NewLineHandling.Entitize:
1413 writer.Write (text [i] == '\r' ?
1416 case NewLineHandling.Replace:
1417 writer.Write (newline);
1420 writer.Write (text [i]);
1428 WriteCheckedBuffer (text, start, end - start);
1433 Exception ArgumentOutOfRangeError (string name)
1436 state = WriteState.Error;
1438 return new ArgumentOutOfRangeException (name);
1441 Exception ArgumentError (string msg)
1444 state = WriteState.Error;
1446 return new ArgumentException (msg);
1449 Exception InvalidOperation (string msg)
1452 state = WriteState.Error;
1454 return new InvalidOperationException (msg);
1457 Exception StateError (string occured)
1459 return InvalidOperation (String.Format ("This XmlWriter does not accept {0} at this state {1}.", occured, state));