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);
208 TextWriter source; // the input TextWriter to .ctor().
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;
229 XmlNodeInfo [] elements = new XmlNodeInfo [10];
230 Stack new_local_namespaces = new Stack ();
231 ArrayList explicit_nsdecls = new ArrayList ();
234 int indent_count = 2;
235 char indent_char = ' ';
236 string indent_string = " ";
238 bool indent_attributes;
240 char quote_char = '"';
244 public XmlTextWriter (string filename, Encoding encoding)
245 : this (new FileStream (filename, FileMode.Create, FileAccess.Write, FileShare.None), encoding)
249 public XmlTextWriter (Stream stream, Encoding encoding)
250 : this (new StreamWriter (stream,
251 encoding == null ? unmarked_utf8encoding : encoding))
253 ignore_encoding = (encoding == null);
255 allow_doc_fragment = true;
258 public XmlTextWriter (TextWriter writer)
261 allow_doc_fragment = true;
265 internal XmlTextWriter (
266 TextWriter writer, XmlWriterSettings settings, bool closeOutput)
268 if (settings == null)
269 settings = new XmlWriterSettings ();
273 close_output_stream = closeOutput;
275 settings.ConformanceLevel != ConformanceLevel.Document;
276 switch (settings.ConformanceLevel) {
277 case ConformanceLevel.Auto:
278 xmldecl_state = settings.OmitXmlDeclaration ? XmlDeclState.Ignore : XmlDeclState.Allow;
280 case ConformanceLevel.Document:
282 // On MSDN, XmlWriterSettings.OmitXmlDeclaration is documented as:
283 // "The XML declaration is always written if
284 // ConformanceLevel is set to Document, even
285 // if OmitXmlDeclaration is set to true. "
286 // but it is incorrect. It does consider
287 // OmitXmlDeclaration property.
288 xmldecl_state = settings.OmitXmlDeclaration ? XmlDeclState.Ignore : XmlDeclState.Auto;
290 case ConformanceLevel.Fragment:
291 xmldecl_state = XmlDeclState.Prohibit;
295 Formatting = Formatting.Indented;
296 indent_string = settings.IndentChars == null ?
297 String.Empty : settings.IndentChars;
298 if (settings.NewLineChars != null)
299 newline = settings.NewLineChars;
300 indent_attributes = settings.NewLineOnAttributes;
302 check_character_validity = settings.CheckCharacters;
303 newline_handling = settings.NewLineHandling;
307 void Initialize (TextWriter writer)
310 throw new ArgumentNullException ("writer");
311 XmlNameTable name_table = new NameTable ();
312 this.writer = writer;
313 if (writer is StreamWriter)
314 base_stream = ((StreamWriter) writer).BaseStream;
316 nsmanager = new XmlNamespaceManager (name_table);
317 newline = writer.NewLine;
320 newline_handling != NewLineHandling.None ?
321 new char [] {'&', '<', '>', '\r', '\n'} :
322 new char [] {'&', '<', '>'};
324 new char [] {'"', '&', '<', '>', '\r', '\n'};
328 // 2.0 XmlWriterSettings support
330 // As for ConformanceLevel, MS.NET is inconsistent with
331 // MSDN documentation. For example, even if ConformanceLevel
332 // is set as .Auto, multiple WriteStartDocument() calls
333 // result in an error.
334 // ms-help://MS.NETFramework.v20.en/wd_xml/html/7db8802b-53d8-4735-a637-4d2d2158d643.htm
338 // Literal Output Control
340 public Formatting Formatting {
341 get { return indent ? Formatting.Indented : Formatting.None; }
343 // Someone thinks it should be settable even
344 // after writing some content (bug #78148).
345 // I totally disagree but here is the fix.
347 //if (state != WriteState.Start)
348 // throw InvalidOperation ("Formatting must be set before it is actually used to write output.");
349 indent = (value == Formatting.Indented);
353 public int Indentation {
354 get { return indent_count; }
357 throw ArgumentError ("Indentation must be non-negative integer.");
358 indent_count = value;
359 indent_string = value == 0 ? String.Empty :
360 new string (indent_char, indent_count);
364 public char IndentChar {
365 get { return indent_char; }
368 indent_string = new string (indent_char, indent_count);
372 public char QuoteChar {
373 get { return quote_char; }
375 if (state == WriteState.Attribute)
376 throw InvalidOperation ("QuoteChar must not be changed inside attribute value.");
377 if ((value != '\'') && (value != '\"'))
378 throw ArgumentError ("Only ' and \" are allowed as an attribute quote character.");
380 escaped_attr_chars [0] = quote_char;
386 public override string XmlLang {
387 get { return open_count == 0 ? null : elements [open_count - 1].XmlLang; }
390 public override XmlSpace XmlSpace {
391 get { return open_count == 0 ? XmlSpace.None : elements [open_count - 1].XmlSpace; }
394 public override WriteState WriteState {
395 get { return state; }
398 public override string LookupPrefix (string namespaceUri)
400 if (namespaceUri == null || namespaceUri == String.Empty)
401 throw ArgumentError ("The Namespace cannot be empty.");
403 if (namespaceUri == nsmanager.DefaultNamespace)
406 string prefix = nsmanager.LookupPrefixExclusive (
407 namespaceUri, false);
409 // XmlNamespaceManager has changed to return null
410 // when NSURI not found.
411 // (Contradiction to the ECMA documentation.)
417 public Stream BaseStream {
418 get { return base_stream; }
421 public override void Close ()
424 if (state != WriteState.Error) {
426 if (state == WriteState.Attribute)
427 WriteEndAttribute ();
428 while (open_count > 0)
434 if (close_output_stream)
438 state = WriteState.Closed;
441 public override void Flush ()
447 public bool Namespaces {
448 get { return namespaces; }
450 if (state != WriteState.Start)
451 throw InvalidOperation ("This property must be set before writing output.");
458 public override void WriteStartDocument ()
460 WriteStartDocumentCore (false, false);
461 is_document_entity = true;
464 public override void WriteStartDocument (bool standalone)
466 WriteStartDocumentCore (true, standalone);
467 is_document_entity = true;
470 void WriteStartDocumentCore (bool outputStd, bool standalone)
472 if (state != WriteState.Start)
473 throw StateError ("XmlDeclaration");
475 switch (xmldecl_state) {
476 case XmlDeclState.Ignore:
478 case XmlDeclState.Prohibit:
479 throw InvalidOperation ("WriteStartDocument cannot be called when ConformanceLevel is Fragment.");
482 state = WriteState.Prolog;
484 writer.Write ("<?xml version=");
485 writer.Write (quote_char);
486 writer.Write ("1.0");
487 writer.Write (quote_char);
488 if (!ignore_encoding) {
489 writer.Write (" encoding=");
490 writer.Write (quote_char);
491 writer.Write (writer.Encoding.WebName);
492 writer.Write (quote_char);
495 writer.Write (" standalone=");
496 writer.Write (quote_char);
497 writer.Write (standalone ? "yes" : "no");
498 writer.Write (quote_char);
502 xmldecl_state = XmlDeclState.Ignore;
505 public override void WriteEndDocument ()
509 case WriteState.Error:
511 case WriteState.Closed:
512 case WriteState.Start:
513 throw StateError ("EndDocument");
516 if (state == WriteState.Attribute)
517 WriteEndAttribute ();
518 while (open_count > 0)
521 state = WriteState.Start;
522 is_document_entity = false;
525 // DocType Declaration
527 public override void WriteDocType (string name,
528 string pubid, string sysid, string subset)
531 throw ArgumentError ("name");
532 if (!XmlChar.IsName (name))
533 throw ArgumentError ("name");
535 if (node_state != XmlNodeType.None)
536 throw StateError ("DocType");
537 node_state = XmlNodeType.DocumentType;
539 if (xmldecl_state == XmlDeclState.Auto)
540 OutputAutoStartDocument ();
544 writer.Write ("<!DOCTYPE ");
547 writer.Write (" PUBLIC ");
548 writer.Write (quote_char);
549 writer.Write (pubid);
550 writer.Write (quote_char);
552 writer.Write (quote_char);
554 writer.Write (sysid);
555 writer.Write (quote_char);
557 else if (sysid != null) {
558 writer.Write (" SYSTEM ");
559 writer.Write (quote_char);
560 writer.Write (sysid);
561 writer.Write (quote_char);
564 if (subset != null) {
566 // LAMESPEC: see the top of this source.
567 writer.Write (subset);
572 state = WriteState.Prolog;
577 public override void WriteStartElement (
578 string prefix, string localName, string namespaceUri)
581 if (state == WriteState.Error || state == WriteState.Closed)
583 if (state == WriteState.Closed)
585 throw StateError ("StartTag");
586 node_state = XmlNodeType.Element;
588 bool anonPrefix = (prefix == null);
590 prefix = String.Empty;
592 // Crazy namespace check goes here.
594 // 1. if Namespaces is false, then any significant
595 // namespace indication is not allowed.
596 // 2. if Prefix is non-empty and NamespaceURI is
597 // empty, it is an error in 1.x, or it is reset to
598 // an empty string in 2.0.
599 // 3. null NamespaceURI indicates that namespace is
601 // 4. prefix must not be equivalent to "XML" in
602 // case-insensitive comparison.
603 if (!namespaces && namespaceUri != null && namespaceUri.Length > 0)
604 throw ArgumentError ("Namespace is disabled in this XmlTextWriter.");
605 if (!namespaces && prefix.Length > 0)
606 throw ArgumentError ("Namespace prefix is disabled in this XmlTextWriter.");
608 if (prefix.Length > 0 && namespaceUri == null)
609 throw ArgumentError ("Namespace URI must not be null when prefix is not an empty string.");
610 // Considering the fact that WriteStartAttribute()
611 // automatically changes argument namespaceURI, this
612 // is kind of silly implementation. See bug #77094.
614 prefix != null && prefix.Length == 3 &&
615 namespaceUri != XmlNamespace &&
616 (prefix [0] == 'x' || prefix [0] == 'X') &&
617 (prefix [1] == 'm' || prefix [1] == 'M') &&
618 (prefix [2] == 'l' || prefix [2] == 'L'))
619 throw new ArgumentException ("A prefix cannot be equivalent to \"xml\" in case-insensitive match.");
622 if (xmldecl_state == XmlDeclState.Auto)
623 OutputAutoStartDocument ();
624 if (state == WriteState.Element)
625 CloseStartElement ();
627 elements [open_count - 1].HasElements = true;
629 nsmanager.PushScope ();
631 if (namespaces && namespaceUri != null) {
632 // If namespace URI is empty, then prefix must
634 if (anonPrefix && namespaceUri.Length > 0)
635 prefix = LookupPrefix (namespaceUri);
636 if (prefix == null || namespaceUri.Length == 0)
637 prefix = String.Empty;
644 if (prefix.Length > 0) {
645 writer.Write (prefix);
648 writer.Write (localName);
650 if (elements.Length == open_count) {
651 XmlNodeInfo [] tmp = new XmlNodeInfo [open_count << 1];
652 Array.Copy (elements, tmp, open_count);
655 if (elements [open_count] == null)
656 elements [open_count] =
658 XmlNodeInfo info = elements [open_count];
659 info.Prefix = prefix;
660 info.LocalName = localName;
661 info.NS = namespaceUri;
662 info.HasSimple = false;
663 info.HasElements = false;
664 info.XmlLang = XmlLang;
665 info.XmlSpace = XmlSpace;
668 if (namespaces && namespaceUri != null) {
669 string oldns = nsmanager.LookupNamespace (prefix, false);
670 if (oldns != namespaceUri) {
671 nsmanager.AddNamespace (prefix, namespaceUri);
672 new_local_namespaces.Push (prefix);
676 state = WriteState.Element;
679 void CloseStartElement ()
681 CloseStartElementCore ();
683 if (state == WriteState.Element)
685 state = WriteState.Content;
688 void CloseStartElementCore ()
690 if (state == WriteState.Attribute)
691 WriteEndAttribute ();
693 if (new_local_namespaces.Count == 0) {
694 if (explicit_nsdecls.Count > 0)
695 explicit_nsdecls.Clear ();
699 // Missing xmlns attributes are added to
700 // explicit_nsdecls (it is cleared but this way
701 // I save another array creation).
702 int idx = explicit_nsdecls.Count;
703 while (new_local_namespaces.Count > 0) {
704 string p = (string) new_local_namespaces.Pop ();
706 for (int i = 0; i < explicit_nsdecls.Count; i++) {
707 if ((string) explicit_nsdecls [i] == p) {
714 explicit_nsdecls.Add (p);
717 for (int i = idx; i < explicit_nsdecls.Count; i++) {
718 string prefix = (string) explicit_nsdecls [i];
719 string ns = nsmanager.LookupNamespace (prefix, false);
721 continue; // superceded
722 if (prefix.Length > 0) {
723 writer.Write (" xmlns:");
724 writer.Write (prefix);
726 writer.Write (" xmlns");
729 writer.Write (quote_char);
730 WriteEscapedString (ns, true);
731 writer.Write (quote_char);
733 explicit_nsdecls.Clear ();
738 public override void WriteEndElement ()
740 WriteEndElementCore (false);
743 public override void WriteFullEndElement ()
745 WriteEndElementCore (true);
748 void WriteEndElementCore (bool full)
751 if (state == WriteState.Error || state == WriteState.Closed)
753 if (state == WriteState.Closed)
755 throw StateError ("EndElement");
757 throw InvalidOperation ("There is no more open element.");
759 bool isEmpty = state != WriteState.Content;
761 CloseStartElementCore ();
763 nsmanager.PopScope ();
765 if (state == WriteState.Element) {
769 writer.Write (" />");
772 if (full || state == WriteState.Content)
773 WriteIndentEndElement ();
775 XmlNodeInfo info = elements [--open_count];
777 if (full || state == WriteState.Content) {
779 if (info.Prefix.Length > 0) {
780 writer.Write (info.Prefix);
783 writer.Write (info.LocalName);
787 state = WriteState.Content;
789 node_state = XmlNodeType.EndElement;
794 public override void WriteStartAttribute (
795 string prefix, string localName, string namespaceUri)
797 // LAMESPEC: this violates the expected behavior of
798 // this method, as it incorrectly allows unbalanced
799 // output of attributes. Microfot changes description
800 // on its behavior at their will, regardless of
802 if (state == WriteState.Attribute)
803 WriteEndAttribute ();
805 if (state != WriteState.Element && state != WriteState.Start)
806 throw StateError ("Attribute");
808 if ((object) prefix == null)
809 prefix = String.Empty;
811 // For xmlns URI, prefix is forced to be "xmlns"
812 bool isNSDecl = false;
813 if (namespaceUri == XmlnsNamespace) {
815 if (prefix.Length == 0 && localName != "xmlns")
819 isNSDecl = (prefix == "xmlns" ||
820 localName == "xmlns" && prefix.Length == 0);
823 // MS implementation is pretty hacky here.
824 // Regardless of namespace URI it is regarded
825 // as NS URI for "xml".
827 namespaceUri = XmlNamespace;
828 // infer namespace URI.
829 else if ((object) namespaceUri == null) {
831 namespaceUri = XmlnsNamespace;
833 namespaceUri = String.Empty;
836 // It is silly design - null namespace with
837 // "xmlns" are allowed (for namespace-less
838 // output; while there is Namespaces property)
839 // On the other hand, namespace "" is not
841 if (isNSDecl && namespaceUri != XmlnsNamespace)
842 throw ArgumentError (String.Format ("The 'xmlns' attribute is bound to the reserved namespace '{0}'", XmlnsNamespace));
844 // If namespace URI is empty, then prefix
845 // must be empty as well.
846 if (prefix.Length > 0 && namespaceUri.Length == 0)
847 throw ArgumentError ("Namespace URI must not be null when prefix is not an empty string.");
849 // Dive into extremely complex procedure.
850 if (!isNSDecl && namespaceUri.Length > 0)
851 prefix = DetermineAttributePrefix (
852 prefix, localName, namespaceUri);
855 if (indent_attributes)
856 WriteIndentAttribute ();
857 else if (state != WriteState.Start)
860 if (prefix.Length > 0) {
861 writer.Write (prefix);
864 writer.Write (localName);
866 writer.Write (quote_char);
868 if (isNSDecl || prefix == "xml") {
869 if (preserver == null)
870 preserver = new StringWriter ();
872 preserver.GetStringBuilder ().Length = 0;
876 is_preserved_xmlns = false;
877 preserved_name = localName;
879 is_preserved_xmlns = true;
880 preserved_name = localName == "xmlns" ?
881 String.Empty : localName;
885 state = WriteState.Attribute;
889 // "DetermineAttributePrefix(): local mapping overwrite"
890 string DetermineAttributePrefix (
891 string prefix, string local, string ns)
894 if (prefix.Length == 0) {
895 prefix = LookupPrefix (ns);
896 if (prefix != null && prefix.Length > 0)
900 prefix = nsmanager.NameTable.Add (prefix);
901 string existing = nsmanager.LookupNamespace (prefix, true);
904 if (existing != null) {
905 // See code comment on the head of
907 nsmanager.RemoveNamespace (prefix, existing);
908 if (nsmanager.LookupNamespace (prefix, true) != existing) {
910 nsmanager.AddNamespace (prefix, existing);
916 prefix = MockupPrefix (ns, true);
917 new_local_namespaces.Push (prefix);
918 nsmanager.AddNamespace (prefix, ns);
923 string MockupPrefix (string ns, bool skipLookup)
925 string prefix = skipLookup ? null :
927 if (prefix != null && prefix.Length > 0)
929 for (int p = 1; ; p++) {
930 prefix = StringUtil.Format ("d{0}p{1}", open_count, p);
931 if (new_local_namespaces.Contains (prefix))
933 if (null != nsmanager.LookupNamespace (
934 nsmanager.NameTable.Get (prefix)))
936 nsmanager.AddNamespace (prefix, ns);
937 new_local_namespaces.Push (prefix);
942 public override void WriteEndAttribute ()
944 if (state != WriteState.Attribute)
945 throw StateError ("End of attribute");
947 if (writer == preserver) {
949 string value = preserver.ToString ();
950 if (is_preserved_xmlns) {
951 if (preserved_name.Length > 0 &&
953 throw ArgumentError ("Non-empty prefix must be mapped to non-empty namespace URI.");
954 string existing = nsmanager.LookupNamespace (preserved_name, false);
955 explicit_nsdecls.Add (preserved_name);
956 if (open_count > 0 &&
957 elements [open_count - 1].NS == String.Empty &&
958 elements [open_count - 1].Prefix == preserved_name)
960 else if (existing != value)
961 nsmanager.AddNamespace (preserved_name, value);
963 switch (preserved_name) {
966 elements [open_count - 1].XmlLang = value;
972 elements [open_count - 1].XmlSpace = XmlSpace.Default;
976 elements [open_count - 1].XmlSpace = XmlSpace.Preserve;
979 throw ArgumentError ("Invalid value for xml:space.");
984 writer.Write (value);
987 writer.Write (quote_char);
988 state = WriteState.Element;
993 public override void WriteComment (string text)
996 throw ArgumentError ("text");
998 if (text.Length > 0 && text [text.Length - 1] == '-')
999 throw ArgumentError ("An input string to WriteComment method must not end with '-'. Escape it with 'D;'.");
1000 if (StringUtil.IndexOf (text, "--") > 0)
1001 throw ArgumentError ("An XML comment cannot end with \"-\".");
1003 if (state == WriteState.Attribute || state == WriteState.Element)
1004 CloseStartElement ();
1008 ShiftStateTopLevel ("Comment", false, false, false);
1010 writer.Write ("<!--");
1011 writer.Write (text);
1012 writer.Write ("-->");
1015 // LAMESPEC: see comments on the top of this source.
1016 public override void WriteProcessingInstruction (string name, string text)
1019 throw ArgumentError ("name");
1021 throw ArgumentError ("text");
1025 if (!XmlChar.IsName (name))
1026 throw ArgumentError ("A processing instruction name must be a valid XML name.");
1028 if (StringUtil.IndexOf (text, "?>") > 0)
1029 throw ArgumentError ("Processing instruction cannot contain \"?>\" as its value.");
1031 ShiftStateTopLevel ("ProcessingInstruction", false, name == "xml", false);
1033 writer.Write ("<?");
1034 writer.Write (name);
1036 writer.Write (text);
1037 writer.Write ("?>");
1039 if (state == WriteState.Start)
1040 state = WriteState.Prolog;
1045 public override void WriteWhitespace (string text)
1048 throw ArgumentError ("text");
1050 // huh? Shouldn't it accept an empty string???
1051 if (text.Length == 0 ||
1052 XmlChar.IndexOfNonWhitespace (text) >= 0)
1053 throw ArgumentError ("WriteWhitespace method accepts only whitespaces.");
1055 ShiftStateTopLevel ("Whitespace", true, false, true);
1057 writer.Write (text);
1060 public override void WriteCData (string text)
1063 text = String.Empty;
1064 ShiftStateContent ("CData", false);
1066 if (StringUtil.IndexOf (text, "]]>") >= 0)
1067 throw ArgumentError ("CDATA section must not contain ']]>'.");
1068 writer.Write ("<![CDATA[");
1069 WriteCheckedString (text);
1070 writer.Write ("]]>");
1073 public override void WriteString (string text)
1075 if (text == null || text.Length == 0)
1076 return; // do nothing, including state transition.
1077 ShiftStateContent ("Text", true);
1079 WriteEscapedString (text, state == WriteState.Attribute);
1082 public override void WriteRaw (string raw)
1085 return; // do nothing, including state transition.
1089 // LAMESPEC: It rejects XMLDecl while it allows
1090 // DocType which could consist of non well-formed XML.
1091 ShiftStateTopLevel ("Raw string", true, true, true);
1096 public override void WriteCharEntity (char ch)
1098 WriteCharacterEntity (ch, '\0', false);
1101 public override void WriteSurrogateCharEntity (char low, char high)
1103 WriteCharacterEntity (low, high, true);
1106 void WriteCharacterEntity (char ch, char high, bool surrogate)
1109 ('\uD800' > high || high > '\uDC00' ||
1110 '\uDC00' > ch || ch > '\uDFFF'))
1111 throw ArgumentError (String.Format ("Invalid surrogate pair was found. Low: &#x{0:X}; High: &#x{0:X};", (int) ch, (int) high));
1112 else if (check_character_validity && XmlChar.IsInvalid (ch))
1113 throw ArgumentError (String.Format ("Invalid character &#x{0:X};", (int) ch));
1115 ShiftStateContent ("Character", true);
1117 int v = surrogate ? (high - 0xD800) * 0x400 + ch - 0xDC00 + 0x10000 : (int) ch;
1118 writer.Write ("&#x");
1119 writer.Write (v.ToString ("X", CultureInfo.InvariantCulture));
1123 public override void WriteEntityRef (string name)
1126 throw ArgumentError ("name");
1127 if (!XmlChar.IsName (name))
1128 throw ArgumentError ("Argument name must be a valid XML name.");
1130 ShiftStateContent ("Entity reference", true);
1133 writer.Write (name);
1139 public override void WriteName (string name)
1142 throw ArgumentError ("name");
1143 if (!XmlChar.IsName (name))
1144 throw ArgumentError ("Not a valid name string.");
1148 public override void WriteNmToken (string nmtoken)
1150 if (nmtoken == null)
1151 throw ArgumentError ("nmtoken");
1152 if (!XmlChar.IsNmToken (nmtoken))
1153 throw ArgumentError ("Not a valid NMTOKEN string.");
1154 WriteString (nmtoken);
1157 public override void WriteQualifiedName (
1158 string localName, string ns)
1160 if (localName == null)
1161 throw ArgumentError ("localName");
1165 if (ns == XmlnsNamespace)
1166 throw ArgumentError ("Prefix 'xmlns' is reserved and cannot be overriden.");
1167 if (!XmlChar.IsNCName (localName))
1168 throw ArgumentError ("localName must be a valid NCName.");
1170 ShiftStateContent ("QName", true);
1172 string prefix = ns.Length > 0 ? LookupPrefix (ns) : String.Empty;
1173 if (prefix == null) {
1174 if (state == WriteState.Attribute)
1175 prefix = MockupPrefix (ns, false);
1177 throw ArgumentError (String.Format ("Namespace '{0}' is not declared.", ns));
1180 if (prefix != String.Empty) {
1181 writer.Write (prefix);
1184 writer.Write (localName);
1189 void CheckChunkRange (Array buffer, int index, int count)
1192 throw new ArgumentNullException ("buffer");
1193 if (index < 0 || buffer.Length < index)
1194 throw ArgumentOutOfRangeError ("index");
1195 if (count < 0 || buffer.Length < index + count)
1196 throw ArgumentOutOfRangeError ("count");
1199 public override void WriteBase64 (byte [] buffer, int index, int count)
1201 CheckChunkRange (buffer, index, count);
1203 WriteString (Convert.ToBase64String (buffer, index, count));
1206 public override void WriteBinHex (byte [] buffer, int index, int count)
1208 CheckChunkRange (buffer, index, count);
1210 ShiftStateContent ("BinHex", true);
1212 XmlConvert.WriteBinHex (buffer, index, count, writer);
1215 public override void WriteChars (char [] buffer, int index, int count)
1217 CheckChunkRange (buffer, index, count);
1219 ShiftStateContent ("Chars", true);
1221 WriteEscapedBuffer (buffer, index, count,
1222 state == WriteState.Attribute);
1225 public override void WriteRaw (char [] buffer, int index, int count)
1227 CheckChunkRange (buffer, index, count);
1229 ShiftStateContent ("Raw text", false);
1231 writer.Write (buffer, index, count);
1238 WriteIndentCore (0, false);
1241 void WriteIndentEndElement ()
1243 WriteIndentCore (-1, false);
1246 void WriteIndentAttribute ()
1248 if (!WriteIndentCore (0, true))
1249 writer.Write (' '); // space is required instead.
1252 bool WriteIndentCore (int nestFix, bool attribute)
1256 for (int i = open_count - 1; i >= 0; i--)
1257 if (!attribute && elements [i].HasSimple)
1260 if (state != WriteState.Start)
1261 writer.Write (newline);
1262 for (int i = 0; i < open_count + nestFix; i++)
1263 writer.Write (indent_string);
1267 void OutputAutoStartDocument ()
1269 if (state != WriteState.Start)
1271 WriteStartDocumentCore (false, false);
1274 void ShiftStateTopLevel (string occured, bool allowAttribute, bool dontCheckXmlDecl, bool isCharacter)
1278 case WriteState.Error:
1280 case WriteState.Closed:
1281 throw StateError (occured);
1282 case WriteState.Start:
1284 CheckMixedContentState ();
1285 if (xmldecl_state == XmlDeclState.Auto && !dontCheckXmlDecl)
1286 OutputAutoStartDocument ();
1287 state = WriteState.Prolog;
1289 case WriteState.Attribute:
1292 goto case WriteState.Closed;
1293 case WriteState.Element:
1295 CheckMixedContentState ();
1296 CloseStartElement ();
1298 case WriteState.Content:
1300 CheckMixedContentState ();
1306 void CheckMixedContentState ()
1308 // if (open_count > 0 &&
1309 // state != WriteState.Attribute)
1310 // elements [open_count - 1].HasSimple = true;
1312 elements [open_count - 1].HasSimple = true;
1315 void ShiftStateContent (string occured, bool allowAttribute)
1319 case WriteState.Error:
1321 case WriteState.Closed:
1322 throw StateError (occured);
1323 case WriteState.Prolog:
1324 case WriteState.Start:
1325 if (!allow_doc_fragment || is_document_entity)
1326 goto case WriteState.Closed;
1327 if (xmldecl_state == XmlDeclState.Auto)
1328 OutputAutoStartDocument ();
1329 CheckMixedContentState ();
1330 state = WriteState.Content;
1332 case WriteState.Attribute:
1335 goto case WriteState.Closed;
1336 case WriteState.Element:
1337 CloseStartElement ();
1338 CheckMixedContentState ();
1340 case WriteState.Content:
1341 CheckMixedContentState ();
1346 void WriteEscapedString (string text, bool isAttribute)
1348 char [] escaped = isAttribute ?
1349 escaped_attr_chars : escaped_text_chars;
1351 int idx = text.IndexOfAny (escaped);
1353 char [] arr = text.ToCharArray ();
1354 WriteCheckedBuffer (arr, 0, idx);
1355 WriteEscapedBuffer (
1356 arr, idx, arr.Length - idx, isAttribute);
1358 WriteCheckedString (text);
1362 void WriteCheckedString (string s)
1364 int i = XmlChar.IndexOfInvalid (s, true);
1366 char [] arr = s.ToCharArray ();
1367 writer.Write (arr, 0, i);
1368 WriteCheckedBuffer (arr, i, arr.Length - i);
1370 // no invalid character.
1375 void WriteCheckedBuffer (char [] text, int idx, int length)
1378 int end = idx + length;
1379 while ((idx = XmlChar.IndexOfInvalid (text, start, length, true)) >= 0) {
1380 if (check_character_validity) // actually this is one time pass.
1381 throw ArgumentError (String.Format ("Input contains invalid character at {0} : &#x{1:X};", idx, (int) text [idx]));
1383 writer.Write (text, start, idx - start);
1384 writer.Write ("&#x");
1385 writer.Write (((int) text [idx]).ToString (
1387 CultureInfo.InvariantCulture));
1389 length -= idx - start + 1;
1393 writer.Write (text, start, end - start);
1396 void WriteEscapedBuffer (char [] text, int index, int length,
1400 int end = index + length;
1401 for (int i = start; i < end; i++) {
1409 WriteCheckedBuffer (text, start, i - start);
1412 case '&': writer.Write ("amp;"); break;
1413 case '<': writer.Write ("lt;"); break;
1414 case '>': writer.Write ("gt;"); break;
1415 case '\'': writer.Write ("apos;"); break;
1416 case '"': writer.Write ("quot;"); break;
1421 if (isAttribute && text [i] == quote_char)
1425 if (i + 1 < end && text [i] == '\n')
1430 WriteCheckedBuffer (text, start, i - start);
1432 writer.Write (text [i] == '\r' ?
1436 switch (newline_handling) {
1437 case NewLineHandling.Entitize:
1438 writer.Write (text [i] == '\r' ?
1441 case NewLineHandling.Replace:
1442 writer.Write (newline);
1445 writer.Write (text [i]);
1453 WriteCheckedBuffer (text, start, end - start);
1458 Exception ArgumentOutOfRangeError (string name)
1461 state = WriteState.Error;
1463 return new ArgumentOutOfRangeException (name);
1466 Exception ArgumentError (string msg)
1469 state = WriteState.Error;
1471 return new ArgumentException (msg);
1474 Exception InvalidOperation (string msg)
1477 state = WriteState.Error;
1479 return new InvalidOperationException (msg);
1482 Exception StateError (string occured)
1484 return InvalidOperation (String.Format ("This XmlWriter does not accept {0} at this state {1}.", occured, state));