domhelpers.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. # -*- test-case-name: twisted.web.test.test_domhelpers -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. A library for performing interesting tasks with DOM objects.
  6. This module is now deprecated.
  7. """
  8. import warnings
  9. from io import StringIO
  10. from incremental import Version, getVersionString
  11. from twisted.web import microdom
  12. from twisted.web.microdom import escape, getElementsByTagName, unescape
  13. warningString = "twisted.web.domhelpers was deprecated at {}".format(
  14. getVersionString(Version("Twisted", 23, 10, 0))
  15. )
  16. warnings.warn(warningString, DeprecationWarning, stacklevel=3)
  17. # These modules are imported here as a shortcut.
  18. escape
  19. getElementsByTagName
  20. class NodeLookupError(Exception):
  21. pass
  22. def substitute(request, node, subs):
  23. """
  24. Look through the given node's children for strings, and
  25. attempt to do string substitution with the given parameter.
  26. """
  27. for child in node.childNodes:
  28. if hasattr(child, "nodeValue") and child.nodeValue:
  29. child.replaceData(0, len(child.nodeValue), child.nodeValue % subs)
  30. substitute(request, child, subs)
  31. def _get(node, nodeId, nodeAttrs=("id", "class", "model", "pattern")):
  32. """
  33. (internal) Get a node with the specified C{nodeId} as any of the C{class},
  34. C{id} or C{pattern} attributes.
  35. """
  36. if hasattr(node, "hasAttributes") and node.hasAttributes():
  37. for nodeAttr in nodeAttrs:
  38. if str(node.getAttribute(nodeAttr)) == nodeId:
  39. return node
  40. if node.hasChildNodes():
  41. if hasattr(node.childNodes, "length"):
  42. length = node.childNodes.length
  43. else:
  44. length = len(node.childNodes)
  45. for childNum in range(length):
  46. result = _get(node.childNodes[childNum], nodeId)
  47. if result:
  48. return result
  49. def get(node, nodeId):
  50. """
  51. Get a node with the specified C{nodeId} as any of the C{class},
  52. C{id} or C{pattern} attributes. If there is no such node, raise
  53. L{NodeLookupError}.
  54. """
  55. result = _get(node, nodeId)
  56. if result:
  57. return result
  58. raise NodeLookupError(nodeId)
  59. def getIfExists(node, nodeId):
  60. """
  61. Get a node with the specified C{nodeId} as any of the C{class},
  62. C{id} or C{pattern} attributes. If there is no such node, return
  63. L{None}.
  64. """
  65. return _get(node, nodeId)
  66. def getAndClear(node, nodeId):
  67. """Get a node with the specified C{nodeId} as any of the C{class},
  68. C{id} or C{pattern} attributes. If there is no such node, raise
  69. L{NodeLookupError}. Remove all child nodes before returning.
  70. """
  71. result = get(node, nodeId)
  72. if result:
  73. clearNode(result)
  74. return result
  75. def clearNode(node):
  76. """
  77. Remove all children from the given node.
  78. """
  79. node.childNodes[:] = []
  80. def locateNodes(nodeList, key, value, noNesting=1):
  81. """
  82. Find subnodes in the given node where the given attribute
  83. has the given value.
  84. """
  85. returnList = []
  86. if not isinstance(nodeList, type([])):
  87. return locateNodes(nodeList.childNodes, key, value, noNesting)
  88. for childNode in nodeList:
  89. if not hasattr(childNode, "getAttribute"):
  90. continue
  91. if str(childNode.getAttribute(key)) == value:
  92. returnList.append(childNode)
  93. if noNesting:
  94. continue
  95. returnList.extend(locateNodes(childNode, key, value, noNesting))
  96. return returnList
  97. def superSetAttribute(node, key, value):
  98. if not hasattr(node, "setAttribute"):
  99. return
  100. node.setAttribute(key, value)
  101. if node.hasChildNodes():
  102. for child in node.childNodes:
  103. superSetAttribute(child, key, value)
  104. def superPrependAttribute(node, key, value):
  105. if not hasattr(node, "setAttribute"):
  106. return
  107. old = node.getAttribute(key)
  108. if old:
  109. node.setAttribute(key, value + "/" + old)
  110. else:
  111. node.setAttribute(key, value)
  112. if node.hasChildNodes():
  113. for child in node.childNodes:
  114. superPrependAttribute(child, key, value)
  115. def superAppendAttribute(node, key, value):
  116. if not hasattr(node, "setAttribute"):
  117. return
  118. old = node.getAttribute(key)
  119. if old:
  120. node.setAttribute(key, old + "/" + value)
  121. else:
  122. node.setAttribute(key, value)
  123. if node.hasChildNodes():
  124. for child in node.childNodes:
  125. superAppendAttribute(child, key, value)
  126. def gatherTextNodes(iNode, dounescape=0, joinWith=""):
  127. """Visit each child node and collect its text data, if any, into a string.
  128. For example::
  129. >>> doc=microdom.parseString('<a>1<b>2<c>3</c>4</b></a>')
  130. >>> gatherTextNodes(doc.documentElement)
  131. '1234'
  132. With dounescape=1, also convert entities back into normal characters.
  133. @return: the gathered nodes as a single string
  134. @rtype: str"""
  135. gathered = []
  136. gathered_append = gathered.append
  137. slice = [iNode]
  138. while len(slice) > 0:
  139. c = slice.pop(0)
  140. if hasattr(c, "nodeValue") and c.nodeValue is not None:
  141. if dounescape:
  142. val = unescape(c.nodeValue)
  143. else:
  144. val = c.nodeValue
  145. gathered_append(val)
  146. slice[:0] = c.childNodes
  147. return joinWith.join(gathered)
  148. class RawText(microdom.Text):
  149. """This is an evil and horrible speed hack. Basically, if you have a big
  150. chunk of XML that you want to insert into the DOM, but you don't want to
  151. incur the cost of parsing it, you can construct one of these and insert it
  152. into the DOM. This will most certainly only work with microdom as the API
  153. for converting nodes to xml is different in every DOM implementation.
  154. This could be improved by making this class a Lazy parser, so if you
  155. inserted this into the DOM and then later actually tried to mutate this
  156. node, it would be parsed then.
  157. """
  158. def writexml(
  159. self,
  160. writer,
  161. indent="",
  162. addindent="",
  163. newl="",
  164. strip=0,
  165. nsprefixes=None,
  166. namespace=None,
  167. ):
  168. writer.write(f"{indent}{self.data}{newl}")
  169. def findNodes(parent, matcher, accum=None):
  170. if accum is None:
  171. accum = []
  172. if not parent.hasChildNodes():
  173. return accum
  174. for child in parent.childNodes:
  175. # print child, child.nodeType, child.nodeName
  176. if matcher(child):
  177. accum.append(child)
  178. findNodes(child, matcher, accum)
  179. return accum
  180. def findNodesShallowOnMatch(parent, matcher, recurseMatcher, accum=None):
  181. if accum is None:
  182. accum = []
  183. if not parent.hasChildNodes():
  184. return accum
  185. for child in parent.childNodes:
  186. # print child, child.nodeType, child.nodeName
  187. if matcher(child):
  188. accum.append(child)
  189. if recurseMatcher(child):
  190. findNodesShallowOnMatch(child, matcher, recurseMatcher, accum)
  191. return accum
  192. def findNodesShallow(parent, matcher, accum=None):
  193. if accum is None:
  194. accum = []
  195. if not parent.hasChildNodes():
  196. return accum
  197. for child in parent.childNodes:
  198. if matcher(child):
  199. accum.append(child)
  200. else:
  201. findNodes(child, matcher, accum)
  202. return accum
  203. def findElementsWithAttributeShallow(parent, attribute):
  204. """
  205. Return an iterable of the elements which are direct children of C{parent}
  206. and which have the C{attribute} attribute.
  207. """
  208. return findNodesShallow(
  209. parent,
  210. lambda n: getattr(n, "tagName", None) is not None and n.hasAttribute(attribute),
  211. )
  212. def findElements(parent, matcher):
  213. """
  214. Return an iterable of the elements which are children of C{parent} for
  215. which the predicate C{matcher} returns true.
  216. """
  217. return findNodes(
  218. parent,
  219. lambda n, matcher=matcher: getattr(n, "tagName", None) is not None
  220. and matcher(n),
  221. )
  222. def findElementsWithAttribute(parent, attribute, value=None):
  223. if value:
  224. return findElements(
  225. parent,
  226. lambda n, attribute=attribute, value=value: n.hasAttribute(attribute)
  227. and n.getAttribute(attribute) == value,
  228. )
  229. else:
  230. return findElements(
  231. parent, lambda n, attribute=attribute: n.hasAttribute(attribute)
  232. )
  233. def findNodesNamed(parent, name):
  234. return findNodes(parent, lambda n, name=name: n.nodeName == name)
  235. def writeNodeData(node, oldio):
  236. for subnode in node.childNodes:
  237. if hasattr(subnode, "data"):
  238. oldio.write("" + subnode.data)
  239. else:
  240. writeNodeData(subnode, oldio)
  241. def getNodeText(node):
  242. oldio = StringIO()
  243. writeNodeData(node, oldio)
  244. return oldio.getvalue()
  245. def getParents(node):
  246. l = []
  247. while node:
  248. l.append(node)
  249. node = node.parentNode
  250. return l
  251. def namedChildren(parent, nodeName):
  252. """namedChildren(parent, nodeName) -> children (not descendants) of parent
  253. that have tagName == nodeName
  254. """
  255. return [n for n in parent.childNodes if getattr(n, "tagName", "") == nodeName]