SimpleXMLWriter.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. #
  2. # SimpleXMLWriter
  3. # $Id: SimpleXMLWriter.py 2312 2005-03-02 18:13:39Z fredrik $
  4. #
  5. # a simple XML writer
  6. #
  7. # history:
  8. # 2001-12-28 fl created
  9. # 2002-11-25 fl fixed attribute encoding
  10. # 2002-12-02 fl minor fixes for 1.5.2
  11. # 2004-06-17 fl added pythondoc markup
  12. # 2004-07-23 fl added flush method (from Jay Graves)
  13. # 2004-10-03 fl added declaration method
  14. #
  15. # Copyright (c) 2001-2004 by Fredrik Lundh
  16. #
  17. # fredrik@pythonware.com
  18. # http://www.pythonware.com
  19. #
  20. # --------------------------------------------------------------------
  21. # The SimpleXMLWriter module is
  22. #
  23. # Copyright (c) 2001-2004 by Fredrik Lundh
  24. #
  25. # By obtaining, using, and/or copying this software and/or its
  26. # associated documentation, you agree that you have read, understood,
  27. # and will comply with the following terms and conditions:
  28. #
  29. # Permission to use, copy, modify, and distribute this software and
  30. # its associated documentation for any purpose and without fee is
  31. # hereby granted, provided that the above copyright notice appears in
  32. # all copies, and that both that copyright notice and this permission
  33. # notice appear in supporting documentation, and that the name of
  34. # Secret Labs AB or the author not be used in advertising or publicity
  35. # pertaining to distribution of the software without specific, written
  36. # prior permission.
  37. #
  38. # SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
  39. # TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
  40. # ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR
  41. # BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
  42. # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
  43. # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
  44. # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
  45. # OF THIS SOFTWARE.
  46. # --------------------------------------------------------------------
  47. ##
  48. # Tools to write XML files, without having to deal with encoding
  49. # issues, well-formedness, etc.
  50. # <p>
  51. # The current version does not provide built-in support for
  52. # namespaces. To create files using namespaces, you have to provide
  53. # "xmlns" attributes and explicitly add prefixes to tags and
  54. # attributes.
  55. #
  56. # <h3>Patterns</h3>
  57. #
  58. # The following example generates a small XHTML document.
  59. # <pre>
  60. #
  61. # from elementtree.SimpleXMLWriter import XMLWriter
  62. # import sys
  63. #
  64. # w = XMLWriter(sys.stdout)
  65. #
  66. # html = w.start("html")
  67. #
  68. # w.start("head")
  69. # w.element("title", "my document")
  70. # w.element("meta", name="generator", value="my application 1.0")
  71. # w.end()
  72. #
  73. # w.start("body")
  74. # w.element("h1", "this is a heading")
  75. # w.element("p", "this is a paragraph")
  76. #
  77. # w.start("p")
  78. # w.data("this is ")
  79. # w.element("b", "bold")
  80. # w.data(" and ")
  81. # w.element("i", "italic")
  82. # w.data(".")
  83. # w.end("p")
  84. #
  85. # w.close(html)
  86. # </pre>
  87. ##
  88. import re, sys, string
  89. try:
  90. unicode("")
  91. except NameError:
  92. def encode(s, encoding):
  93. # 1.5.2: application must use the right encoding
  94. return s
  95. _escape = re.compile(r"[&<>\"\x80-\xff]+") # 1.5.2
  96. else:
  97. def encode(s, encoding):
  98. return s.encode(encoding)
  99. _escape = re.compile(eval(r'u"[&<>\"\u0080-\uffff]+"'))
  100. def encode_entity(text, pattern=_escape):
  101. # map reserved and non-ascii characters to numerical entities
  102. def escape_entities(m):
  103. out = []
  104. for char in m.group():
  105. out.append("&#%d;" % ord(char))
  106. return string.join(out, "")
  107. return encode(pattern.sub(escape_entities, text), "ascii")
  108. del _escape
  109. #
  110. # the following functions assume an ascii-compatible encoding
  111. # (or "utf-16")
  112. def escape_cdata(s, encoding=None, replace=string.replace):
  113. s = replace(s, "&", "&amp;")
  114. s = replace(s, "<", "&lt;")
  115. s = replace(s, ">", "&gt;")
  116. if encoding:
  117. try:
  118. return encode(s, encoding)
  119. except UnicodeError:
  120. return encode_entity(s)
  121. return s
  122. def escape_attrib(s, encoding=None, replace=string.replace):
  123. s = replace(s, "&", "&amp;")
  124. s = replace(s, "'", "&apos;")
  125. s = replace(s, "\"", "&quot;")
  126. s = replace(s, "<", "&lt;")
  127. s = replace(s, ">", "&gt;")
  128. if encoding:
  129. try:
  130. return encode(s, encoding)
  131. except UnicodeError:
  132. return encode_entity(s)
  133. return s
  134. ##
  135. # XML writer class.
  136. #
  137. # @param file A file or file-like object. This object must implement
  138. # a <b>write</b> method that takes an 8-bit string.
  139. # @param encoding Optional encoding.
  140. class XMLWriter:
  141. def __init__(self, file, encoding="us-ascii"):
  142. if not hasattr(file, "write"):
  143. file = open(file, "w")
  144. self.__write = file.write
  145. if hasattr(file, "flush"):
  146. self.flush = file.flush
  147. self.__open = 0 # true if start tag is open
  148. self.__tags = []
  149. self.__data = []
  150. self.__encoding = encoding
  151. def __flush(self):
  152. # flush internal buffers
  153. if self.__open:
  154. self.__write(">")
  155. self.__open = 0
  156. if self.__data:
  157. data = string.join(self.__data, "")
  158. self.__write(escape_cdata(data, self.__encoding))
  159. self.__data = []
  160. ##
  161. # Writes an XML declaration.
  162. def declaration(self):
  163. encoding = self.__encoding
  164. if encoding == "us-ascii" or encoding == "utf-8":
  165. self.__write("<?xml version='1.0'?>\n")
  166. else:
  167. self.__write("<?xml version='1.0' encoding='%s'?>\n" % encoding)
  168. ##
  169. # Opens a new element. Attributes can be given as keyword
  170. # arguments, or as a string/string dictionary. You can pass in
  171. # 8-bit strings or Unicode strings; the former are assumed to use
  172. # the encoding passed to the constructor. The method returns an
  173. # opaque identifier that can be passed to the <b>close</b> method,
  174. # to close all open elements up to and including this one.
  175. #
  176. # @param tag Element tag.
  177. # @param attrib Attribute dictionary. Alternatively, attributes
  178. # can be given as keyword arguments.
  179. # @return An element identifier.
  180. def start(self, tag, attrib={}, **extra):
  181. self.__flush()
  182. tag = escape_cdata(tag, self.__encoding)
  183. self.__data = []
  184. self.__tags.append(tag)
  185. self.__write("<%s" % tag)
  186. if attrib or extra:
  187. attrib = attrib.copy()
  188. attrib.update(extra)
  189. attrib = attrib.items()
  190. attrib.sort()
  191. for k, v in attrib:
  192. k = escape_cdata(k, self.__encoding)
  193. v = escape_attrib(v, self.__encoding)
  194. self.__write(" %s=\"%s\"" % (k, v))
  195. self.__open = 1
  196. return len(self.__tags)-1
  197. ##
  198. # Adds a comment to the output stream.
  199. #
  200. # @param comment Comment text, as an 8-bit string or Unicode string.
  201. def comment(self, comment):
  202. self.__flush()
  203. self.__write("<!-- %s -->\n" % escape_cdata(comment, self.__encoding))
  204. ##
  205. # Adds character data to the output stream.
  206. #
  207. # @param text Character data, as an 8-bit string or Unicode string.
  208. def data(self, text):
  209. self.__data.append(text)
  210. ##
  211. # Closes the current element (opened by the most recent call to
  212. # <b>start</b>).
  213. #
  214. # @param tag Element tag. If given, the tag must match the start
  215. # tag. If omitted, the current element is closed.
  216. def end(self, tag=None):
  217. if tag:
  218. assert self.__tags, "unbalanced end(%s)" % tag
  219. assert escape_cdata(tag, self.__encoding) == self.__tags[-1],\
  220. "expected end(%s), got %s" % (self.__tags[-1], tag)
  221. else:
  222. assert self.__tags, "unbalanced end()"
  223. tag = self.__tags.pop()
  224. if self.__data:
  225. self.__flush()
  226. elif self.__open:
  227. self.__open = 0
  228. self.__write(" />")
  229. return
  230. self.__write("</%s>" % tag)
  231. ##
  232. # Closes open elements, up to (and including) the element identified
  233. # by the given identifier.
  234. #
  235. # @param id Element identifier, as returned by the <b>start</b> method.
  236. def close(self, id):
  237. while len(self.__tags) > id:
  238. self.end()
  239. ##
  240. # Adds an entire element. This is the same as calling <b>start</b>,
  241. # <b>data</b>, and <b>end</b> in sequence. The <b>text</b> argument
  242. # can be omitted.
  243. def element(self, tag, text=None, attrib={}, **extra):
  244. apply(self.start, (tag, attrib), extra)
  245. if text:
  246. self.data(text)
  247. self.end()
  248. ##
  249. # Flushes the output stream.
  250. def flush(self):
  251. pass # replaced by the constructor