comparison emeraldtree/ElementPath.py @ 1:34be1290ac0b

Rename module. * PKG-INFO, setup.py: Update names and description. * emeraldtree: Move from elementtree.
author Bastian Blank <bblank@thinkmo.de>
date Sat, 31 May 2008 11:23:30 +0200
parents elementtree/ElementPath.py@5169fce2d144
children 7b33b90de8be
comparison
equal deleted inserted replaced
0:5169fce2d144 1:34be1290ac0b
1 #
2 # ElementTree
3 # $Id: ElementPath.py 3276 2007-09-12 06:52:30Z fredrik $
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 ##
47 # Implementation module for XPath support. There's usually no reason
48 # to import this module directly; the <b>ElementTree</b> does this for
49 # you, if needed.
50 ##
51
52 import re
53
54 xpath_tokenizer = re.compile(
55 "("
56 "'[^']*'|\"[^\"]*\"|"
57 "::|"
58 "//?|"
59 "\.\.|"
60 "\(\)|"
61 "[/.*:\[\]\(\)@=])|"
62 "((?:\{[^}]+\})?[^/:\[\]\(\)@=\s]+)|"
63 "\s+"
64 ).findall
65
66 def prepare_tag(next, token):
67 tag = token[1]
68 def select(context, result):
69 for elem in result:
70 for e in elem:
71 if e.tag == tag:
72 yield e
73 return select
74
75 def prepare_star(next, token):
76 def select(context, result):
77 for elem in result:
78 for e in elem:
79 yield e
80 return select
81
82 def prepare_dot(next, token):
83 def select(context, result):
84 for elem in result:
85 yield elem
86 return select
87
88 def prepare_iter(next, token):
89 token = next()
90 if token[0] == "*":
91 tag = "*"
92 elif not token[0]:
93 tag = token[1]
94 else:
95 raise SyntaxError
96 def select(context, result):
97 for elem in result:
98 for e in elem.iter(tag):
99 if e is not elem:
100 yield e
101 return select
102
103 def prepare_dot_dot(next, token):
104 def select(context, result):
105 parent_map = context.parent_map
106 if parent_map is None:
107 context.parent_map = parent_map = {}
108 for p in context.root.iter():
109 for e in p:
110 parent_map[e] = p
111 for elem in result:
112 if elem in parent_map:
113 yield parent_map[elem]
114 return select
115
116 def prepare_predicate(next, token):
117 # this one should probably be refactored...
118 token = next()
119 if token[0] == "@":
120 # attribute
121 token = next()
122 if token[0]:
123 raise SyntaxError("invalid attribute predicate")
124 key = token[1]
125 token = next()
126 if token[0] == "]":
127 def select(context, result):
128 for elem in result:
129 if elem.get(key) is not None:
130 yield elem
131 elif token[0] == "=":
132 value = next()[0]
133 if value[:1] == "'" or value[:1] == '"':
134 value = value[1:-1]
135 else:
136 raise SyntaxError("invalid comparision target")
137 token = next()
138 def select(context, result):
139 for elem in result:
140 if elem.get(key) == value:
141 yield elem
142 if token[0] != "]":
143 raise SyntaxError("invalid attribute predicate")
144 elif not token[0]:
145 tag = token[1]
146 token = next()
147 if token[0] != "]":
148 raise SyntaxError("invalid node predicate")
149 def select(context, result):
150 for elem in result:
151 if elem.find(tag) is not None:
152 yield elem
153 else:
154 raise SyntaxError("invalid predicate")
155 return select
156
157 ops = {
158 "": prepare_tag,
159 "*": prepare_star,
160 ".": prepare_dot,
161 "..": prepare_dot_dot,
162 "//": prepare_iter,
163 "[": prepare_predicate,
164 }
165
166 _cache = {}
167
168 class _SelectorContext:
169 parent_map = None
170 def __init__(self, root):
171 self.root = root
172
173 # --------------------------------------------------------------------
174
175 ##
176 # Find first matching object.
177
178 def find(elem, path):
179 try:
180 return findall(elem, path).next()
181 except StopIteration:
182 return None
183
184 ##
185 # Find all matching objects.
186
187 def findall(elem, path):
188 # compile selector pattern
189 try:
190 selector = _cache[path]
191 except KeyError:
192 if len(_cache) > 100:
193 _cache.clear()
194 if path[:1] == "/":
195 raise SyntaxError("cannot use absolute path on element")
196 stream = iter(xpath_tokenizer(path))
197 next = stream.next; token = next()
198 selector = []
199 while 1:
200 try:
201 selector.append(ops[token[0]](next, token))
202 except StopIteration:
203 raise SyntaxError("invalid path")
204 try:
205 token = next()
206 if token[0] == "/":
207 token = next()
208 except StopIteration:
209 break
210 _cache[path] = selector
211 # execute selector pattern
212 result = [elem]
213 context = _SelectorContext(elem)
214 for select in selector:
215 result = select(context, result)
216 return result
217
218 ##
219 # Find text for first matching object.
220
221 def findtext(elem, path, default=None):
222 try:
223 elem = findall(elem, path).next()
224 return elem.text
225 except StopIteration:
226 return default