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;
48 public XmlTextWriter (TextWriter w) : base ()
51 nullEncoding = (w.Encoding == null);
54 baseStream = ((StreamWriter)w).BaseStream;
59 public XmlTextWriter (Stream w, Encoding encoding) : base ()
61 if (encoding == null) {
63 encoding = new UTF8Encoding ();
66 this.w = new StreamWriter(w, encoding);
70 public XmlTextWriter (string filename, Encoding encoding) :
71 this (new FileStream (filename, FileMode.Create, FileAccess.Write, FileShare.None), encoding)
79 public Stream BaseStream {
80 get { return baseStream; }
84 public Formatting Formatting {
85 get { return formatting; }
86 set { formatting = value; }
89 private bool IndentingOverriden
92 if (openElements.Count == 0)
95 return (((XmlTextWriterOpenElement)openElements.Peek()).IndentingOverriden);
98 if (openElements.Count > 0)
99 ((XmlTextWriterOpenElement)openElements.Peek()).IndentingOverriden = value;
103 public int Indentation {
104 get { return indentation; }
107 UpdateIndentChars ();
111 public char IndentChar {
112 get { return indentChar; }
115 UpdateIndentChars ();
119 public bool Namespaces {
120 get { return namespaces; }
122 if (ws != WriteState.Start)
123 throw new InvalidOperationException ("NotInWriteState.");
129 public char QuoteChar {
130 get { return quoteChar; }
132 if ((value != '\'') && (value != '\"'))
133 throw new ArgumentException ("This is an invalid XML attribute quote character. Valid attribute quote characters are ' and \".");
139 public override WriteState WriteState {
143 public override string XmlLang {
145 string xmlLang = null;
148 for (i = 0; i < openElements.Count; i++)
150 xmlLang = ((XmlTextWriterOpenElement)openElements.ToArray().GetValue(i)).XmlLang;
159 public override XmlSpace XmlSpace {
161 XmlSpace xmlSpace = XmlSpace.None;
164 for (i = 0; i < openElements.Count; i++)
166 xmlSpace = ((XmlTextWriterOpenElement)openElements.ToArray().GetValue(i)).XmlSpace;
167 if (xmlSpace != XmlSpace.None)
179 private void CheckState ()
182 throw new InvalidOperationException ("The Writer is closed.");
184 if ((documentStarted == true) && (formatting == Formatting.Indented) && (!IndentingOverriden)) {
185 indentFormatting = "\r\n";
186 if (indentLevel > 0) {
187 for (int i = 0; i < indentLevel; i++)
188 indentFormatting += indentChars;
192 indentFormatting = "";
194 documentStarted = true;
197 public override void Close ()
199 CloseOpenAttributeAndElements ();
202 ws = WriteState.Closed;
206 private void CloseOpenAttributeAndElements ()
209 WriteEndAttribute ();
211 while (openElements.Count > 0) {
216 private void CloseStartElement ()
218 if (openStartElement) {
220 ws = WriteState.Content;
221 openStartElement = false;
222 attributeWrittenForElement = false;
226 public override void Flush ()
231 public override string LookupPrefix (string ns)
233 string prefix = namespaceManager.LookupPrefix (ns);
235 if (prefix == String.Empty)
241 private void UpdateIndentChars ()
244 for (int i = 0; i < indentation; i++)
245 indentChars += indentChar;
248 public override void WriteBase64 (byte[] buffer, int index, int count)
250 w.Write (Convert.ToBase64String (buffer, index, count));
254 public override void WriteBinHex (byte[] buffer, int index, int count)
256 throw new NotImplementedException ();
259 public override void WriteCData (string text)
261 if (text.IndexOf("]]>") > 0)
262 throw new ArgumentException ();
265 CloseStartElement ();
267 w.Write("<![CDATA[{0}]]>", text);
270 public override void WriteCharEntity (char ch)
272 Int16 intCh = (Int16)ch;
274 // Make sure the character is not in the surrogate pair
275 // character range, 0xd800- 0xdfff
276 if ((intCh >= -10240) && (intCh <= -8193))
277 throw new ArgumentException ("Surrogate Pair is invalid.");
279 w.Write("&#x{0:X};", intCh);
283 public override void WriteChars (char[] buffer, int index, int count)
285 throw new NotImplementedException ();
288 public override void WriteComment (string text)
290 if ((text.EndsWith("-")) || (text.IndexOf("-->") > 0)) {
291 throw new ArgumentException ();
295 CloseStartElement ();
297 w.Write ("<!--{0}-->", text);
300 public override void WriteDocType (string name, string pubid, string sysid, string subset)
302 if (name == null || name.Trim ().Length == 0)
303 throw new ArgumentException ("Invalid DOCTYPE name", "name");
305 w.Write ("<!DOCTYPE ");
308 w.Write (String.Format (" PUBLIC {0}{1}{0} {0}{2}{0}", quoteChar, pubid, sysid));
309 } else if (sysid != null) {
310 w.Write (String.Format (" SYSTEM {0}{1}{0}", quoteChar, sysid));
314 w.Write ("[" + subset + "]");
319 public override void WriteEndAttribute ()
322 throw new InvalidOperationException("Token EndAttribute in state Start would result in an invalid XML document.");
329 ((XmlTextWriterOpenElement)openElements.Peek()).XmlLang = xmlLang;
334 w.Write (xmlSpace.ToString ().ToLower ());
335 openXmlSpace = false;
336 ((XmlTextWriterOpenElement)openElements.Peek()).XmlSpace = xmlSpace;
339 w.Write ("{0}", quoteChar);
341 openAttribute = false;
344 public override void WriteEndDocument ()
346 CloseOpenAttributeAndElements ();
348 if ((ws == WriteState.Start) || (ws == WriteState.Prolog))
349 throw new ArgumentException ("This document does not have a root element.");
351 ws = WriteState.Start;
354 public override void WriteEndElement ()
356 WriteEndElementInternal (false);
359 private void WriteEndElementInternal (bool fullEndElement)
361 if (openElements.Count == 0)
362 throw new InvalidOperationException("There was no XML start tag open.");
367 if (openStartElement) {
369 WriteEndAttribute ();
371 w.Write ("></{0}>", ((XmlTextWriterOpenElement)openElements.Peek ()).Name);
376 openStartElement = false;
378 w.Write ("{0}</{1}>", indentFormatting, openElements.Pop ());
381 namespaceManager.PopScope();
385 public override void WriteEntityRef (string name)
387 throw new NotImplementedException ();
390 public override void WriteFullEndElement ()
392 WriteEndElementInternal (true);
395 private void CheckValidChars (string name, bool firstOnlyLetter)
397 foreach (char c in name) {
398 if (XmlConvert.IsInvalid (c, firstOnlyLetter))
399 throw new ArgumentException ("There is an invalid character: '" + c +
404 public override void WriteName (string name)
406 CheckValidChars (name, true);
410 public override void WriteNmToken (string name)
412 CheckValidChars (name, false);
416 public override void WriteProcessingInstruction (string name, string text)
418 if ((name == null) || (name == string.Empty) || (name.IndexOf("?>") > 0) || (text.IndexOf("?>") > 0)) {
419 throw new ArgumentException ();
423 CloseStartElement ();
425 w.Write ("{0}<?{1} {2}?>", indentFormatting, name, text);
429 public override void WriteQualifiedName (string localName, string ns)
431 if (localName == null || localName == String.Empty)
432 throw new ArgumentException ();
435 w.Write ("{0}:{1}", ns, localName);
438 public override void WriteRaw (string data)
440 WriteStringInternal (data, false);
444 public override void WriteRaw (char[] buffer, int index, int count)
446 throw new NotImplementedException ();
449 public override void WriteStartAttribute (string prefix, string localName, string ns)
451 if ((prefix == "xml") && (localName == "lang"))
454 if ((prefix == "xml") && (localName == "space"))
457 if ((prefix == "xmlns") && (localName == "xmlns"))
458 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.");
462 if (ws == WriteState.Content)
463 throw new InvalidOperationException ("Token StartAttribute in state " + WriteState + " would result in an invalid XML document.");
466 prefix = String.Empty;
471 string formatPrefix = "";
472 string formatSpace = "";
474 if (ns != String.Empty)
476 string existingPrefix = namespaceManager.LookupPrefix (ns);
478 if (prefix == String.Empty)
479 prefix = existingPrefix;
482 if (prefix != String.Empty)
484 formatPrefix = prefix + ":";
487 if (openStartElement || attributeWrittenForElement)
490 w.Write ("{0}{1}{2}={3}", formatSpace, formatPrefix, localName, quoteChar);
492 openAttribute = true;
493 attributeWrittenForElement = true;
494 ws = WriteState.Attribute;
497 public override void WriteStartDocument ()
499 WriteStartDocument ("");
502 public override void WriteStartDocument (bool standalone)
504 string standaloneFormatting;
506 if (standalone == true)
507 standaloneFormatting = String.Format (" standalone={0}yes{0}", quoteChar);
509 standaloneFormatting = String.Format (" standalone={0}no{0}", quoteChar);
511 WriteStartDocument (standaloneFormatting);
514 private void WriteStartDocument (string standaloneFormatting)
516 if (documentStarted == true)
517 throw new InvalidOperationException("WriteStartDocument should be the first call.");
521 string encodingFormatting = "";
524 encodingFormatting = String.Format (" encoding={0}{1}{0}", quoteChar, w.Encoding.HeaderName);
526 w.Write("<?xml version={0}1.0{0}{1}{2}?>", quoteChar, encodingFormatting, standaloneFormatting);
527 ws = WriteState.Prolog;
530 public override void WriteStartElement (string prefix, string localName, string ns)
532 if (!Namespaces && (((prefix != null) && (prefix != String.Empty))
533 || ((ns != null) && (ns != String.Empty))))
534 throw new ArgumentException ("Cannot set the namespace if Namespaces is 'false'.");
536 WriteStartElementInternal (prefix, localName, ns);
539 private void WriteStartElementInternal (string prefix, string localName, string ns)
542 prefix = String.Empty;
544 if ((prefix != String.Empty) && ((ns == null) || (ns == String.Empty)))
545 throw new ArgumentException ("Cannot use a prefix with an empty namespace.");
548 CloseStartElement ();
550 string formatXmlns = "";
551 string formatPrefix = "";
555 if (ns != String.Empty)
557 string existingPrefix = namespaceManager.LookupPrefix (ns);
558 bool addDefaultNamespace = false;
560 if (existingPrefix == String.Empty && !namespaceManager.HasNamespace (prefix)) {
561 namespaceManager.AddNamespace (prefix, ns);
562 addDefaultNamespace = true;
565 if (prefix == String.Empty)
566 prefix = existingPrefix;
568 if (prefix != existingPrefix)
569 formatXmlns = String.Format (" xmlns:{0}={1}{2}{1}", prefix, quoteChar, ns);
570 else if (addDefaultNamespace)
571 formatXmlns = String.Format (" xmlns={0}{1}{0}", quoteChar, ns);
573 else if ((prefix == String.Empty) && (namespaceManager.LookupNamespace(prefix) != String.Empty)) {
574 formatXmlns = String.Format (" xmlns={0}{0}", quoteChar);
577 if (prefix != String.Empty) {
578 formatPrefix = prefix + ":";
582 w.Write ("{0}<{1}{2}{3}", indentFormatting, formatPrefix, localName, formatXmlns);
585 openElements.Push (new XmlTextWriterOpenElement (formatPrefix + localName));
586 ws = WriteState.Element;
587 openStartElement = true;
589 namespaceManager.PushScope ();
592 namespaceManager.AddNamespace (prefix, ns);
597 public override void WriteString (string text)
599 if (ws == WriteState.Prolog)
600 throw new InvalidOperationException ("Token content in state Prolog would result in an invalid XML document.");
602 WriteStringInternal (text, true);
605 private void WriteStringInternal (string text, bool entitize)
610 if (text != String.Empty)
616 text = text.Replace ("&", "&");
617 text = text.Replace ("<", "<");
618 text = text.Replace (">", ">");
622 if (quoteChar == '"')
623 text = text.Replace ("\"", """);
625 text = text.Replace ("'", "'");
631 IndentingOverriden = true;
632 CloseStartElement ();
634 if (!openXmlLang && !openXmlSpace)
645 xmlSpace = XmlSpace.Default;
648 xmlSpace = XmlSpace.Preserve;
651 throw new ArgumentException ("'{0}' is an invalid xml:space value.");
659 public override void WriteSurrogateCharEntity (char lowChar, char highChar)
661 throw new NotImplementedException ();
664 public override void WriteWhitespace (string ws)
666 foreach (char c in ws) {
667 if ((c != ' ') && (c != '\t') && (c != '\r') && (c != '\n'))
668 throw new ArgumentException ();