XmlTextWriter.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649
  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. if (localName == null || localName == String.Empty)
  332. throw new ArgumentException ();
  333. CheckState ();
  334. w.Write ("{0}:{1}", ns, localName);
  335. }
  336. public override void WriteRaw (string data)
  337. {
  338. WriteStringInternal (data, false);
  339. }
  340. [MonoTODO]
  341. public override void WriteRaw (char[] buffer, int index, int count)
  342. {
  343. throw new NotImplementedException ();
  344. }
  345. public override void WriteStartAttribute (string prefix, string localName, string ns)
  346. {
  347. if ((prefix == "xml") && (localName == "lang"))
  348. openXmlLang = true;
  349. if ((prefix == "xml") && (localName == "space"))
  350. openXmlSpace = true;
  351. if ((prefix == "xmlns") && (localName == "xmlns"))
  352. 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.");
  353. CheckState ();
  354. if (ws == WriteState.Content)
  355. throw new InvalidOperationException ("Token StartAttribute in state " + WriteState + " would result in an invalid XML document.");
  356. if (prefix == null)
  357. prefix = String.Empty;
  358. if (ns == null)
  359. ns = String.Empty;
  360. string formatPrefix = "";
  361. string formatSpace = "";
  362. if (ns != String.Empty)
  363. {
  364. string existingPrefix = namespaceManager.LookupPrefix (ns);
  365. if (prefix == String.Empty)
  366. prefix = existingPrefix;
  367. }
  368. if (prefix != String.Empty)
  369. {
  370. formatPrefix = prefix + ":";
  371. }
  372. if (openStartElement || attributeWrittenForElement)
  373. formatSpace = " ";
  374. w.Write ("{0}{1}{2}={3}", formatSpace, formatPrefix, localName, quoteChar);
  375. openAttribute = true;
  376. attributeWrittenForElement = true;
  377. ws = WriteState.Attribute;
  378. }
  379. public override void WriteStartDocument ()
  380. {
  381. WriteStartDocument ("");
  382. }
  383. public override void WriteStartDocument (bool standalone)
  384. {
  385. string standaloneFormatting;
  386. if (standalone == true)
  387. standaloneFormatting = String.Format (" standalone={0}yes{0}", quoteChar);
  388. else
  389. standaloneFormatting = String.Format (" standalone={0}no{0}", quoteChar);
  390. WriteStartDocument (standaloneFormatting);
  391. }
  392. private void WriteStartDocument (string standaloneFormatting)
  393. {
  394. if (documentStarted == true)
  395. throw new InvalidOperationException("WriteStartDocument should be the first call.");
  396. CheckState ();
  397. string encodingFormatting = "";
  398. if (!nullEncoding)
  399. encodingFormatting = String.Format (" encoding={0}{1}{0}", quoteChar, w.Encoding.HeaderName);
  400. w.Write("<?xml version={0}1.0{0}{1}{2}?>", quoteChar, encodingFormatting, standaloneFormatting);
  401. ws = WriteState.Prolog;
  402. }
  403. public override void WriteStartElement (string prefix, string localName, string ns)
  404. {
  405. if (!Namespaces && (((prefix != null) && (prefix != String.Empty))
  406. || ((ns != null) && (ns != String.Empty))))
  407. throw new ArgumentException ("Cannot set the namespace if Namespaces is 'false'.");
  408. WriteStartElementInternal (prefix, localName, ns);
  409. }
  410. protected override void WriteStartElementInternal (string prefix, string localName, string ns)
  411. {
  412. if (prefix == null)
  413. prefix = String.Empty;
  414. if ((prefix != String.Empty) && ((ns == null) || (ns == String.Empty)))
  415. throw new ArgumentException ("Cannot use a prefix with an empty namespace.");
  416. CheckState ();
  417. CloseStartElement ();
  418. string formatXmlns = "";
  419. string formatPrefix = "";
  420. if(ns != null)
  421. {
  422. if (ns != String.Empty)
  423. {
  424. string existingPrefix = namespaceManager.LookupPrefix (ns);
  425. if (prefix == String.Empty)
  426. prefix = existingPrefix;
  427. if (prefix != existingPrefix)
  428. formatXmlns = String.Format (" xmlns:{0}={1}{2}{1}", prefix, quoteChar, ns);
  429. else if (existingPrefix == String.Empty)
  430. formatXmlns = String.Format (" xmlns={0}{1}{0}", quoteChar, ns);
  431. }
  432. else if ((prefix == String.Empty) && (namespaceManager.LookupNamespace(prefix) != String.Empty)) {
  433. formatXmlns = String.Format (" xmlns={0}{0}", quoteChar);
  434. }
  435. if (prefix != String.Empty) {
  436. formatPrefix = prefix + ":";
  437. }
  438. }
  439. w.Write ("{0}<{1}{2}{3}", indentFormatting, formatPrefix, localName, formatXmlns);
  440. openElements.Push (new XmlTextWriterOpenElement (formatPrefix + localName));
  441. ws = WriteState.Element;
  442. openStartElement = true;
  443. namespaceManager.PushScope ();
  444. if(ns != null)
  445. {
  446. namespaceManager.AddNamespace (prefix, ns);
  447. }
  448. indentLevel++;
  449. }
  450. public override void WriteString (string text)
  451. {
  452. if (ws == WriteState.Prolog)
  453. throw new InvalidOperationException ("Token content in state Prolog would result in an invalid XML document.");
  454. WriteStringInternal (text, true);
  455. }
  456. public void WriteStringInternal (string text, bool entitize)
  457. {
  458. if (text == null)
  459. text = String.Empty;
  460. if (text != String.Empty)
  461. {
  462. CheckState ();
  463. if (entitize)
  464. {
  465. text = text.Replace ("&", "&amp;");
  466. text = text.Replace ("<", "&lt;");
  467. text = text.Replace (">", "&gt;");
  468. if (openAttribute)
  469. {
  470. if (quoteChar == '"')
  471. text = text.Replace ("\"", "&quot;");
  472. else
  473. text = text.Replace ("'", "&apos;");
  474. }
  475. }
  476. if (!openAttribute)
  477. {
  478. IndentingOverriden = true;
  479. CloseStartElement ();
  480. }
  481. if (!openXmlLang && !openXmlSpace)
  482. w.Write (text);
  483. else
  484. {
  485. if (openXmlLang)
  486. xmlLang = text;
  487. else
  488. {
  489. switch (text)
  490. {
  491. case "default":
  492. xmlSpace = XmlSpace.Default;
  493. break;
  494. case "preserve":
  495. xmlSpace = XmlSpace.Preserve;
  496. break;
  497. default:
  498. throw new ArgumentException ("'{0}' is an invalid xml:space value.");
  499. }
  500. }
  501. }
  502. }
  503. }
  504. [MonoTODO]
  505. public override void WriteSurrogateCharEntity (char lowChar, char highChar)
  506. {
  507. throw new NotImplementedException ();
  508. }
  509. public override void WriteWhitespace (string ws)
  510. {
  511. foreach (char c in ws) {
  512. if ((c != ' ') && (c != '\t') && (c != '\r') && (c != '\n'))
  513. throw new ArgumentException ();
  514. }
  515. w.Write (ws);
  516. }
  517. #endregion
  518. }
  519. }