2 // System.Xml.XmlTextWriter
5 // Kral Ferch <kral_ferch@hotmail.com>
11 using System.Collections;
17 public class XmlTextWriter : XmlWriter
22 bool nullEncoding = false;
23 bool openWriter = true;
24 bool openStartElement = false;
25 bool openStartAttribute = false;
26 bool documentStarted = false;
27 bool namespaces = true;
28 bool openAttribute = false;
29 bool attributeWrittenForElement = false;
30 Stack openElements = new Stack ();
31 Formatting formatting = Formatting.None;
33 char indentChar = ' ';
34 string indentChars = " ";
35 char quoteChar = '\"';
37 string indentFormatting;
38 Stream baseStream = null;
39 string xmlLang = null;
40 XmlSpace xmlSpace = XmlSpace.None;
41 bool openXmlLang = false;
42 bool openXmlSpace = false;
43 string openElementPrefix;
46 Hashtable writtenAttributes = new Hashtable ();
47 bool checkMultipleAttributes = false;
53 public XmlTextWriter (TextWriter w) : base ()
56 nullEncoding = (w.Encoding == null);
59 baseStream = ((StreamWriter)w).BaseStream;
64 public XmlTextWriter (Stream w, Encoding encoding) : base ()
66 if (encoding == null) {
68 encoding = new UTF8Encoding ();
71 this.w = new StreamWriter(w, encoding);
75 public XmlTextWriter (string filename, Encoding encoding) :
76 this (new FileStream (filename, FileMode.Create, FileAccess.Write, FileShare.None), encoding)
84 public Stream BaseStream {
85 get { return baseStream; }
89 public Formatting Formatting {
90 get { return formatting; }
91 set { formatting = value; }
94 private bool IndentingOverriden
97 if (openElements.Count == 0)
100 return (((XmlTextWriterOpenElement)openElements.Peek()).IndentingOverriden);
103 if (openElements.Count > 0)
104 ((XmlTextWriterOpenElement)openElements.Peek()).IndentingOverriden = value;
108 public int Indentation {
109 get { return indentation; }
112 UpdateIndentChars ();
116 public char IndentChar {
117 get { return indentChar; }
120 UpdateIndentChars ();
124 public bool Namespaces {
125 get { return namespaces; }
127 if (ws != WriteState.Start)
128 throw new InvalidOperationException ("NotInWriteState.");
134 public char QuoteChar {
135 get { return quoteChar; }
137 if ((value != '\'') && (value != '\"'))
138 throw new ArgumentException ("This is an invalid XML attribute quote character. Valid attribute quote characters are ' and \".");
144 public override WriteState WriteState {
148 public override string XmlLang {
150 string xmlLang = null;
153 for (i = 0; i < openElements.Count; i++)
155 xmlLang = ((XmlTextWriterOpenElement)openElements.ToArray().GetValue(i)).XmlLang;
164 public override XmlSpace XmlSpace {
166 XmlSpace xmlSpace = XmlSpace.None;
169 for (i = 0; i < openElements.Count; i++)
171 xmlSpace = ((XmlTextWriterOpenElement)openElements.ToArray().GetValue(i)).XmlSpace;
172 if (xmlSpace != XmlSpace.None)
183 private void AddMissingElementXmlns ()
185 // output namespace declaration if not exist.
186 string prefix = openElementPrefix;
187 string ns = openElementNS;
188 openElementPrefix = null;
189 openElementNS = null;
191 // LAMESPEC: If prefix was already assigned another nsuri, then this element's nsuri goes away!
195 string formatXmlns = String.Empty;
196 if (ns != String.Empty)
198 string existingPrefix = namespaceManager.LookupPrefix (ns);
199 bool addDefaultNamespace = false;
201 if (existingPrefix == null)
203 namespaceManager.AddNamespace (prefix, ns);
204 addDefaultNamespace = true;
207 if (prefix == String.Empty)
208 prefix = existingPrefix;
210 if (prefix != existingPrefix)
211 formatXmlns = String.Format (" xmlns:{0}={1}{2}{1}", prefix, quoteChar, ns);
212 else if (addDefaultNamespace)
213 formatXmlns = String.Format (" xmlns={0}{1}{0}", quoteChar, ns);
215 else if ((prefix == String.Empty) && (namespaceManager.LookupNamespace (prefix) != ns))
217 namespaceManager.AddNamespace (prefix, ns);
218 formatXmlns = String.Format (" xmlns={0}{0}", quoteChar);
220 if(formatXmlns != String.Empty) {
221 string xmlns = formatXmlns.Trim ();
222 if (checkMultipleAttributes && !writtenAttributes.Contains (xmlns.Substring (0, xmlns.IndexOf ('='))))
223 w.Write(formatXmlns);
228 private void CheckState ()
231 throw new InvalidOperationException ("The Writer is closed.");
233 if ((documentStarted == true) && (formatting == Formatting.Indented) && (!IndentingOverriden)) {
234 indentFormatting = w.NewLine;
235 if (indentLevel > 0) {
236 for (int i = 0; i < indentLevel; i++)
237 indentFormatting += indentChars;
241 indentFormatting = "";
243 documentStarted = true;
246 public override void Close ()
248 CloseOpenAttributeAndElements ();
251 ws = WriteState.Closed;
255 private void CloseOpenAttributeAndElements ()
258 WriteEndAttribute ();
260 while (openElements.Count > 0) {
265 private void CloseStartElement ()
267 if (!openStartElement)
270 AddMissingElementXmlns ();
273 ws = WriteState.Content;
274 openStartElement = false;
275 attributeWrittenForElement = false;
276 checkMultipleAttributes = false;
277 writtenAttributes.Clear ();
280 public override void Flush ()
285 public override string LookupPrefix (string ns)
287 string prefix = namespaceManager.LookupPrefix (ns);
289 // XmlNamespaceManager has changed to return null when NSURI not found.
290 // (Contradiction to the documentation.)
291 //if (prefix == String.Empty)
296 private void UpdateIndentChars ()
299 for (int i = 0; i < indentation; i++)
300 indentChars += indentChar;
303 public override void WriteBase64 (byte[] buffer, int index, int count)
305 w.Write (Convert.ToBase64String (buffer, index, count));
309 public override void WriteBinHex (byte[] buffer, int index, int count)
311 throw new NotImplementedException ();
314 public override void WriteCData (string text)
316 if (text.IndexOf("]]>") > 0)
317 throw new ArgumentException ();
320 CloseStartElement ();
322 w.Write("<![CDATA[{0}]]>", text);
325 public override void WriteCharEntity (char ch)
327 Int16 intCh = (Int16)ch;
329 // Make sure the character is not in the surrogate pair
330 // character range, 0xd800- 0xdfff
331 if ((intCh >= -10240) && (intCh <= -8193))
332 throw new ArgumentException ("Surrogate Pair is invalid.");
334 w.Write("&#x{0:X};", intCh);
338 public override void WriteChars (char[] buffer, int index, int count)
340 throw new NotImplementedException ();
343 public override void WriteComment (string text)
345 if ((text.EndsWith("-")) || (text.IndexOf("-->") > 0)) {
346 throw new ArgumentException ();
350 CloseStartElement ();
352 w.Write ("<!--{0}-->", text);
355 public override void WriteDocType (string name, string pubid, string sysid, string subset)
357 if (name == null || name.Trim ().Length == 0)
358 throw new ArgumentException ("Invalid DOCTYPE name", "name");
360 w.Write ("<!DOCTYPE ");
363 w.Write (String.Format (" PUBLIC {0}{1}{0} {0}{2}{0}", quoteChar, pubid, sysid));
364 } else if (sysid != null) {
365 w.Write (String.Format (" SYSTEM {0}{1}{0}", quoteChar, sysid));
369 w.Write ("[" + subset + "]");
374 public override void WriteEndAttribute ()
377 throw new InvalidOperationException("Token EndAttribute in state Start would result in an invalid XML document.");
384 ((XmlTextWriterOpenElement)openElements.Peek()).XmlLang = xmlLang;
389 w.Write (xmlSpace.ToString ().ToLower ());
390 openXmlSpace = false;
391 ((XmlTextWriterOpenElement)openElements.Peek()).XmlSpace = xmlSpace;
394 w.Write ("{0}", quoteChar);
396 openAttribute = false;
399 public override void WriteEndDocument ()
401 CloseOpenAttributeAndElements ();
404 throw new ArgumentException ("This document does not have a root element.");
406 ws = WriteState.Start;
410 public override void WriteEndElement ()
412 WriteEndElementInternal (false);
415 private void WriteEndElementInternal (bool fullEndElement)
417 if (openElements.Count == 0)
418 throw new InvalidOperationException("There was no XML start tag open.");
422 AddMissingElementXmlns ();
424 if (openStartElement) {
426 WriteEndAttribute ();
428 w.Write ("></{0}>", ((XmlTextWriterOpenElement)openElements.Peek ()).Name);
433 openStartElement = false;
435 w.Write ("{0}</{1}>", indentFormatting, openElements.Pop ());
438 namespaceManager.PopScope();
442 public override void WriteEntityRef (string name)
444 throw new NotImplementedException ();
447 public override void WriteFullEndElement ()
449 WriteEndElementInternal (true);
452 private void CheckValidChars (string name, bool firstOnlyLetter)
454 foreach (char c in name) {
455 if (XmlConvert.IsInvalid (c, firstOnlyLetter))
456 throw new ArgumentException ("There is an invalid character: '" + c +
461 public override void WriteName (string name)
463 CheckValidChars (name, true);
467 public override void WriteNmToken (string name)
469 CheckValidChars (name, false);
473 public override void WriteProcessingInstruction (string name, string text)
475 if ((name == null) || (name == string.Empty) || (name.IndexOf("?>") > 0) || (text.IndexOf("?>") > 0)) {
476 throw new ArgumentException ();
480 CloseStartElement ();
482 w.Write ("{0}<?{1} {2}?>", indentFormatting, name, text);
486 public override void WriteQualifiedName (string localName, string ns)
488 if (localName == null || localName == String.Empty)
489 throw new ArgumentException ();
492 w.Write ("{0}:{1}", ns, localName);
495 public override void WriteRaw (string data)
497 WriteStringInternal (data, false);
500 public override void WriteRaw (char[] buffer, int index, int count)
502 WriteStringInternal (new string (buffer, index, count), false);
505 public override void WriteStartAttribute (string prefix, string localName, string ns)
507 if ((prefix == "xml") && (localName == "lang"))
510 if ((prefix == "xml") && (localName == "space"))
513 if ((prefix == "xmlns") && (localName.ToLower ().StartsWith ("xml")))
514 throw new ArgumentException ("Prefixes beginning with \"xml\" (regardless of whether the characters are uppercase, lowercase, or some combination thereof) are reserved for use by XML: " + prefix + ":" + localName);
518 if (ws == WriteState.Content)
519 throw new InvalidOperationException ("Token StartAttribute in state " + WriteState + " would result in an invalid XML document.");
522 prefix = String.Empty;
527 string formatPrefix = "";
528 string formatSpace = "";
530 if (ns != String.Empty)
532 string existingPrefix = namespaceManager.LookupPrefix (ns);
534 if (prefix == String.Empty)
535 prefix = (existingPrefix == null) ?
536 String.Empty : existingPrefix;
539 if (prefix != String.Empty)
541 formatPrefix = prefix + ":";
544 if (openStartElement || attributeWrittenForElement)
547 // If already written, then break up.
548 if (checkMultipleAttributes &&
549 writtenAttributes.Contains (formatPrefix + localName))
552 w.Write ("{0}{1}{2}={3}", formatSpace, formatPrefix, localName, quoteChar);
553 if (checkMultipleAttributes)
554 writtenAttributes.Add (formatPrefix + localName, formatPrefix + localName);
556 openAttribute = true;
557 attributeWrittenForElement = true;
558 ws = WriteState.Attribute;
559 if (prefix == String.Empty && localName == "xmlns") {
560 if (namespaceManager.LookupNamespace (prefix) == null)
561 namespaceManager.AddNamespace (prefix, ns);
562 } else if (prefix == "xmlns") {
563 if (namespaceManager.LookupNamespace (localName) == null)
564 namespaceManager.AddNamespace (localName, ns);
568 public override void WriteStartDocument ()
570 WriteStartDocument ("");
573 public override void WriteStartDocument (bool standalone)
575 string standaloneFormatting;
577 if (standalone == true)
578 standaloneFormatting = String.Format (" standalone={0}yes{0}", quoteChar);
580 standaloneFormatting = String.Format (" standalone={0}no{0}", quoteChar);
582 WriteStartDocument (standaloneFormatting);
585 private void WriteStartDocument (string standaloneFormatting)
587 if (documentStarted == true)
588 throw new InvalidOperationException("WriteStartDocument should be the first call.");
591 throw new XmlException ("WriteStartDocument called twice.");
597 string encodingFormatting = "";
600 encodingFormatting = String.Format (" encoding={0}{1}{0}", quoteChar, w.Encoding.HeaderName);
602 w.Write("<?xml version={0}1.0{0}{1}{2}?>", quoteChar, encodingFormatting, standaloneFormatting);
603 ws = WriteState.Prolog;
606 public override void WriteStartElement (string prefix, string localName, string ns)
608 if (!Namespaces && (((prefix != null) && (prefix != String.Empty))
609 || ((ns != null) && (ns != String.Empty))))
610 throw new ArgumentException ("Cannot set the namespace if Namespaces is 'false'.");
612 WriteStartElementInternal (prefix, localName, ns);
615 private void WriteStartElementInternal (string prefix, string localName, string ns)
617 if ((prefix != null && prefix != String.Empty) && ((ns == null) || (ns == String.Empty)))
618 throw new ArgumentException ("Cannot use a prefix with an empty namespace.");
621 CloseStartElement ();
622 writtenAttributes.Clear ();
623 checkMultipleAttributes = true;
626 prefix = namespaceManager.LookupPrefix (ns);
628 prefix = String.Empty;
630 string formatPrefix = "";
633 if (prefix != String.Empty)
634 formatPrefix = prefix + ":";
637 w.Write ("{0}<{1}{2}", indentFormatting, formatPrefix, localName);
639 openElements.Push (new XmlTextWriterOpenElement (formatPrefix + localName));
640 ws = WriteState.Element;
641 openStartElement = true;
643 openElementPrefix = prefix;
645 namespaceManager.PushScope ();
649 public override void WriteString (string text)
651 if (ws == WriteState.Prolog)
652 throw new InvalidOperationException ("Token content in state Prolog would result in an invalid XML document.");
654 WriteStringInternal (text, true);
657 private void WriteStringInternal (string text, bool entitize)
662 if (text != String.Empty)
668 text = text.Replace ("&", "&");
669 text = text.Replace ("<", "<");
670 text = text.Replace (">", ">");
674 if (quoteChar == '"')
675 text = text.Replace ("\"", """);
677 text = text.Replace ("'", "'");
683 IndentingOverriden = true;
684 CloseStartElement ();
686 if (!openXmlLang && !openXmlSpace)
697 xmlSpace = XmlSpace.Default;
700 xmlSpace = XmlSpace.Preserve;
703 throw new ArgumentException ("'{0}' is an invalid xml:space value.");
711 public override void WriteSurrogateCharEntity (char lowChar, char highChar)
713 throw new NotImplementedException ();
716 public override void WriteWhitespace (string ws)
718 foreach (char c in ws) {
719 if ((c != ' ') && (c != '\t') && (c != '\r') && (c != '\n'))
720 throw new ArgumentException ();