PageRenderTime 46ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/tests/etree13/ElementPath.py

https://bitbucket.org/birkenfeld/sphinx/
Python | 226 lines | 165 code | 10 blank | 51 comment | 34 complexity | 8960cc7f5281fd738ae46243f10f7c30 MD5 | raw file
Possible License(s): BSD-2-Clause
  1. #
  2. # ElementTree
  3. # $Id$
  4. #
  5. # limited xpath support for element trees
  6. #
  7. # history:
  8. # 2003-05-23 fl created
  9. # 2003-05-28 fl added support for // etc
  10. # 2003-08-27 fl fixed parsing of periods in element names
  11. # 2007-09-10 fl new selection engine
  12. #
  13. # Copyright (c) 2003-2007 by Fredrik Lundh. All rights reserved.
  14. #
  15. # fredrik@pythonware.com
  16. # http://www.pythonware.com
  17. #
  18. # --------------------------------------------------------------------
  19. # The ElementTree toolkit is
  20. #
  21. # Copyright (c) 1999-2007 by Fredrik Lundh
  22. #
  23. # By obtaining, using, and/or copying this software and/or its
  24. # associated documentation, you agree that you have read, understood,
  25. # and will comply with the following terms and conditions:
  26. #
  27. # Permission to use, copy, modify, and distribute this software and
  28. # its associated documentation for any purpose and without fee is
  29. # hereby granted, provided that the above copyright notice appears in
  30. # all copies, and that both that copyright notice and this permission
  31. # notice appear in supporting documentation, and that the name of
  32. # Secret Labs AB or the author not be used in advertising or publicity
  33. # pertaining to distribution of the software without specific, written
  34. # prior permission.
  35. #
  36. # SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
  37. # TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
  38. # ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR
  39. # BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
  40. # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
  41. # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
  42. # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
  43. # OF THIS SOFTWARE.
  44. # --------------------------------------------------------------------
  45. ##
  46. # Implementation module for XPath support. There's usually no reason
  47. # to import this module directly; the <b>ElementTree</b> does this for
  48. # you, if needed.
  49. ##
  50. import re
  51. xpath_tokenizer = re.compile(
  52. "("
  53. "'[^']*'|\"[^\"]*\"|"
  54. "::|"
  55. "//?|"
  56. "\.\.|"
  57. "\(\)|"
  58. "[/.*:\[\]\(\)@=])|"
  59. "((?:\{[^}]+\})?[^/:\[\]\(\)@=\s]+)|"
  60. "\s+"
  61. ).findall
  62. def prepare_tag(next, token):
  63. tag = token[1]
  64. def select(context, result):
  65. for elem in result:
  66. for e in elem:
  67. if e.tag == tag:
  68. yield e
  69. return select
  70. def prepare_star(next, token):
  71. def select(context, result):
  72. for elem in result:
  73. for e in elem:
  74. yield e
  75. return select
  76. def prepare_dot(next, token):
  77. def select(context, result):
  78. for elem in result:
  79. yield elem
  80. return select
  81. def prepare_iter(next, token):
  82. token = next()
  83. if token[0] == "*":
  84. tag = "*"
  85. elif not token[0]:
  86. tag = token[1]
  87. else:
  88. raise SyntaxError
  89. def select(context, result):
  90. for elem in result:
  91. for e in elem.iter(tag):
  92. if e is not elem:
  93. yield e
  94. return select
  95. def prepare_dot_dot(next, token):
  96. def select(context, result):
  97. parent_map = context.parent_map
  98. if parent_map is None:
  99. context.parent_map = parent_map = {}
  100. for p in context.root.iter():
  101. for e in p:
  102. parent_map[e] = p
  103. for elem in result:
  104. if elem in parent_map:
  105. yield parent_map[elem]
  106. return select
  107. def prepare_predicate(next, token):
  108. # this one should probably be refactored...
  109. token = next()
  110. if token[0] == "@":
  111. # attribute
  112. token = next()
  113. if token[0]:
  114. raise SyntaxError("invalid attribute predicate")
  115. key = token[1]
  116. token = next()
  117. if token[0] == "]":
  118. def select(context, result):
  119. for elem in result:
  120. if elem.get(key) is not None:
  121. yield elem
  122. elif token[0] == "=":
  123. value = next()[0]
  124. if value[:1] == "'" or value[:1] == '"':
  125. value = value[1:-1]
  126. else:
  127. raise SyntaxError("invalid comparision target")
  128. token = next()
  129. def select(context, result):
  130. for elem in result:
  131. if elem.get(key) == value:
  132. yield elem
  133. if token[0] != "]":
  134. raise SyntaxError("invalid attribute predicate")
  135. elif not token[0]:
  136. tag = token[1]
  137. token = next()
  138. if token[0] != "]":
  139. raise SyntaxError("invalid node predicate")
  140. def select(context, result):
  141. for elem in result:
  142. if elem.find(tag) is not None:
  143. yield elem
  144. else:
  145. raise SyntaxError("invalid predicate")
  146. return select
  147. ops = {
  148. "": prepare_tag,
  149. "*": prepare_star,
  150. ".": prepare_dot,
  151. "..": prepare_dot_dot,
  152. "//": prepare_iter,
  153. "[": prepare_predicate,
  154. }
  155. _cache = {}
  156. class _SelectorContext:
  157. parent_map = None
  158. def __init__(self, root):
  159. self.root = root
  160. # --------------------------------------------------------------------
  161. ##
  162. # Find first matching object.
  163. def find(elem, path):
  164. try:
  165. return next(findall(elem, path))
  166. except StopIteration:
  167. return None
  168. ##
  169. # Find all matching objects.
  170. def findall(elem, path):
  171. # compile selector pattern
  172. try:
  173. selector = _cache[path]
  174. except KeyError:
  175. if len(_cache) > 100:
  176. _cache.clear()
  177. if path[:1] == "/":
  178. raise SyntaxError("cannot use absolute path on element")
  179. stream = iter(xpath_tokenizer(path))
  180. next_ = lambda: next(stream); token = next_()
  181. selector = []
  182. while 1:
  183. try:
  184. selector.append(ops[token[0]](next_, token))
  185. except StopIteration:
  186. raise SyntaxError("invalid path")
  187. try:
  188. token = next_()
  189. if token[0] == "/":
  190. token = next_()
  191. except StopIteration:
  192. break
  193. _cache[path] = selector
  194. # execute selector pattern
  195. result = [elem]
  196. context = _SelectorContext(elem)
  197. for select in selector:
  198. result = select(context, result)
  199. return result
  200. ##
  201. # Find text for first matching object.
  202. def findtext(elem, path, default=None):
  203. try:
  204. elem = next(findall(elem, path))
  205. return elem.text
  206. except StopIteration:
  207. return default