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;
51 public XmlTextWriter (TextWriter w) : base ()
54 nullEncoding = (w.Encoding == null);
57 baseStream = ((StreamWriter)w).BaseStream;
62 public XmlTextWriter (Stream w, Encoding encoding) : base ()
64 if (encoding == null) {
66 encoding = new UTF8Encoding ();
69 this.w = new StreamWriter(w, encoding);
73 public XmlTextWriter (string filename, Encoding encoding) :
74 this (new FileStream (filename, FileMode.Create, FileAccess.Write, FileShare.None), encoding)
82 public Stream BaseStream {
83 get { return baseStream; }
87 public Formatting Formatting {
88 get { return formatting; }
89 set { formatting = value; }
92 private bool IndentingOverriden
95 if (openElements.Count == 0)
98 return (((XmlTextWriterOpenElement)openElements.Peek()).IndentingOverriden);
101 if (openElements.Count > 0)
102 ((XmlTextWriterOpenElement)openElements.Peek()).IndentingOverriden = value;
106 public int Indentation {
107 get { return indentation; }
110 UpdateIndentChars ();
114 public char IndentChar {
115 get { return indentChar; }
118 UpdateIndentChars ();
122 public bool Namespaces {
123 get { return namespaces; }
125 if (ws != WriteState.Start)
126 throw new InvalidOperationException ("NotInWriteState.");
132 public char QuoteChar {
133 get { return quoteChar; }
135 if ((value != '\'') && (value != '\"'))
136 throw new ArgumentException ("This is an invalid XML attribute quote character. Valid attribute quote characters are ' and \".");
142 public override WriteState WriteState {
146 public override string XmlLang {
148 string xmlLang = null;
151 for (i = 0; i < openElements.Count; i++)
153 xmlLang = ((XmlTextWriterOpenElement)openElements.ToArray().GetValue(i)).XmlLang;
162 public override XmlSpace XmlSpace {
164 XmlSpace xmlSpace = XmlSpace.None;
167 for (i = 0; i < openElements.Count; i++)
169 xmlSpace = ((XmlTextWriterOpenElement)openElements.ToArray().GetValue(i)).XmlSpace;
170 if (xmlSpace != XmlSpace.None)
181 private void AddMissingElementXmlns ()
183 // output namespace declaration if not exist.
184 string prefix = openElementPrefix;
185 string ns = openElementNS;
186 if (ns != null/* && LookupPrefix (ns) != prefix*/)
188 string formatXmlns = String.Empty;
189 if (ns != String.Empty)
191 string existingPrefix = namespaceManager.LookupPrefix (ns);
192 bool addDefaultNamespace = false;
194 if (existingPrefix == null)
196 namespaceManager.AddNamespace (prefix, ns);
197 addDefaultNamespace = true;
200 if (prefix == String.Empty)
201 prefix = existingPrefix;
203 if (prefix != existingPrefix)
204 formatXmlns = String.Format (" xmlns:{0}={1}{2}{1}", prefix, quoteChar, ns);
205 else if (addDefaultNamespace)
206 formatXmlns = String.Format (" xmlns={0}{1}{0}", quoteChar, ns);
208 else if ((prefix == String.Empty) && (namespaceManager.LookupNamespace (prefix) != String.Empty))
210 namespaceManager.AddNamespace (prefix, ns);
211 formatXmlns = String.Format (" xmlns={0}{0}", quoteChar);
213 if(formatXmlns != String.Empty)
214 w.Write(formatXmlns);
215 openElementPrefix = null;
216 openElementNS = null;
220 private void CheckState ()
223 throw new InvalidOperationException ("The Writer is closed.");
225 if ((documentStarted == true) && (formatting == Formatting.Indented) && (!IndentingOverriden)) {
226 indentFormatting = w.NewLine;
227 if (indentLevel > 0) {
228 for (int i = 0; i < indentLevel; i++)
229 indentFormatting += indentChars;
233 indentFormatting = "";
235 documentStarted = true;
238 public override void Close ()
240 CloseOpenAttributeAndElements ();
243 ws = WriteState.Closed;
247 private void CloseOpenAttributeAndElements ()
250 WriteEndAttribute ();
252 while (openElements.Count > 0) {
257 private void CloseStartElement ()
259 if (!openStartElement)
262 AddMissingElementXmlns ();
265 ws = WriteState.Content;
266 openStartElement = false;
267 attributeWrittenForElement = false;
270 public override void Flush ()
275 public override string LookupPrefix (string ns)
277 string prefix = namespaceManager.LookupPrefix (ns);
279 // XmlNamespaceManager has changed to return null when NSURI not found.
280 // (Contradiction to the documentation.)
281 //if (prefix == String.Empty)
286 private void UpdateIndentChars ()
289 for (int i = 0; i < indentation; i++)
290 indentChars += indentChar;
293 public override void WriteBase64 (byte[] buffer, int index, int count)
295 w.Write (Convert.ToBase64String (buffer, index, count));
299 public override void WriteBinHex (byte[] buffer, int index, int count)
301 throw new NotImplementedException ();
304 public override void WriteCData (string text)
306 if (text.IndexOf("]]>") > 0)
307 throw new ArgumentException ();
310 CloseStartElement ();
312 w.Write("<![CDATA[{0}]]>", text);
315 public override void WriteCharEntity (char ch)
317 Int16 intCh = (Int16)ch;
319 // Make sure the character is not in the surrogate pair
320 // character range, 0xd800- 0xdfff
321 if ((intCh >= -10240) && (intCh <= -8193))
322 throw new ArgumentException ("Surrogate Pair is invalid.");
324 w.Write("&#x{0:X};", intCh);
328 public override void WriteChars (char[] buffer, int index, int count)
330 throw new NotImplementedException ();
333 public override void WriteComment (string text)
335 if ((text.EndsWith("-")) || (text.IndexOf("-->") > 0)) {
336 throw new ArgumentException ();
340 CloseStartElement ();
342 w.Write ("<!--{0}-->", text);
345 public override void WriteDocType (string name, string pubid, string sysid, string subset)
347 if (name == null || name.Trim ().Length == 0)
348 throw new ArgumentException ("Invalid DOCTYPE name", "name");
350 w.Write ("<!DOCTYPE ");
353 w.Write (String.Format (" PUBLIC {0}{1}{0} {0}{2}{0}", quoteChar, pubid, sysid));
354 } else if (sysid != null) {
355 w.Write (String.Format (" SYSTEM {0}{1}{0}", quoteChar, sysid));
359 w.Write ("[" + subset + "]");
364 public override void WriteEndAttribute ()
367 throw new InvalidOperationException("Token EndAttribute in state Start would result in an invalid XML document.");
374 ((XmlTextWriterOpenElement)openElements.Peek()).XmlLang = xmlLang;
379 w.Write (xmlSpace.ToString ().ToLower ());
380 openXmlSpace = false;
381 ((XmlTextWriterOpenElement)openElements.Peek()).XmlSpace = xmlSpace;
384 w.Write ("{0}", quoteChar);
386 openAttribute = false;
389 public override void WriteEndDocument ()
391 CloseOpenAttributeAndElements ();
394 throw new ArgumentException ("This document does not have a root element.");
396 ws = WriteState.Start;
400 public override void WriteEndElement ()
402 WriteEndElementInternal (false);
405 private void WriteEndElementInternal (bool fullEndElement)
407 if (openElements.Count == 0)
408 throw new InvalidOperationException("There was no XML start tag open.");
412 AddMissingElementXmlns ();
414 if (openStartElement) {
416 WriteEndAttribute ();
418 w.Write ("></{0}>", ((XmlTextWriterOpenElement)openElements.Peek ()).Name);
423 openStartElement = false;
425 w.Write ("{0}</{1}>", indentFormatting, openElements.Pop ());
428 namespaceManager.PopScope();
432 public override void WriteEntityRef (string name)
434 throw new NotImplementedException ();
437 public override void WriteFullEndElement ()
439 WriteEndElementInternal (true);
442 private void CheckValidChars (string name, bool firstOnlyLetter)
444 foreach (char c in name) {
445 if (XmlConvert.IsInvalid (c, firstOnlyLetter))
446 throw new ArgumentException ("There is an invalid character: '" + c +
451 public override void WriteName (string name)
453 CheckValidChars (name, true);
457 public override void WriteNmToken (string name)
459 CheckValidChars (name, false);
463 public override void WriteProcessingInstruction (string name, string text)
465 if ((name == null) || (name == string.Empty) || (name.IndexOf("?>") > 0) || (text.IndexOf("?>") > 0)) {
466 throw new ArgumentException ();
470 CloseStartElement ();
472 w.Write ("{0}<?{1} {2}?>", indentFormatting, name, text);
476 public override void WriteQualifiedName (string localName, string ns)
478 if (localName == null || localName == String.Empty)
479 throw new ArgumentException ();
482 w.Write ("{0}:{1}", ns, localName);
485 public override void WriteRaw (string data)
487 WriteStringInternal (data, false);
490 public override void WriteRaw (char[] buffer, int index, int count)
492 WriteStringInternal (new string (buffer, index, count), false);
495 public override void WriteStartAttribute (string prefix, string localName, string ns)
497 if ((prefix == "xml") && (localName == "lang"))
500 if ((prefix == "xml") && (localName == "space"))
503 if ((prefix == "xmlns") && (localName == "xmlns"))
504 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.");
508 if (ws == WriteState.Content)
509 throw new InvalidOperationException ("Token StartAttribute in state " + WriteState + " would result in an invalid XML document.");
512 prefix = String.Empty;
517 string formatPrefix = "";
518 string formatSpace = "";
520 if (ns != String.Empty)
522 string existingPrefix = namespaceManager.LookupPrefix (ns);
524 if (prefix == String.Empty)
525 prefix = (existingPrefix == null) ?
526 String.Empty : existingPrefix;
529 if (prefix != String.Empty)
531 formatPrefix = prefix + ":";
534 if (openStartElement || attributeWrittenForElement)
537 w.Write ("{0}{1}{2}={3}", formatSpace, formatPrefix, localName, quoteChar);
539 openAttribute = true;
540 attributeWrittenForElement = true;
541 ws = WriteState.Attribute;
544 public override void WriteStartDocument ()
546 WriteStartDocument ("");
549 public override void WriteStartDocument (bool standalone)
551 string standaloneFormatting;
553 if (standalone == true)
554 standaloneFormatting = String.Format (" standalone={0}yes{0}", quoteChar);
556 standaloneFormatting = String.Format (" standalone={0}no{0}", quoteChar);
558 WriteStartDocument (standaloneFormatting);
561 private void WriteStartDocument (string standaloneFormatting)
563 if (documentStarted == true)
564 throw new InvalidOperationException("WriteStartDocument should be the first call.");
567 throw new XmlException ("WriteStartDocument called twice.");
573 string encodingFormatting = "";
576 encodingFormatting = String.Format (" encoding={0}{1}{0}", quoteChar, w.Encoding.HeaderName);
578 w.Write("<?xml version={0}1.0{0}{1}{2}?>", quoteChar, encodingFormatting, standaloneFormatting);
579 ws = WriteState.Prolog;
582 public override void WriteStartElement (string prefix, string localName, string ns)
584 if (!Namespaces && (((prefix != null) && (prefix != String.Empty))
585 || ((ns != null) && (ns != String.Empty))))
586 throw new ArgumentException ("Cannot set the namespace if Namespaces is 'false'.");
588 WriteStartElementInternal (prefix, localName, ns);
591 private void WriteStartElementInternal (string prefix, string localName, string ns)
593 if ((prefix != null && prefix != String.Empty) && ((ns == null) || (ns == String.Empty)))
594 throw new ArgumentException ("Cannot use a prefix with an empty namespace.");
597 CloseStartElement ();
600 prefix = namespaceManager.LookupPrefix (ns);
602 prefix = String.Empty;
604 string formatXmlns = "";
605 string formatPrefix = "";
608 if (prefix != String.Empty)
609 formatPrefix = prefix + ":";
612 w.Write ("{0}<{1}{2}{3}", indentFormatting, formatPrefix, localName, formatXmlns);
615 openElements.Push (new XmlTextWriterOpenElement (formatPrefix + localName));
616 ws = WriteState.Element;
617 openStartElement = true;
619 openElementPrefix = prefix;
621 namespaceManager.PushScope ();
623 // namespaceManager.AddNamespace (prefix, ns);
627 public override void WriteString (string text)
629 if (ws == WriteState.Prolog)
630 throw new InvalidOperationException ("Token content in state Prolog would result in an invalid XML document.");
632 WriteStringInternal (text, true);
635 private void WriteStringInternal (string text, bool entitize)
640 if (text != String.Empty)
646 text = text.Replace ("&", "&");
647 text = text.Replace ("<", "<");
648 text = text.Replace (">", ">");
652 if (quoteChar == '"')
653 text = text.Replace ("\"", """);
655 text = text.Replace ("'", "'");
661 IndentingOverriden = true;
662 CloseStartElement ();
664 if (!openXmlLang && !openXmlSpace)
675 xmlSpace = XmlSpace.Default;
678 xmlSpace = XmlSpace.Preserve;
681 throw new ArgumentException ("'{0}' is an invalid xml:space value.");
689 public override void WriteSurrogateCharEntity (char lowChar, char highChar)
691 throw new NotImplementedException ();
694 public override void WriteWhitespace (string ws)
696 foreach (char c in ws) {
697 if ((c != ' ') && (c != '\t') && (c != '\r') && (c != '\n'))
698 throw new ArgumentException ();