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).
144 public class XmlTextWriter : XmlWriter
146 // Static/constant members.
148 const string XmlNamespace = "http://www.w3.org/XML/1998/namespace";
149 const string XmlnsNamespace = "http://www.w3.org/2000/xmlns/";
151 static readonly Encoding unmarked_utf8encoding =
152 new UTF8Encoding (false, false);
153 static char [] escaped_text_chars;
154 static char [] escaped_attr_chars;
160 public string Prefix;
161 public string LocalName;
163 public bool HasSimple;
164 public bool HasElements;
165 public string XmlLang;
166 public XmlSpace XmlSpace;
169 internal class StringUtil
171 static CultureInfo cul = CultureInfo.InvariantCulture;
172 static CompareInfo cmp =
173 CultureInfo.InvariantCulture.CompareInfo;
175 public static int IndexOf (string src, string target)
177 return cmp.IndexOf (src, target);
180 public static int Compare (string s1, string s2)
182 return cmp.Compare (s1, s2);
185 public static string Format (
186 string format, params object [] args)
188 return String.Format (cul, format, args);
202 TextWriter source; // the input TextWriter to .ctor().
204 // It is used for storing xml:space, xml:lang and xmlns values.
205 StringWriter preserver;
206 string preserved_name;
207 bool is_preserved_xmlns;
209 bool allow_doc_fragment;
210 bool close_output_stream = true;
211 bool ignore_encoding;
212 bool namespaces = true;
213 XmlDeclState xmldecl_state = XmlDeclState.Allow;
215 bool check_character_validity;
216 NewLineHandling newline_handling = NewLineHandling.Replace;
218 bool is_document_entity;
219 WriteState state = WriteState.Start;
220 XmlNodeType node_state = XmlNodeType.None;
221 XmlNamespaceManager nsmanager;
223 bool top_level_space_ignored;
224 XmlNodeInfo [] elements = new XmlNodeInfo [10];
225 Stack new_local_namespaces = new Stack ();
226 ArrayList explicit_nsdecls = new ArrayList ();
227 NamespaceHandling namespace_handling;
230 int indent_count = 2;
231 char indent_char = ' ';
232 string indent_string = " ";
234 bool indent_attributes;
236 char quote_char = '"';
242 public XmlTextWriter (string filename, Encoding encoding)
243 : this (new FileStream (filename, FileMode.Create, FileAccess.Write, FileShare.None), encoding)
247 public XmlTextWriter (Stream w, Encoding encoding)
248 : this (new StreamWriter (w,
249 encoding == null ? unmarked_utf8encoding : encoding))
251 ignore_encoding = (encoding == null);
253 allow_doc_fragment = true;
256 public XmlTextWriter (TextWriter w)
259 throw new ArgumentNullException ("writer");
260 ignore_encoding = (w.Encoding == null);
262 allow_doc_fragment = true;
265 internal XmlTextWriter (
266 TextWriter writer, XmlWriterSettings settings, bool closeOutput)
270 if (settings == null)
271 settings = new XmlWriterSettings ();
273 newline_handling = settings.NewLineHandling;
276 close_output_stream = closeOutput;
278 settings.ConformanceLevel != ConformanceLevel.Document;
279 switch (settings.ConformanceLevel) {
280 case ConformanceLevel.Auto:
281 xmldecl_state = settings.OmitXmlDeclaration ? XmlDeclState.Ignore : XmlDeclState.Allow;
283 case ConformanceLevel.Document:
285 // On MSDN, XmlWriterSettings.OmitXmlDeclaration is documented as:
286 // "The XML declaration is always written if
287 // ConformanceLevel is set to Document, even
288 // if OmitXmlDeclaration is set to true. "
289 // but it is incorrect. It does consider
290 // OmitXmlDeclaration property.
291 xmldecl_state = settings.OmitXmlDeclaration ? XmlDeclState.Ignore : XmlDeclState.Auto;
293 case ConformanceLevel.Fragment:
294 xmldecl_state = XmlDeclState.Prohibit;
298 Formatting = Formatting.Indented;
299 indent_string = settings.IndentChars == null ?
300 String.Empty : settings.IndentChars;
301 if (settings.NewLineChars != null)
302 newline = settings.NewLineChars;
303 indent_attributes = settings.NewLineOnAttributes;
305 check_character_validity = settings.CheckCharacters;
306 namespace_handling = settings.NamespaceHandling;
309 void Initialize (TextWriter writer)
312 throw new ArgumentNullException ("writer");
313 XmlNameTable name_table = new NameTable ();
314 this.writer = writer;
315 if (writer is StreamWriter)
316 base_stream = ((StreamWriter) writer).BaseStream;
318 nsmanager = new XmlNamespaceManager (name_table);
319 newline = writer.NewLine;
322 newline_handling != NewLineHandling.None ?
323 new char [] {'&', '<', '>', '\r', '\n'} :
324 new char [] {'&', '<', '>'};
326 newline_handling != NewLineHandling.None ?
327 v2 ? new char [] {'"', '&', '<', '>', '\r', '\n', '\t'} : new char [] {'"', '&', '<', '>', '\r', '\n' } :
328 new char [] {'"', '&', '<', '>' };
331 // 2.0 XmlWriterSettings support
333 // As for ConformanceLevel, MS.NET is inconsistent with
334 // MSDN documentation. For example, even if ConformanceLevel
335 // is set as .Auto, multiple WriteStartDocument() calls
336 // result in an error.
337 // ms-help://MS.NETFramework.v20.en/wd_xml/html/7db8802b-53d8-4735-a637-4d2d2158d643.htm
339 // Literal Output Control
341 public Formatting Formatting {
342 get { return indent ? Formatting.Indented : Formatting.None; }
344 // Someone thinks it should be settable even
345 // after writing some content (bug #78148).
346 // I totally disagree but here is the fix.
348 //if (state != WriteState.Start)
349 // throw InvalidOperation ("Formatting must be set before it is actually used to write output.");
350 indent = (value == Formatting.Indented);
354 public int Indentation {
355 get { return indent_count; }
358 throw ArgumentError ("Indentation must be non-negative integer.");
359 indent_count = value;
360 indent_string = value == 0 ? String.Empty :
361 new string (indent_char, indent_count);
365 public char IndentChar {
366 get { return indent_char; }
369 indent_string = new string (indent_char, indent_count);
373 public char QuoteChar {
374 get { return quote_char; }
376 if (state == WriteState.Attribute)
377 throw InvalidOperation ("QuoteChar must not be changed inside attribute value.");
378 if ((value != '\'') && (value != '\"'))
379 throw ArgumentError ("Only ' and \" are allowed as an attribute quote character.");
381 escaped_attr_chars [0] = quote_char;
387 public override string XmlLang {
388 get { return open_count == 0 ? null : elements [open_count - 1].XmlLang; }
391 public override XmlSpace XmlSpace {
392 get { return open_count == 0 ? XmlSpace.None : elements [open_count - 1].XmlSpace; }
395 public override WriteState WriteState {
396 get { return state; }
399 public override string LookupPrefix (string ns)
401 if (ns == null || ns == String.Empty)
402 throw ArgumentError ("The Namespace cannot be empty.");
404 if (ns == nsmanager.DefaultNamespace)
407 string prefix = nsmanager.LookupPrefixExclusive (
410 // XmlNamespaceManager has changed to return null
411 // when NSURI not found.
412 // (Contradiction to the ECMA documentation.)
418 public Stream BaseStream {
419 get { return base_stream; }
422 public override void Close ()
424 if (state != WriteState.Error) {
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 switch (xmldecl_state) {
472 case XmlDeclState.Ignore:
474 case XmlDeclState.Prohibit:
475 throw InvalidOperation ("WriteStartDocument cannot be called when ConformanceLevel is Fragment.");
478 state = WriteState.Prolog;
480 writer.Write ("<?xml version=");
481 writer.Write (quote_char);
482 writer.Write ("1.0");
483 writer.Write (quote_char);
484 if (!ignore_encoding) {
485 writer.Write (" encoding=");
486 writer.Write (quote_char);
487 writer.Write (writer.Encoding.WebName);
488 writer.Write (quote_char);
491 writer.Write (" standalone=");
492 writer.Write (quote_char);
493 writer.Write (standalone ? "yes" : "no");
494 writer.Write (quote_char);
498 xmldecl_state = XmlDeclState.Ignore;
501 public override void WriteEndDocument ()
504 case WriteState.Error:
505 case WriteState.Closed:
506 case WriteState.Start:
507 throw StateError ("EndDocument");
510 if (state == WriteState.Attribute)
511 WriteEndAttribute ();
512 while (open_count > 0)
515 state = WriteState.Start;
516 is_document_entity = false;
519 // DocType Declaration
521 public override void WriteDocType (string name,
522 string pubid, string sysid, string subset)
525 throw ArgumentError ("name");
526 if (!XmlChar.IsName (name))
527 throw ArgumentError ("name");
529 if (node_state != XmlNodeType.None)
530 throw StateError ("DocType");
531 node_state = XmlNodeType.DocumentType;
533 if (xmldecl_state == XmlDeclState.Auto)
534 OutputAutoStartDocument ();
538 writer.Write ("<!DOCTYPE ");
541 writer.Write (" PUBLIC ");
542 writer.Write (quote_char);
543 writer.Write (pubid);
544 writer.Write (quote_char);
546 writer.Write (quote_char);
548 writer.Write (sysid);
549 writer.Write (quote_char);
551 else if (sysid != null) {
552 writer.Write (" SYSTEM ");
553 writer.Write (quote_char);
554 writer.Write (sysid);
555 writer.Write (quote_char);
558 if (subset != null) {
560 // LAMESPEC: see the top of this source.
561 writer.Write (subset);
566 state = WriteState.Prolog;
571 public override void WriteStartElement (
572 string prefix, string localName, string ns)
574 if (state == WriteState.Error || 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 && ns != null && ns.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 namespace URI is empty, then either prefix
599 // must be empty as well, or there is an
600 // existing namespace mapping for the prefix.
601 if (prefix.Length > 0 && ns == null) {
602 ns = nsmanager.LookupNamespace (prefix, false);
603 if (ns == null || ns.Length == 0)
604 throw ArgumentError ("Namespace URI must not be null when prefix is not an empty string.");
606 // Considering the fact that WriteStartAttribute()
607 // automatically changes argument namespaceURI, this
608 // is kind of silly implementation. See bug #77094.
610 prefix != null && prefix.Length == 3 &&
611 ns != XmlNamespace &&
612 (prefix [0] == 'x' || prefix [0] == 'X') &&
613 (prefix [1] == 'm' || prefix [1] == 'M') &&
614 (prefix [2] == 'l' || prefix [2] == 'L'))
615 throw new ArgumentException ("A prefix cannot be equivalent to \"xml\" in case-insensitive match.");
618 if (xmldecl_state == XmlDeclState.Auto)
619 OutputAutoStartDocument ();
620 if (state == WriteState.Element)
621 CloseStartElement ();
623 elements [open_count - 1].HasElements = true;
625 nsmanager.PushScope ();
627 if (namespaces && ns != null) {
628 // If namespace URI is empty, then prefix must
630 if (anonPrefix && ns.Length > 0)
631 prefix = LookupPrefix (ns);
632 if (prefix == null || ns.Length == 0)
633 prefix = String.Empty;
640 if (prefix.Length > 0) {
641 writer.Write (prefix);
644 writer.Write (localName);
646 if (elements.Length == open_count) {
647 XmlNodeInfo [] tmp = new XmlNodeInfo [open_count << 1];
648 Array.Copy (elements, tmp, open_count);
651 if (elements [open_count] == null)
652 elements [open_count] =
654 XmlNodeInfo info = elements [open_count];
655 info.Prefix = prefix;
656 info.LocalName = localName;
658 info.HasSimple = false;
659 info.HasElements = false;
660 info.XmlLang = XmlLang;
661 info.XmlSpace = XmlSpace;
664 if (namespaces && ns != null) {
665 string oldns = nsmanager.LookupNamespace (prefix, false);
667 nsmanager.AddNamespace (prefix, ns);
668 new_local_namespaces.Push (prefix);
672 state = WriteState.Element;
675 void CloseStartElement ()
677 CloseStartElementCore ();
679 if (state == WriteState.Element)
681 state = WriteState.Content;
684 void CloseStartElementCore ()
686 if (state == WriteState.Attribute)
687 WriteEndAttribute ();
689 if (new_local_namespaces.Count == 0) {
690 if (explicit_nsdecls.Count > 0)
691 explicit_nsdecls.Clear ();
695 // Missing xmlns attributes are added to
696 // explicit_nsdecls (it is cleared but this way
697 // I save another array creation).
698 int idx = explicit_nsdecls.Count;
699 while (new_local_namespaces.Count > 0) {
700 string p = (string) new_local_namespaces.Pop ();
702 for (int i = 0; i < explicit_nsdecls.Count; i++) {
703 if ((string) explicit_nsdecls [i] == p) {
710 explicit_nsdecls.Add (p);
713 for (int i = idx; i < explicit_nsdecls.Count; i++) {
714 string prefix = (string) explicit_nsdecls [i];
715 string ns = nsmanager.LookupNamespace (prefix, false);
717 continue; // superceded
718 if (prefix.Length > 0) {
719 writer.Write (" xmlns:");
720 writer.Write (prefix);
722 writer.Write (" xmlns");
725 writer.Write (quote_char);
726 WriteEscapedString (ns, true);
727 writer.Write (quote_char);
729 explicit_nsdecls.Clear ();
734 public override void WriteEndElement ()
736 WriteEndElementCore (false);
739 public override void WriteFullEndElement ()
741 WriteEndElementCore (true);
744 void WriteEndElementCore (bool full)
746 if (state == WriteState.Error || state == WriteState.Closed)
747 throw StateError ("EndElement");
749 throw InvalidOperation ("There is no more open element.");
751 // bool isEmpty = state != WriteState.Content;
753 CloseStartElementCore ();
755 nsmanager.PopScope ();
757 if (state == WriteState.Element) {
761 writer.Write (" />");
764 if (full || state == WriteState.Content)
765 WriteIndentEndElement ();
767 XmlNodeInfo info = elements [--open_count];
769 if (full || state == WriteState.Content) {
771 if (info.Prefix.Length > 0) {
772 writer.Write (info.Prefix);
775 writer.Write (info.LocalName);
779 state = WriteState.Content;
781 node_state = XmlNodeType.EndElement;
786 public override void WriteStartAttribute (
787 string prefix, string localName, string ns)
789 // LAMESPEC: this violates the expected behavior of
790 // this method, as it incorrectly allows unbalanced
791 // output of attributes. Microfot changes description
792 // on its behavior at their will, regardless of
794 if (state == WriteState.Attribute)
795 WriteEndAttribute ();
797 if (state != WriteState.Element && state != WriteState.Start)
798 throw StateError ("Attribute");
800 if ((object) prefix == null)
801 prefix = String.Empty;
803 // For xmlns URI, prefix is forced to be "xmlns"
804 bool isNSDecl = false;
805 if (ns == XmlnsNamespace) {
807 if (prefix.Length == 0 && localName != "xmlns")
811 isNSDecl = (prefix == "xmlns" ||
812 localName == "xmlns" && prefix.Length == 0);
815 // MS implementation is pretty hacky here.
816 // Regardless of namespace URI it is regarded
817 // as NS URI for "xml".
820 // infer namespace URI.
821 else if ((object) ns == null || (v2 && ns.Length == 0)) {
828 // It is silly design - null namespace with
829 // "xmlns" are allowed (for namespace-less
830 // output; while there is Namespaces property)
831 // On the other hand, namespace "" is not
833 if (isNSDecl && ns != XmlnsNamespace)
834 throw ArgumentError (String.Format ("The 'xmlns' attribute is bound to the reserved namespace '{0}'", XmlnsNamespace));
836 // If namespace URI is empty, then either prefix
837 // must be empty as well, or there is an
838 // existing namespace mapping for the prefix.
839 if (prefix.Length > 0 && ns.Length == 0) {
840 ns = nsmanager.LookupNamespace (prefix, false);
841 if (ns == null || ns.Length == 0)
842 throw ArgumentError ("Namespace URI must not be null when prefix is not an empty string.");
845 // Dive into extremely complex procedure.
846 if (!isNSDecl && ns.Length > 0)
847 prefix = DetermineAttributePrefix (
848 prefix, localName, ns);
851 if (indent_attributes)
852 WriteIndentAttribute ();
853 else if (state != WriteState.Start)
856 if (prefix.Length > 0) {
857 writer.Write (prefix);
860 writer.Write (localName);
862 writer.Write (quote_char);
864 if (isNSDecl || prefix == "xml") {
865 if (preserver == null)
866 preserver = new StringWriter ();
868 preserver.GetStringBuilder ().Length = 0;
872 is_preserved_xmlns = false;
873 preserved_name = localName;
875 is_preserved_xmlns = true;
876 preserved_name = localName == "xmlns" ?
877 String.Empty : localName;
881 state = WriteState.Attribute;
885 // "DetermineAttributePrefix(): local mapping overwrite"
886 string DetermineAttributePrefix (
887 string prefix, string local, string ns)
890 if (prefix.Length == 0) {
891 prefix = LookupPrefix (ns);
892 if (prefix != null && prefix.Length > 0)
896 prefix = nsmanager.NameTable.Add (prefix);
897 string existing = nsmanager.LookupNamespace (prefix, true);
900 if (existing != null) {
901 // See code comment on the head of
903 nsmanager.RemoveNamespace (prefix, existing);
904 if (nsmanager.LookupNamespace (prefix, true) != existing) {
906 nsmanager.AddNamespace (prefix, existing);
912 prefix = MockupPrefix (ns, true);
913 new_local_namespaces.Push (prefix);
914 nsmanager.AddNamespace (prefix, ns);
919 string MockupPrefix (string ns, bool skipLookup)
921 string prefix = skipLookup ? null :
923 if (prefix != null && prefix.Length > 0)
925 for (int p = 1; ; p++) {
926 prefix = StringUtil.Format ("d{0}p{1}", open_count, p);
927 if (new_local_namespaces.Contains (prefix))
929 if (null != nsmanager.LookupNamespace (
930 nsmanager.NameTable.Get (prefix)))
932 nsmanager.AddNamespace (prefix, ns);
933 new_local_namespaces.Push (prefix);
938 public override void WriteEndAttribute ()
940 if (state != WriteState.Attribute)
941 throw StateError ("End of attribute");
943 if (writer == preserver) {
945 string value = preserver.ToString ();
946 if (is_preserved_xmlns) {
947 if (preserved_name.Length > 0 &&
949 throw ArgumentError ("Non-empty prefix must be mapped to non-empty namespace URI.");
950 string existing = nsmanager.LookupNamespace (preserved_name, false);
952 // consider OmitDuplicates here.
953 if ((namespace_handling & NamespaceHandling.OmitDuplicates) == 0 || existing != value)
954 explicit_nsdecls.Add (preserved_name);
956 if (open_count > 0) {
959 elements [open_count - 1].Prefix == preserved_name &&
960 elements [open_count - 1].NS != value)
961 throw new XmlException (String.Format ("Cannot redefine the namespace for prefix '{0}' used at current element", preserved_name));
963 if (elements [open_count - 1].NS == String.Empty &&
964 elements [open_count - 1].Prefix == preserved_name)
966 else if (existing != value)
967 nsmanager.AddNamespace (preserved_name, value);
970 switch (preserved_name) {
973 elements [open_count - 1].XmlLang = value;
979 elements [open_count - 1].XmlSpace = XmlSpace.Default;
983 elements [open_count - 1].XmlSpace = XmlSpace.Preserve;
986 throw ArgumentError ("Invalid value for xml:space.");
991 writer.Write (value);
994 writer.Write (quote_char);
995 state = WriteState.Element;
1000 public override void WriteComment (string text)
1003 throw ArgumentError ("text");
1005 if (text.Length > 0 && text [text.Length - 1] == '-')
1006 throw ArgumentError ("An input string to WriteComment method must not end with '-'. Escape it with 'D;'.");
1007 if (StringUtil.IndexOf (text, "--") > 0)
1008 throw ArgumentError ("An XML comment cannot end with \"-\".");
1010 if (state == WriteState.Attribute || state == WriteState.Element)
1011 CloseStartElement ();
1015 ShiftStateTopLevel ("Comment", false, false, false);
1017 writer.Write ("<!--");
1018 writer.Write (text);
1019 writer.Write ("-->");
1022 // LAMESPEC: see comments on the top of this source.
1023 public override void WriteProcessingInstruction (string name, string text)
1026 throw ArgumentError ("name");
1028 throw ArgumentError ("text");
1032 if (!XmlChar.IsName (name))
1033 throw ArgumentError ("A processing instruction name must be a valid XML name.");
1035 if (StringUtil.IndexOf (text, "?>") > 0)
1036 throw ArgumentError ("Processing instruction cannot contain \"?>\" as its value.");
1038 ShiftStateTopLevel ("ProcessingInstruction", false, name == "xml", false);
1040 writer.Write ("<?");
1041 writer.Write (name);
1043 writer.Write (text);
1044 writer.Write ("?>");
1046 if (state == WriteState.Start)
1047 state = WriteState.Prolog;
1052 public override void WriteWhitespace (string ws)
1055 throw ArgumentError ("text");
1057 // huh? Shouldn't it accept an empty string???
1058 if (ws.Length == 0 ||
1059 XmlChar.IndexOfNonWhitespace (ws) >= 0)
1060 throw ArgumentError ("WriteWhitespace method accepts only whitespaces.");
1062 bool pastTopLevelWSIgnored = top_level_space_ignored;
1063 ShiftStateTopLevel ("Whitespace", true, false, true);
1064 if (!indent || WriteState != WriteState.Prolog || pastTopLevelWSIgnored)
1066 top_level_space_ignored = true;
1069 public override void WriteCData (string text)
1072 text = String.Empty;
1073 ShiftStateContent ("CData", false);
1075 if (StringUtil.IndexOf (text, "]]>") >= 0)
1076 throw ArgumentError ("CDATA section must not contain ']]>'.");
1077 writer.Write ("<![CDATA[");
1078 WriteCheckedString (text);
1079 writer.Write ("]]>");
1082 public override void WriteString (string text)
1084 if (text == null || (text.Length == 0 && !v2))
1085 return; // do nothing, including state transition.
1086 ShiftStateContent ("Text", true);
1088 WriteEscapedString (text, state == WriteState.Attribute);
1091 public override void WriteRaw (string data)
1094 return; // do nothing, including state transition.
1098 // LAMESPEC: It rejects XMLDecl while it allows
1099 // DocType which could consist of non well-formed XML.
1100 ShiftStateTopLevel ("Raw string", true, true, true);
1102 writer.Write (data);
1105 public override void WriteCharEntity (char ch)
1107 WriteCharacterEntity (ch, '\0', false);
1110 public override void WriteSurrogateCharEntity (char lowChar, char highChar)
1112 WriteCharacterEntity (lowChar, highChar, true);
1115 void WriteCharacterEntity (char ch, char high, bool surrogate)
1118 ('\uD800' > high || high > '\uDC00' ||
1119 '\uDC00' > ch || ch > '\uDFFF'))
1120 throw ArgumentError (String.Format ("Invalid surrogate pair was found. Low: &#x{0:X}; High: &#x{0:X};", (int) ch, (int) high));
1121 else if (check_character_validity && XmlChar.IsInvalid (ch))
1122 throw ArgumentError (String.Format ("Invalid character &#x{0:X};", (int) ch));
1124 ShiftStateContent ("Character", true);
1126 int v = surrogate ? (high - 0xD800) * 0x400 + ch - 0xDC00 + 0x10000 : (int) ch;
1127 writer.Write ("&#x");
1128 writer.Write (v.ToString ("X", CultureInfo.InvariantCulture));
1132 public override void WriteEntityRef (string name)
1135 throw ArgumentError ("name");
1136 if (!XmlChar.IsName (name))
1137 throw ArgumentError ("Argument name must be a valid XML name.");
1139 ShiftStateContent ("Entity reference", true);
1142 writer.Write (name);
1148 public override void WriteName (string name)
1151 throw ArgumentError ("name");
1152 if (!XmlChar.IsName (name))
1153 throw ArgumentError ("Not a valid name string.");
1157 public override void WriteNmToken (string name)
1160 throw ArgumentError ("nmtoken");
1161 if (!XmlChar.IsNmToken (name))
1162 throw ArgumentError ("Not a valid NMTOKEN string.");
1166 public override void WriteQualifiedName (
1167 string localName, string ns)
1169 if (localName == null)
1170 throw ArgumentError ("localName");
1174 if (ns == XmlnsNamespace)
1175 throw ArgumentError ("Prefix 'xmlns' is reserved and cannot be overriden.");
1176 if (!XmlChar.IsNCName (localName))
1177 throw ArgumentError ("localName must be a valid NCName.");
1179 ShiftStateContent ("QName", true);
1181 string prefix = ns.Length > 0 ? LookupPrefix (ns) : String.Empty;
1182 if (prefix == null) {
1183 if (state == WriteState.Attribute)
1184 prefix = MockupPrefix (ns, false);
1186 throw ArgumentError (String.Format ("Namespace '{0}' is not declared.", ns));
1189 if (prefix != String.Empty) {
1190 writer.Write (prefix);
1193 writer.Write (localName);
1198 void CheckChunkRange (Array buffer, int index, int count)
1201 throw new ArgumentNullException ("buffer");
1202 if (index < 0 || buffer.Length < index)
1203 throw ArgumentOutOfRangeError ("index");
1204 if (count < 0 || buffer.Length < index + count)
1205 throw ArgumentOutOfRangeError ("count");
1208 public override void WriteBase64 (byte [] buffer, int index, int count)
1210 CheckChunkRange (buffer, index, count);
1212 WriteString (Convert.ToBase64String (buffer, index, count));
1215 public override void WriteBinHex (byte [] buffer, int index, int count)
1217 CheckChunkRange (buffer, index, count);
1219 ShiftStateContent ("BinHex", true);
1221 XmlConvert.WriteBinHex (buffer, index, count, writer);
1224 public override void WriteChars (char [] buffer, int index, int count)
1226 CheckChunkRange (buffer, index, count);
1228 ShiftStateContent ("Chars", true);
1230 WriteEscapedBuffer (buffer, index, count,
1231 state == WriteState.Attribute);
1234 public override void WriteRaw (char [] buffer, int index, int count)
1236 CheckChunkRange (buffer, index, count);
1238 ShiftStateContent ("Raw text", false);
1240 writer.Write (buffer, index, count);
1247 WriteIndentCore (0, false);
1250 void WriteIndentEndElement ()
1252 WriteIndentCore (-1, false);
1255 void WriteIndentAttribute ()
1257 if (!WriteIndentCore (0, true))
1258 writer.Write (' '); // space is required instead.
1261 bool WriteIndentCore (int nestFix, bool attribute)
1265 for (int i = open_count - 1; i >= 0; i--)
1266 if (!attribute && elements [i].HasSimple)
1269 if (state != WriteState.Start)
1270 writer.Write (newline);
1271 for (int i = 0; i < open_count + nestFix; i++)
1272 writer.Write (indent_string);
1276 void OutputAutoStartDocument ()
1278 if (state != WriteState.Start)
1280 WriteStartDocumentCore (false, false);
1283 void ShiftStateTopLevel (string occured, bool allowAttribute, bool dontCheckXmlDecl, bool isCharacter)
1286 case WriteState.Error:
1287 case WriteState.Closed:
1288 throw StateError (occured);
1289 case WriteState.Start:
1291 CheckMixedContentState ();
1292 if (xmldecl_state == XmlDeclState.Auto && !dontCheckXmlDecl)
1293 OutputAutoStartDocument ();
1294 state = WriteState.Prolog;
1296 case WriteState.Attribute:
1299 goto case WriteState.Closed;
1300 case WriteState.Element:
1302 CheckMixedContentState ();
1303 CloseStartElement ();
1305 case WriteState.Content:
1307 CheckMixedContentState ();
1310 top_level_space_ignored = false;
1313 void CheckMixedContentState ()
1315 // if (open_count > 0 &&
1316 // state != WriteState.Attribute)
1317 // elements [open_count - 1].HasSimple = true;
1319 elements [open_count - 1].HasSimple = true;
1322 void ShiftStateContent (string occured, bool allowAttribute)
1325 case WriteState.Error:
1326 case WriteState.Closed:
1327 throw StateError (occured);
1328 case WriteState.Prolog:
1329 case WriteState.Start:
1330 if (!allow_doc_fragment || is_document_entity)
1331 goto case WriteState.Closed;
1332 if (xmldecl_state == XmlDeclState.Auto)
1333 OutputAutoStartDocument ();
1334 CheckMixedContentState ();
1335 state = WriteState.Content;
1337 case WriteState.Attribute:
1340 goto case WriteState.Closed;
1341 case WriteState.Element:
1342 CloseStartElement ();
1343 CheckMixedContentState ();
1345 case WriteState.Content:
1346 CheckMixedContentState ();
1351 void WriteEscapedString (string text, bool isAttribute)
1353 char [] escaped = isAttribute ?
1354 escaped_attr_chars : escaped_text_chars;
1356 int idx = text.IndexOfAny (escaped);
1358 char [] arr = text.ToCharArray ();
1359 WriteCheckedBuffer (arr, 0, idx);
1360 WriteEscapedBuffer (
1361 arr, idx, arr.Length - idx, isAttribute);
1363 WriteCheckedString (text);
1367 void WriteCheckedString (string s)
1369 int i = XmlChar.IndexOfInvalid (s, true);
1371 char [] arr = s.ToCharArray ();
1372 writer.Write (arr, 0, i);
1373 WriteCheckedBuffer (arr, i, arr.Length - i);
1375 // no invalid character.
1380 void WriteCheckedBuffer (char [] text, int idx, int length)
1383 int end = idx + length;
1384 while ((idx = XmlChar.IndexOfInvalid (text, start, length, true)) >= 0) {
1385 if (check_character_validity) // actually this is one time pass.
1386 throw ArgumentError (String.Format ("Input contains invalid character at {0} : &#x{1:X};", idx, (int) text [idx]));
1388 writer.Write (text, start, idx - start);
1389 writer.Write ("&#x");
1390 writer.Write (((int) text [idx]).ToString (
1392 CultureInfo.InvariantCulture));
1394 length -= idx - start + 1;
1398 writer.Write (text, start, end - start);
1401 void WriteEscapedBuffer (char [] text, int index, int length,
1405 int end = index + length;
1406 for (int i = start; i < end; i++) {
1414 WriteCheckedBuffer (text, start, i - start);
1417 case '&': writer.Write ("amp;"); break;
1418 case '<': writer.Write ("lt;"); break;
1419 case '>': writer.Write ("gt;"); break;
1420 case '\'': writer.Write ("apos;"); break;
1421 case '"': writer.Write ("quot;"); break;
1426 if (isAttribute && text [i] == quote_char)
1430 if(isAttribute && v2
1431 && newline_handling != NewLineHandling.None) {
1433 WriteCheckedBuffer (text, start, i - start);
1434 writer.Write ("	");
1440 // If no translation was requested, don't change
1442 if(newline_handling == NewLineHandling.None)
1444 // \n is left alone in text if entitizing.
1446 && newline_handling == NewLineHandling.Entitize
1447 && text [i] == '\n')
1450 WriteCheckedBuffer (text, start, i - start);
1451 // Both newline characters in attributes are fully
1452 // entitized for both Entitize and Replace.
1454 || newline_handling == NewLineHandling.Entitize) {
1455 writer.Write (text [i] == '\r' ?
1459 // By this point the requested behavior must be
1460 // Replace, and the text must not be an attribute
1461 // value. CR, LF and CRLF all get converted to
1462 // the configured newline sequence.
1463 if (text [i] == '\r' && i + 1 < end && text [i + 1] == '\n')
1465 writer.Write (newline);
1471 WriteCheckedBuffer (text, start, end - start);
1476 Exception ArgumentOutOfRangeError (string name)
1478 state = WriteState.Error;
1479 return new ArgumentOutOfRangeException (name);
1482 Exception ArgumentError (string msg)
1484 state = WriteState.Error;
1485 return new ArgumentException (msg);
1488 Exception InvalidOperation (string msg)
1490 state = WriteState.Error;
1491 return new InvalidOperationException (msg);
1494 Exception StateError (string occured)
1496 return InvalidOperation (String.Format ("This XmlWriter does not accept {0} at this state {1}.", occured, state));