_stan.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. # -*- test-case-name: twisted.web.test.test_stan -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. An s-expression-like syntax for expressing xml in pure python.
  6. Stan tags allow you to build XML documents using Python.
  7. Stan is a DOM, or Document Object Model, implemented using basic Python types
  8. and functions called "flatteners". A flattener is a function that knows how to
  9. turn an object of a specific type into something that is closer to an HTML
  10. string. Stan differs from the W3C DOM by not being as cumbersome and heavy
  11. weight. Since the object model is built using simple python types such as lists,
  12. strings, and dictionaries, the API is simpler and constructing a DOM less
  13. cumbersome.
  14. @var voidElements: the names of HTML 'U{void
  15. elements<http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html#void-elements>}';
  16. those which can't have contents and can therefore be self-closing in the
  17. output.
  18. """
  19. from __future__ import absolute_import, division
  20. from twisted.python.compat import iteritems
  21. class slot(object):
  22. """
  23. Marker for markup insertion in a template.
  24. @type name: C{str}
  25. @ivar name: The name of this slot. The key which must be used in
  26. L{Tag.fillSlots} to fill it.
  27. @type children: C{list}
  28. @ivar children: The L{Tag} objects included in this L{slot}'s template.
  29. @type default: anything flattenable, or L{None}
  30. @ivar default: The default contents of this slot, if it is left unfilled.
  31. If this is L{None}, an L{UnfilledSlot} will be raised, rather than
  32. L{None} actually being used.
  33. @type filename: C{str} or L{None}
  34. @ivar filename: The name of the XML file from which this tag was parsed.
  35. If it was not parsed from an XML file, L{None}.
  36. @type lineNumber: C{int} or L{None}
  37. @ivar lineNumber: The line number on which this tag was encountered in the
  38. XML file from which it was parsed. If it was not parsed from an XML
  39. file, L{None}.
  40. @type columnNumber: C{int} or L{None}
  41. @ivar columnNumber: The column number at which this tag was encountered in
  42. the XML file from which it was parsed. If it was not parsed from an
  43. XML file, L{None}.
  44. """
  45. def __init__(self, name, default=None, filename=None, lineNumber=None,
  46. columnNumber=None):
  47. self.name = name
  48. self.children = []
  49. self.default = default
  50. self.filename = filename
  51. self.lineNumber = lineNumber
  52. self.columnNumber = columnNumber
  53. def __repr__(self):
  54. return "slot(%r)" % (self.name,)
  55. class Tag(object):
  56. """
  57. A L{Tag} represents an XML tags with a tag name, attributes, and children.
  58. A L{Tag} can be constructed using the special L{twisted.web.template.tags}
  59. object, or it may be constructed directly with a tag name. L{Tag}s have a
  60. special method, C{__call__}, which makes representing trees of XML natural
  61. using pure python syntax.
  62. @ivar tagName: The name of the represented element. For a tag like
  63. C{<div></div>}, this would be C{"div"}.
  64. @type tagName: C{str}
  65. @ivar attributes: The attributes of the element.
  66. @type attributes: C{dict} mapping C{str} to renderable objects.
  67. @ivar children: The child L{Tag}s of this C{Tag}.
  68. @type children: C{list} of renderable objects.
  69. @ivar render: The name of the render method to use for this L{Tag}. This
  70. name will be looked up at render time by the
  71. L{twisted.web.template.Element} doing the rendering, via
  72. L{twisted.web.template.Element.lookupRenderMethod}, to determine which
  73. method to call.
  74. @type render: C{str}
  75. @type filename: C{str} or L{None}
  76. @ivar filename: The name of the XML file from which this tag was parsed.
  77. If it was not parsed from an XML file, L{None}.
  78. @type lineNumber: C{int} or L{None}
  79. @ivar lineNumber: The line number on which this tag was encountered in the
  80. XML file from which it was parsed. If it was not parsed from an XML
  81. file, L{None}.
  82. @type columnNumber: C{int} or L{None}
  83. @ivar columnNumber: The column number at which this tag was encountered in
  84. the XML file from which it was parsed. If it was not parsed from an
  85. XML file, L{None}.
  86. @type slotData: C{dict} or L{None}
  87. @ivar slotData: The data which can fill slots. If present, a dictionary
  88. mapping slot names to renderable values. The values in this dict might
  89. be anything that can be present as the child of a L{Tag}; strings,
  90. lists, L{Tag}s, generators, etc.
  91. """
  92. slotData = None
  93. filename = None
  94. lineNumber = None
  95. columnNumber = None
  96. def __init__(self, tagName, attributes=None, children=None, render=None,
  97. filename=None, lineNumber=None, columnNumber=None):
  98. self.tagName = tagName
  99. self.render = render
  100. if attributes is None:
  101. self.attributes = {}
  102. else:
  103. self.attributes = attributes
  104. if children is None:
  105. self.children = []
  106. else:
  107. self.children = children
  108. if filename is not None:
  109. self.filename = filename
  110. if lineNumber is not None:
  111. self.lineNumber = lineNumber
  112. if columnNumber is not None:
  113. self.columnNumber = columnNumber
  114. def fillSlots(self, **slots):
  115. """
  116. Remember the slots provided at this position in the DOM.
  117. During the rendering of children of this node, slots with names in
  118. C{slots} will be rendered as their corresponding values.
  119. @return: C{self}. This enables the idiom C{return tag.fillSlots(...)} in
  120. renderers.
  121. """
  122. if self.slotData is None:
  123. self.slotData = {}
  124. self.slotData.update(slots)
  125. return self
  126. def __call__(self, *children, **kw):
  127. """
  128. Add children and change attributes on this tag.
  129. This is implemented using __call__ because it then allows the natural
  130. syntax::
  131. table(tr1, tr2, width="100%", height="50%", border="1")
  132. Children may be other tag instances, strings, functions, or any other
  133. object which has a registered flatten.
  134. Attributes may be 'transparent' tag instances (so that
  135. C{a(href=transparent(data="foo", render=myhrefrenderer))} works),
  136. strings, functions, or any other object which has a registered
  137. flattener.
  138. If the attribute is a python keyword, such as 'class', you can add an
  139. underscore to the name, like 'class_'.
  140. There is one special keyword argument, 'render', which will be used as
  141. the name of the renderer and saved as the 'render' attribute of this
  142. instance, rather than the DOM 'render' attribute in the attributes
  143. dictionary.
  144. """
  145. self.children.extend(children)
  146. for k, v in iteritems(kw):
  147. if k[-1] == '_':
  148. k = k[:-1]
  149. if k == 'render':
  150. self.render = v
  151. else:
  152. self.attributes[k] = v
  153. return self
  154. def _clone(self, obj, deep):
  155. """
  156. Clone an arbitrary object; used by L{Tag.clone}.
  157. @param obj: an object with a clone method, a list or tuple, or something
  158. which should be immutable.
  159. @param deep: whether to continue cloning child objects; i.e. the
  160. contents of lists, the sub-tags within a tag.
  161. @return: a clone of C{obj}.
  162. """
  163. if hasattr(obj, 'clone'):
  164. return obj.clone(deep)
  165. elif isinstance(obj, (list, tuple)):
  166. return [self._clone(x, deep) for x in obj]
  167. else:
  168. return obj
  169. def clone(self, deep=True):
  170. """
  171. Return a clone of this tag. If deep is True, clone all of this tag's
  172. children. Otherwise, just shallow copy the children list without copying
  173. the children themselves.
  174. """
  175. if deep:
  176. newchildren = [self._clone(x, True) for x in self.children]
  177. else:
  178. newchildren = self.children[:]
  179. newattrs = self.attributes.copy()
  180. for key in newattrs.keys():
  181. newattrs[key] = self._clone(newattrs[key], True)
  182. newslotdata = None
  183. if self.slotData:
  184. newslotdata = self.slotData.copy()
  185. for key in newslotdata:
  186. newslotdata[key] = self._clone(newslotdata[key], True)
  187. newtag = Tag(
  188. self.tagName,
  189. attributes=newattrs,
  190. children=newchildren,
  191. render=self.render,
  192. filename=self.filename,
  193. lineNumber=self.lineNumber,
  194. columnNumber=self.columnNumber)
  195. newtag.slotData = newslotdata
  196. return newtag
  197. def clear(self):
  198. """
  199. Clear any existing children from this tag.
  200. """
  201. self.children = []
  202. return self
  203. def __repr__(self):
  204. rstr = ''
  205. if self.attributes:
  206. rstr += ', attributes=%r' % self.attributes
  207. if self.children:
  208. rstr += ', children=%r' % self.children
  209. return "Tag(%r%s)" % (self.tagName, rstr)
  210. voidElements = ('img', 'br', 'hr', 'base', 'meta', 'link', 'param', 'area',
  211. 'input', 'col', 'basefont', 'isindex', 'frame', 'command',
  212. 'embed', 'keygen', 'source', 'track', 'wbs')
  213. class CDATA(object):
  214. """
  215. A C{<![CDATA[]]>} block from a template. Given a separate representation in
  216. the DOM so that they may be round-tripped through rendering without losing
  217. information.
  218. @ivar data: The data between "C{<![CDATA[}" and "C{]]>}".
  219. @type data: C{unicode}
  220. """
  221. def __init__(self, data):
  222. self.data = data
  223. def __repr__(self):
  224. return 'CDATA(%r)' % (self.data,)
  225. class Comment(object):
  226. """
  227. A C{<!-- -->} comment from a template. Given a separate representation in
  228. the DOM so that they may be round-tripped through rendering without losing
  229. information.
  230. @ivar data: The data between "C{<!--}" and "C{-->}".
  231. @type data: C{unicode}
  232. """
  233. def __init__(self, data):
  234. self.data = data
  235. def __repr__(self):
  236. return 'Comment(%r)' % (self.data,)
  237. class CharRef(object):
  238. """
  239. A numeric character reference. Given a separate representation in the DOM
  240. so that non-ASCII characters may be output as pure ASCII.
  241. @ivar ordinal: The ordinal value of the unicode character to which this is
  242. object refers.
  243. @type ordinal: C{int}
  244. @since: 12.0
  245. """
  246. def __init__(self, ordinal):
  247. self.ordinal = ordinal
  248. def __repr__(self):
  249. return "CharRef(%d)" % (self.ordinal,)