2 // System.Xml.XmlTextWriter
5 // Kral Ferch <kral_ferch@hotmail.com>
11 using System.Collections;
17 public class XmlTextWriter : XmlWriter
21 protected TextWriter w;
22 protected bool nullEncoding = false;
23 protected bool openWriter = true;
24 protected bool openStartElement = false;
25 protected bool openStartAttribute = false;
26 protected bool documentStarted = false;
27 private bool namespaces = true;
28 protected bool openAttribute = false;
29 protected bool attributeWrittenForElement = false;
30 protected Stack openElements = new Stack ();
31 private Formatting formatting = Formatting.None;
32 private int indentation = 2;
33 private char indentChar = ' ';
34 protected string indentChars = " ";
35 private char quoteChar = '\"';
36 protected int indentLevel = 0;
37 protected string indentFormatting;
38 protected Stream baseStream = null;
39 protected string xmlLang = null;
40 protected XmlSpace xmlSpace = XmlSpace.None;
41 protected bool openXmlLang = false;
42 protected bool openXmlSpace = false;
48 public XmlTextWriter (TextWriter w) : base ()
53 baseStream = ((StreamWriter)w).BaseStream;
58 public XmlTextWriter (Stream w, Encoding encoding) : base ()
60 if (encoding == null) {
62 encoding = new UTF8Encoding ();
65 this.w = new StreamWriter(w, encoding);
69 public XmlTextWriter (string filename, Encoding encoding) : base ()
71 this.w = new StreamWriter(filename, false, encoding);
72 baseStream = ((StreamWriter)w).BaseStream;
79 public Stream BaseStream {
80 get { return baseStream; }
84 public Formatting Formatting {
85 get { return formatting; }
86 set { formatting = value; }
89 public 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.");
185 if ((documentStarted == true) && (formatting == Formatting.Indented) && (!IndentingOverriden)) {
186 indentFormatting = "\r\n";
187 if (indentLevel > 0) {
188 for (int i = 0; i < indentLevel; i++)
189 indentFormatting += indentChars;
193 indentFormatting = "";
195 documentStarted = true;
198 public override void Close ()
200 while (openElements.Count > 0) {
205 ws = WriteState.Closed;
209 private void CloseStartElement ()
211 if (openStartElement)
214 ws = WriteState.Content;
215 openStartElement = false;
216 attributeWrittenForElement = false;
220 public override void Flush ()
225 public override string LookupPrefix (string ns)
227 string prefix = namespaceManager.LookupPrefix (ns);
229 if (prefix == String.Empty)
235 private void UpdateIndentChars ()
238 for (int i = 0; i < indentation; i++)
239 indentChars += indentChar;
242 public override void WriteBase64 (byte[] buffer, int index, int count)
244 w.Write (Convert.ToBase64String (buffer, index, count));
248 public override void WriteBinHex (byte[] buffer, int index, int count)
250 throw new NotImplementedException ();
253 public override void WriteCData (string text)
255 if (text.IndexOf("]]>") > 0)
256 throw new ArgumentException ();
259 CloseStartElement ();
261 w.Write("<![CDATA[{0}]]>", text);
264 public override void WriteCharEntity (char ch)
266 Int16 intCh = (Int16)ch;
268 // Make sure the character is not in the surrogate pair
269 // character range, 0xd800- 0xdfff
270 if ((intCh >= -10240) && (intCh <= -8193))
271 throw new ArgumentException ("Surrogate Pair is invalid.");
273 w.Write("&#x{0:X};", intCh);
277 public override void WriteChars (char[] buffer, int index, int count)
279 throw new NotImplementedException ();
282 public override void WriteComment (string text)
284 if ((text.EndsWith("-")) || (text.IndexOf("-->") > 0)) {
285 throw new ArgumentException ();
289 CloseStartElement ();
291 w.Write ("<!--{0}-->", text);
295 public override void WriteDocType (string name, string pubid, string sysid, string subset)
297 throw new NotImplementedException ();
300 public override void WriteEndAttribute ()
303 throw new InvalidOperationException("Token EndAttribute in state Start would result in an invalid XML document.");
310 ((XmlTextWriterOpenElement)openElements.Peek()).XmlLang = xmlLang;
315 w.Write (xmlSpace.ToString ().ToLower ());
316 openXmlSpace = false;
317 ((XmlTextWriterOpenElement)openElements.Peek()).XmlSpace = xmlSpace;
320 w.Write ("{0}", quoteChar);
322 openAttribute = false;
326 public override void WriteEndDocument ()
328 throw new NotImplementedException ();
331 public override void WriteEndElement ()
333 if (openElements.Count == 0)
334 throw new InvalidOperationException("There was no XML start tag open.");
340 if (openStartElement) {
343 openStartElement = false;
346 w.Write ("{0}</{1}>", indentFormatting, openElements.Pop ());
349 namespaceManager.PopScope();
353 public override void WriteEntityRef (string name)
355 throw new NotImplementedException ();
359 public override void WriteFullEndElement ()
361 throw new NotImplementedException ();
365 public override void WriteName (string name)
367 throw new NotImplementedException ();
371 public override void WriteNmToken (string name)
373 throw new NotImplementedException ();
376 public override void WriteProcessingInstruction (string name, string text)
378 if ((name == null) || (name == string.Empty) || (name.IndexOf("?>") > 0) || (text.IndexOf("?>") > 0)) {
379 throw new ArgumentException ();
383 CloseStartElement ();
385 w.Write ("{0}<?{1} {2}?>", indentFormatting, name, text);
389 public override void WriteQualifiedName (string localName, string ns)
391 throw new NotImplementedException ();
395 public override void WriteRaw (string data)
397 throw new NotImplementedException ();
401 public override void WriteRaw (char[] buffer, int index, int count)
403 throw new NotImplementedException ();
406 public override void WriteStartAttribute (string prefix, string localName, string ns)
408 if ((prefix == "xml") && (localName == "lang"))
411 if ((prefix == "xml") && (localName == "space"))
414 if ((prefix == "xmlns") && (localName == "xmlns"))
415 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.");
419 if (ws == WriteState.Content)
420 throw new InvalidOperationException ("Token StartAttribute in state " + WriteState + " would result in an invalid XML document.");
423 prefix = String.Empty;
428 string formatPrefix = "";
429 string formatSpace = "";
431 if (ns != String.Empty)
433 string existingPrefix = namespaceManager.LookupPrefix (ns);
435 if (prefix == String.Empty)
436 prefix = existingPrefix;
439 if (prefix != String.Empty)
441 formatPrefix = prefix + ":";
444 if (openStartElement || attributeWrittenForElement)
447 w.Write ("{0}{1}{2}={3}", formatSpace, formatPrefix, localName, quoteChar);
449 openAttribute = true;
450 attributeWrittenForElement = true;
451 ws = WriteState.Attribute;
454 public override void WriteStartDocument ()
456 WriteStartDocument ("");
459 public override void WriteStartDocument (bool standalone)
461 string standaloneFormatting;
463 if (standalone == true)
464 standaloneFormatting = String.Format (" standalone={0}yes{0}", quoteChar);
466 standaloneFormatting = String.Format (" standalone={0}no{0}", quoteChar);
468 WriteStartDocument (standaloneFormatting);
471 private void WriteStartDocument (string standaloneFormatting)
473 if (documentStarted == true)
474 throw new InvalidOperationException("WriteStartDocument should be the first call.");
478 string encodingFormatting = "";
481 encodingFormatting = String.Format (" encoding={0}{1}{0}", quoteChar, w.Encoding.HeaderName);
483 w.Write("<?xml version={0}1.0{0}{1}{2}?>", quoteChar, encodingFormatting, standaloneFormatting);
484 ws = WriteState.Prolog;
487 public override void WriteStartElement (string prefix, string localName, string ns)
489 if (!Namespaces && (((prefix != null) && (prefix != String.Empty))
490 || ((ns != null) && (ns != String.Empty))))
491 throw new ArgumentException ("Cannot set the namespace if Namespaces is 'false'.");
493 WriteStartElementInternal (prefix, localName, ns);
496 protected override void WriteStartElementInternal (string prefix, string localName, string ns)
499 prefix = String.Empty;
504 if ((prefix != String.Empty) && ((ns == null) || (ns == String.Empty)))
505 throw new ArgumentException ("Cannot use a prefix with an empty namespace.");
508 CloseStartElement ();
510 string formatXmlns = "";
511 string formatPrefix = "";
513 if (ns != String.Empty)
515 string existingPrefix = namespaceManager.LookupPrefix (ns);
517 if (prefix == String.Empty)
518 prefix = existingPrefix;
520 if (prefix != existingPrefix)
521 formatXmlns = String.Format (" xmlns:{0}={1}{2}{1}", prefix, quoteChar, ns);
522 else if (existingPrefix == String.Empty)
523 formatXmlns = String.Format (" xmlns={0}{1}{0}", quoteChar, ns);
525 else if ((prefix == String.Empty) && (namespaceManager.LookupNamespace(prefix) != String.Empty)) {
526 formatXmlns = String.Format (" xmlns={0}{0}", quoteChar);
529 if (prefix != String.Empty) {
530 formatPrefix = prefix + ":";
533 w.Write ("{0}<{1}{2}{3}", indentFormatting, formatPrefix, localName, formatXmlns);
535 openElements.Push (new XmlTextWriterOpenElement (formatPrefix + localName));
536 ws = WriteState.Element;
537 openStartElement = true;
539 namespaceManager.PushScope ();
540 namespaceManager.AddNamespace (prefix, ns);
545 public override void WriteString (string text)
547 if (ws == WriteState.Prolog)
548 throw new InvalidOperationException ("Token content in state Prolog would result in an invalid XML document.");
553 if (text != String.Empty) {
556 text = text.Replace ("&", "&");
557 text = text.Replace ("<", "<");
558 text = text.Replace (">", ">");
561 if (quoteChar == '"')
562 text = text.Replace ("\"", """);
564 text = text.Replace ("'", "'");
568 CloseStartElement ();
570 if (!openXmlLang && !openXmlSpace)
578 xmlSpace = XmlSpace.Default;
581 xmlSpace = XmlSpace.Preserve;
584 throw new ArgumentException ("'{0}' is an invalid xml:space value.");
590 IndentingOverriden = true;
594 public override void WriteSurrogateCharEntity (char lowChar, char highChar)
596 throw new NotImplementedException ();
599 public override void WriteWhitespace (string ws)
601 foreach (char c in ws) {
602 if ((c != ' ') && (c != '\t') && (c != '\r') && (c != '\n'))
603 throw new ArgumentException ();