| # | 
 | # ElementTree | 
 | # $Id: ElementPath.py 1858 2004-06-17 21:31:41Z Fredrik $ | 
 | # | 
 | # limited xpath support for element trees | 
 | # | 
 | # history: | 
 | # 2003-05-23 fl   created | 
 | # 2003-05-28 fl   added support for // etc | 
 | # 2003-08-27 fl   fixed parsing of periods in element names | 
 | # | 
 | # Copyright (c) 2003-2004 by Fredrik Lundh.  All rights reserved. | 
 | # | 
 | # fredrik@pythonware.com | 
 | # http://www.pythonware.com | 
 | # | 
 | # -------------------------------------------------------------------- | 
 | # The ElementTree toolkit is | 
 | # | 
 | # Copyright (c) 1999-2004 by Fredrik Lundh | 
 | # | 
 | # By obtaining, using, and/or copying this software and/or its | 
 | # associated documentation, you agree that you have read, understood, | 
 | # and will comply with the following terms and conditions: | 
 | # | 
 | # Permission to use, copy, modify, and distribute this software and | 
 | # its associated documentation for any purpose and without fee is | 
 | # hereby granted, provided that the above copyright notice appears in | 
 | # all copies, and that both that copyright notice and this permission | 
 | # notice appear in supporting documentation, and that the name of | 
 | # Secret Labs AB or the author not be used in advertising or publicity | 
 | # pertaining to distribution of the software without specific, written | 
 | # prior permission. | 
 | # | 
 | # SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD | 
 | # TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- | 
 | # ABILITY AND FITNESS.  IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR | 
 | # BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY | 
 | # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, | 
 | # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS | 
 | # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | 
 | # OF THIS SOFTWARE. | 
 | # -------------------------------------------------------------------- | 
 |  | 
 | # Licensed to PSF under a Contributor Agreement. | 
 | # See http://www.python.org/2.4/license for licensing details. | 
 |  | 
 | ## | 
 | # Implementation module for XPath support.  There's usually no reason | 
 | # to import this module directly; the <b>ElementTree</b> does this for | 
 | # you, if needed. | 
 | ## | 
 |  | 
 | import re | 
 |  | 
 | xpath_tokenizer = re.compile( | 
 |     "(::|\.\.|\(\)|[/.*:\[\]\(\)@=])|((?:\{[^}]+\})?[^/:\[\]\(\)@=\s]+)|\s+" | 
 |     ).findall | 
 |  | 
 | class xpath_descendant_or_self: | 
 |     pass | 
 |  | 
 | ## | 
 | # Wrapper for a compiled XPath. | 
 |  | 
 | class Path: | 
 |  | 
 |     ## | 
 |     # Create an Path instance from an XPath expression. | 
 |  | 
 |     def __init__(self, path): | 
 |         tokens = xpath_tokenizer(path) | 
 |         # the current version supports 'path/path'-style expressions only | 
 |         self.path = [] | 
 |         self.tag = None | 
 |         if tokens and tokens[0][0] == "/": | 
 |             raise SyntaxError("cannot use absolute path on element") | 
 |         while tokens: | 
 |             op, tag = tokens.pop(0) | 
 |             if tag or op == "*": | 
 |                 self.path.append(tag or op) | 
 |             elif op == ".": | 
 |                 pass | 
 |             elif op == "/": | 
 |                 self.path.append(xpath_descendant_or_self()) | 
 |                 continue | 
 |             else: | 
 |                 raise SyntaxError("unsupported path syntax (%s)" % op) | 
 |             if tokens: | 
 |                 op, tag = tokens.pop(0) | 
 |                 if op != "/": | 
 |                     raise SyntaxError( | 
 |                         "expected path separator (%s)" % (op or tag) | 
 |                         ) | 
 |         if self.path and isinstance(self.path[-1], xpath_descendant_or_self): | 
 |             raise SyntaxError("path cannot end with //") | 
 |         if len(self.path) == 1 and isinstance(self.path[0], type("")): | 
 |             self.tag = self.path[0] | 
 |  | 
 |     ## | 
 |     # Find first matching object. | 
 |  | 
 |     def find(self, element): | 
 |         tag = self.tag | 
 |         if tag is None: | 
 |             nodeset = self.findall(element) | 
 |             if not nodeset: | 
 |                 return None | 
 |             return nodeset[0] | 
 |         for elem in element: | 
 |             if elem.tag == tag: | 
 |                 return elem | 
 |         return None | 
 |  | 
 |     ## | 
 |     # Find text for first matching object. | 
 |  | 
 |     def findtext(self, element, default=None): | 
 |         tag = self.tag | 
 |         if tag is None: | 
 |             nodeset = self.findall(element) | 
 |             if not nodeset: | 
 |                 return default | 
 |             return nodeset[0].text or "" | 
 |         for elem in element: | 
 |             if elem.tag == tag: | 
 |                 return elem.text or "" | 
 |         return default | 
 |  | 
 |     ## | 
 |     # Find all matching objects. | 
 |  | 
 |     def findall(self, element): | 
 |         nodeset = [element] | 
 |         index = 0 | 
 |         while 1: | 
 |             try: | 
 |                 path = self.path[index] | 
 |                 index = index + 1 | 
 |             except IndexError: | 
 |                 return nodeset | 
 |             set = [] | 
 |             if isinstance(path, xpath_descendant_or_self): | 
 |                 try: | 
 |                     tag = self.path[index] | 
 |                     if not isinstance(tag, type("")): | 
 |                         tag = None | 
 |                     else: | 
 |                         index = index + 1 | 
 |                 except IndexError: | 
 |                     tag = None # invalid path | 
 |                 for node in nodeset: | 
 |                     new = list(node.getiterator(tag)) | 
 |                     if new and new[0] is node: | 
 |                         set.extend(new[1:]) | 
 |                     else: | 
 |                         set.extend(new) | 
 |             else: | 
 |                 for node in nodeset: | 
 |                     for node in node: | 
 |                         if path == "*" or node.tag == path: | 
 |                             set.append(node) | 
 |             if not set: | 
 |                 return [] | 
 |             nodeset = set | 
 |  | 
 | _cache = {} | 
 |  | 
 | ## | 
 | # (Internal) Compile path. | 
 |  | 
 | def _compile(path): | 
 |     p = _cache.get(path) | 
 |     if p is not None: | 
 |         return p | 
 |     p = Path(path) | 
 |     if len(_cache) >= 100: | 
 |         _cache.clear() | 
 |     _cache[path] = p | 
 |     return p | 
 |  | 
 | ## | 
 | # Find first matching object. | 
 |  | 
 | def find(element, path): | 
 |     return _compile(path).find(element) | 
 |  | 
 | ## | 
 | # Find text for first matching object. | 
 |  | 
 | def findtext(element, path, default=None): | 
 |     return _compile(path).findtext(element, default) | 
 |  | 
 | ## | 
 | # Find all matching objects. | 
 |  | 
 | def findall(element, path): | 
 |     return _compile(path).findall(element) |