XmlTextWriter.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  1. //
  2. // System.Xml.XmlTextWriter
  3. //
  4. // Author:
  5. // Kral Ferch <[email protected]>
  6. //
  7. // (C) 2002 Kral Ferch
  8. //
  9. using System;
  10. using System.Collections;
  11. using System.IO;
  12. using System.Text;
  13. namespace System.Xml
  14. {
  15. public class XmlTextWriter : XmlWriter
  16. {
  17. #region Fields
  18. protected TextWriter w;
  19. protected bool nullEncoding = false;
  20. protected bool openWriter = true;
  21. protected bool openStartElement = false;
  22. protected bool openStartAttribute = false;
  23. protected bool documentStarted = false;
  24. private bool namespaces = true;
  25. protected bool openAttribute = false;
  26. protected bool attributeWrittenForElement = false;
  27. protected Stack openElements = new Stack ();
  28. private Formatting formatting = Formatting.None;
  29. private int indentation = 2;
  30. private char indentChar = ' ';
  31. protected string indentChars = " ";
  32. private char quoteChar = '\"';
  33. protected int indentLevel = 0;
  34. protected string indentFormatting;
  35. protected Stream baseStream = null;
  36. protected string xmlLang = null;
  37. protected XmlSpace xmlSpace = XmlSpace.None;
  38. protected bool openXmlLang = false;
  39. protected bool openXmlSpace = false;
  40. #endregion
  41. #region Constructors
  42. public XmlTextWriter (TextWriter w) : base ()
  43. {
  44. this.w = w;
  45. nullEncoding = (w.Encoding == null);
  46. try {
  47. baseStream = ((StreamWriter)w).BaseStream;
  48. }
  49. catch (Exception) { }
  50. }
  51. public XmlTextWriter (Stream w, Encoding encoding) : base ()
  52. {
  53. if (encoding == null) {
  54. nullEncoding = true;
  55. encoding = new UTF8Encoding ();
  56. }
  57. this.w = new StreamWriter(w, encoding);
  58. baseStream = w;
  59. }
  60. public XmlTextWriter (string filename, Encoding encoding) : base ()
  61. {
  62. this.w = new StreamWriter(filename, false, encoding);
  63. baseStream = ((StreamWriter)w).BaseStream;
  64. }
  65. #endregion
  66. #region Properties
  67. public Stream BaseStream {
  68. get { return baseStream; }
  69. }
  70. public Formatting Formatting {
  71. get { return formatting; }
  72. set { formatting = value; }
  73. }
  74. public bool IndentingOverriden
  75. {
  76. get {
  77. if (openElements.Count == 0)
  78. return false;
  79. else
  80. return (((XmlTextWriterOpenElement)openElements.Peek()).IndentingOverriden);
  81. }
  82. set {
  83. if (openElements.Count > 0)
  84. ((XmlTextWriterOpenElement)openElements.Peek()).IndentingOverriden = value;
  85. }
  86. }
  87. public int Indentation {
  88. get { return indentation; }
  89. set {
  90. indentation = value;
  91. UpdateIndentChars ();
  92. }
  93. }
  94. public char IndentChar {
  95. get { return indentChar; }
  96. set {
  97. indentChar = value;
  98. UpdateIndentChars ();
  99. }
  100. }
  101. public bool Namespaces {
  102. get { return namespaces; }
  103. set {
  104. if (ws != WriteState.Start)
  105. throw new InvalidOperationException ("NotInWriteState.");
  106. namespaces = value;
  107. }
  108. }
  109. public char QuoteChar {
  110. get { return quoteChar; }
  111. set {
  112. if ((value != '\'') && (value != '\"'))
  113. throw new ArgumentException ("This is an invalid XML attribute quote character. Valid attribute quote characters are ' and \".");
  114. quoteChar = value;
  115. }
  116. }
  117. public override WriteState WriteState {
  118. get { return ws; }
  119. }
  120. public override string XmlLang {
  121. get {
  122. string xmlLang = null;
  123. int i;
  124. for (i = 0; i < openElements.Count; i++)
  125. {
  126. xmlLang = ((XmlTextWriterOpenElement)openElements.ToArray().GetValue(i)).XmlLang;
  127. if (xmlLang != null)
  128. break;
  129. }
  130. return xmlLang;
  131. }
  132. }
  133. public override XmlSpace XmlSpace {
  134. get {
  135. XmlSpace xmlSpace = XmlSpace.None;
  136. int i;
  137. for (i = 0; i < openElements.Count; i++)
  138. {
  139. xmlSpace = ((XmlTextWriterOpenElement)openElements.ToArray().GetValue(i)).XmlSpace;
  140. if (xmlSpace != XmlSpace.None)
  141. break;
  142. }
  143. return xmlSpace;
  144. }
  145. }
  146. #endregion
  147. #region Methods
  148. private void CheckState ()
  149. {
  150. if (!openWriter) {
  151. throw new InvalidOperationException ("The Writer is closed.");
  152. }
  153. if ((documentStarted == true) && (formatting == Formatting.Indented) && (!IndentingOverriden)) {
  154. indentFormatting = "\r\n";
  155. if (indentLevel > 0) {
  156. for (int i = 0; i < indentLevel; i++)
  157. indentFormatting += indentChars;
  158. }
  159. }
  160. else
  161. indentFormatting = "";
  162. documentStarted = true;
  163. }
  164. public override void Close ()
  165. {
  166. CloseOpenAttributeAndElements ();
  167. w.Close();
  168. ws = WriteState.Closed;
  169. openWriter = false;
  170. }
  171. private void CloseOpenAttributeAndElements ()
  172. {
  173. if (openAttribute)
  174. WriteEndAttribute ();
  175. while (openElements.Count > 0) {
  176. WriteEndElement();
  177. }
  178. }
  179. private void CloseStartElement ()
  180. {
  181. if (openStartElement) {
  182. w.Write(">");
  183. ws = WriteState.Content;
  184. openStartElement = false;
  185. attributeWrittenForElement = false;
  186. }
  187. }
  188. public override void Flush ()
  189. {
  190. w.Flush ();
  191. }
  192. public override string LookupPrefix (string ns)
  193. {
  194. string prefix = namespaceManager.LookupPrefix (ns);
  195. if (prefix == String.Empty)
  196. prefix = null;
  197. return prefix;
  198. }
  199. private void UpdateIndentChars ()
  200. {
  201. indentChars = "";
  202. for (int i = 0; i < indentation; i++)
  203. indentChars += indentChar;
  204. }
  205. public override void WriteBase64 (byte[] buffer, int index, int count)
  206. {
  207. w.Write (Convert.ToBase64String (buffer, index, count));
  208. }
  209. [MonoTODO]
  210. public override void WriteBinHex (byte[] buffer, int index, int count)
  211. {
  212. throw new NotImplementedException ();
  213. }
  214. public override void WriteCData (string text)
  215. {
  216. if (text.IndexOf("]]>") > 0)
  217. throw new ArgumentException ();
  218. CheckState ();
  219. CloseStartElement ();
  220. w.Write("<![CDATA[{0}]]>", text);
  221. }
  222. public override void WriteCharEntity (char ch)
  223. {
  224. Int16 intCh = (Int16)ch;
  225. // Make sure the character is not in the surrogate pair
  226. // character range, 0xd800- 0xdfff
  227. if ((intCh >= -10240) && (intCh <= -8193))
  228. throw new ArgumentException ("Surrogate Pair is invalid.");
  229. w.Write("&#x{0:X};", intCh);
  230. }
  231. [MonoTODO]
  232. public override void WriteChars (char[] buffer, int index, int count)
  233. {
  234. throw new NotImplementedException ();
  235. }
  236. public override void WriteComment (string text)
  237. {
  238. if ((text.EndsWith("-")) || (text.IndexOf("-->") > 0)) {
  239. throw new ArgumentException ();
  240. }
  241. CheckState ();
  242. CloseStartElement ();
  243. w.Write ("<!--{0}-->", text);
  244. }
  245. [MonoTODO]
  246. public override void WriteDocType (string name, string pubid, string sysid, string subset)
  247. {
  248. throw new NotImplementedException ();
  249. }
  250. public override void WriteEndAttribute ()
  251. {
  252. if (!openAttribute)
  253. throw new InvalidOperationException("Token EndAttribute in state Start would result in an invalid XML document.");
  254. CheckState ();
  255. if (openXmlLang) {
  256. w.Write (xmlLang);
  257. openXmlLang = false;
  258. ((XmlTextWriterOpenElement)openElements.Peek()).XmlLang = xmlLang;
  259. }
  260. if (openXmlSpace)
  261. {
  262. w.Write (xmlSpace.ToString ().ToLower ());
  263. openXmlSpace = false;
  264. ((XmlTextWriterOpenElement)openElements.Peek()).XmlSpace = xmlSpace;
  265. }
  266. w.Write ("{0}", quoteChar);
  267. openAttribute = false;
  268. }
  269. public override void WriteEndDocument ()
  270. {
  271. if ((ws == WriteState.Start) || (ws == WriteState.Prolog))
  272. throw new ArgumentException ("This document does not have a root element.");
  273. CloseOpenAttributeAndElements ();
  274. ws = WriteState.Start;
  275. }
  276. public override void WriteEndElement ()
  277. {
  278. WriteEndElementInternal (false);
  279. }
  280. private void WriteEndElementInternal (bool fullEndElement)
  281. {
  282. if (openElements.Count == 0)
  283. throw new InvalidOperationException("There was no XML start tag open.");
  284. indentLevel--;
  285. CheckState ();
  286. if (openStartElement) {
  287. if (openAttribute)
  288. WriteEndAttribute ();
  289. if (fullEndElement)
  290. w.Write ("></{0}>", ((XmlTextWriterOpenElement)openElements.Peek ()).Name);
  291. else
  292. w.Write (" />");
  293. openElements.Pop ();
  294. openStartElement = false;
  295. } else {
  296. w.Write ("{0}</{1}>", indentFormatting, openElements.Pop ());
  297. }
  298. namespaceManager.PopScope();
  299. }
  300. [MonoTODO]
  301. public override void WriteEntityRef (string name)
  302. {
  303. throw new NotImplementedException ();
  304. }
  305. public override void WriteFullEndElement ()
  306. {
  307. WriteEndElementInternal (true);
  308. }
  309. [MonoTODO]
  310. public override void WriteName (string name)
  311. {
  312. throw new NotImplementedException ();
  313. }
  314. [MonoTODO]
  315. public override void WriteNmToken (string name)
  316. {
  317. throw new NotImplementedException ();
  318. }
  319. public override void WriteProcessingInstruction (string name, string text)
  320. {
  321. if ((name == null) || (name == string.Empty) || (name.IndexOf("?>") > 0) || (text.IndexOf("?>") > 0)) {
  322. throw new ArgumentException ();
  323. }
  324. CheckState ();
  325. CloseStartElement ();
  326. w.Write ("{0}<?{1} {2}?>", indentFormatting, name, text);
  327. }
  328. [MonoTODO]
  329. public override void WriteQualifiedName (string localName, string ns)
  330. {
  331. throw new NotImplementedException ();
  332. }
  333. public override void WriteRaw (string data)
  334. {
  335. WriteStringInternal (data, false);
  336. }
  337. [MonoTODO]
  338. public override void WriteRaw (char[] buffer, int index, int count)
  339. {
  340. throw new NotImplementedException ();
  341. }
  342. public override void WriteStartAttribute (string prefix, string localName, string ns)
  343. {
  344. if ((prefix == "xml") && (localName == "lang"))
  345. openXmlLang = true;
  346. if ((prefix == "xml") && (localName == "space"))
  347. openXmlSpace = true;
  348. if ((prefix == "xmlns") && (localName == "xmlns"))
  349. 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.");
  350. CheckState ();
  351. if (ws == WriteState.Content)
  352. throw new InvalidOperationException ("Token StartAttribute in state " + WriteState + " would result in an invalid XML document.");
  353. if (prefix == null)
  354. prefix = String.Empty;
  355. if (ns == null)
  356. ns = String.Empty;
  357. string formatPrefix = "";
  358. string formatSpace = "";
  359. if (ns != String.Empty)
  360. {
  361. string existingPrefix = namespaceManager.LookupPrefix (ns);
  362. if (prefix == String.Empty)
  363. prefix = existingPrefix;
  364. }
  365. if (prefix != String.Empty)
  366. {
  367. formatPrefix = prefix + ":";
  368. }
  369. if (openStartElement || attributeWrittenForElement)
  370. formatSpace = " ";
  371. w.Write ("{0}{1}{2}={3}", formatSpace, formatPrefix, localName, quoteChar);
  372. openAttribute = true;
  373. attributeWrittenForElement = true;
  374. ws = WriteState.Attribute;
  375. }
  376. public override void WriteStartDocument ()
  377. {
  378. WriteStartDocument ("");
  379. }
  380. public override void WriteStartDocument (bool standalone)
  381. {
  382. string standaloneFormatting;
  383. if (standalone == true)
  384. standaloneFormatting = String.Format (" standalone={0}yes{0}", quoteChar);
  385. else
  386. standaloneFormatting = String.Format (" standalone={0}no{0}", quoteChar);
  387. WriteStartDocument (standaloneFormatting);
  388. }
  389. private void WriteStartDocument (string standaloneFormatting)
  390. {
  391. if (documentStarted == true)
  392. throw new InvalidOperationException("WriteStartDocument should be the first call.");
  393. CheckState ();
  394. string encodingFormatting = "";
  395. if (!nullEncoding)
  396. encodingFormatting = String.Format (" encoding={0}{1}{0}", quoteChar, w.Encoding.HeaderName);
  397. w.Write("<?xml version={0}1.0{0}{1}{2}?>", quoteChar, encodingFormatting, standaloneFormatting);
  398. ws = WriteState.Prolog;
  399. }
  400. public override void WriteStartElement (string prefix, string localName, string ns)
  401. {
  402. if (!Namespaces && (((prefix != null) && (prefix != String.Empty))
  403. || ((ns != null) && (ns != String.Empty))))
  404. throw new ArgumentException ("Cannot set the namespace if Namespaces is 'false'.");
  405. WriteStartElementInternal (prefix, localName, ns);
  406. }
  407. protected override void WriteStartElementInternal (string prefix, string localName, string ns)
  408. {
  409. if (prefix == null)
  410. prefix = String.Empty;
  411. if ((prefix != String.Empty) && ((ns == null) || (ns == String.Empty)))
  412. throw new ArgumentException ("Cannot use a prefix with an empty namespace.");
  413. CheckState ();
  414. CloseStartElement ();
  415. string formatXmlns = "";
  416. string formatPrefix = "";
  417. if(ns != null)
  418. {
  419. if (ns != String.Empty)
  420. {
  421. string existingPrefix = namespaceManager.LookupPrefix (ns);
  422. if (prefix == String.Empty)
  423. prefix = existingPrefix;
  424. if (prefix != existingPrefix)
  425. formatXmlns = String.Format (" xmlns:{0}={1}{2}{1}", prefix, quoteChar, ns);
  426. else if (existingPrefix == String.Empty)
  427. formatXmlns = String.Format (" xmlns={0}{1}{0}", quoteChar, ns);
  428. }
  429. else if ((prefix == String.Empty) && (namespaceManager.LookupNamespace(prefix) != String.Empty)) {
  430. formatXmlns = String.Format (" xmlns={0}{0}", quoteChar);
  431. }
  432. if (prefix != String.Empty) {
  433. formatPrefix = prefix + ":";
  434. }
  435. }
  436. w.Write ("{0}<{1}{2}{3}", indentFormatting, formatPrefix, localName, formatXmlns);
  437. openElements.Push (new XmlTextWriterOpenElement (formatPrefix + localName));
  438. ws = WriteState.Element;
  439. openStartElement = true;
  440. namespaceManager.PushScope ();
  441. if(ns != null)
  442. {
  443. namespaceManager.AddNamespace (prefix, ns);
  444. }
  445. indentLevel++;
  446. }
  447. public override void WriteString (string text)
  448. {
  449. if (ws == WriteState.Prolog)
  450. throw new InvalidOperationException ("Token content in state Prolog would result in an invalid XML document.");
  451. WriteStringInternal (text, true);
  452. }
  453. public void WriteStringInternal (string text, bool entitize)
  454. {
  455. if (text == null)
  456. text = String.Empty;
  457. if (text != String.Empty)
  458. {
  459. CheckState ();
  460. if (entitize)
  461. {
  462. text = text.Replace ("&", "&amp;");
  463. text = text.Replace ("<", "&lt;");
  464. text = text.Replace (">", "&gt;");
  465. if (openAttribute)
  466. {
  467. if (quoteChar == '"')
  468. text = text.Replace ("\"", "&quot;");
  469. else
  470. text = text.Replace ("'", "&apos;");
  471. }
  472. }
  473. if (!openAttribute)
  474. {
  475. IndentingOverriden = true;
  476. CloseStartElement ();
  477. }
  478. if (!openXmlLang && !openXmlSpace)
  479. w.Write (text);
  480. else
  481. {
  482. if (openXmlLang)
  483. xmlLang = text;
  484. else
  485. {
  486. switch (text)
  487. {
  488. case "default":
  489. xmlSpace = XmlSpace.Default;
  490. break;
  491. case "preserve":
  492. xmlSpace = XmlSpace.Preserve;
  493. break;
  494. default:
  495. throw new ArgumentException ("'{0}' is an invalid xml:space value.");
  496. }
  497. }
  498. }
  499. }
  500. }
  501. [MonoTODO]
  502. public override void WriteSurrogateCharEntity (char lowChar, char highChar)
  503. {
  504. throw new NotImplementedException ();
  505. }
  506. public override void WriteWhitespace (string ws)
  507. {
  508. foreach (char c in ws) {
  509. if ((c != ' ') && (c != '\t') && (c != '\r') && (c != '\n'))
  510. throw new ArgumentException ();
  511. }
  512. w.Write (ws);
  513. }
  514. #endregion
  515. }
  516. }