import expr as E import parser as P import yappsrt as Y from exceptions import * def api(f): """Decorator for functions and methods that are part of the external module API and that can throw XPathError exceptions. The call stack for these exceptions can be very large, and not very interesting to the user. This decorator rethrows XPathErrors to trim the stack. """ def api_function(*args, **kwargs): try: return f(*args, **kwargs) except XPathError, e: raise e api_function.__name__ = f.__name__ api_function.__doc__ = f.__doc__ return api_function class XPathContext(object): def __init__(self, document=None, **kwargs): self.default_namespace = None self.namespaces = {} self.variables = {} if document is not None: if document.nodeType != document.DOCUMENT_NODE: document = document.ownerDocument if document.documentElement is not None: attrs = document.documentElement.attributes for attr in (attrs.item(i) for i in xrange(attrs.length)): if attr.name == 'xmlns': self.default_namespace = attr.value elif attr.name.startswith('xmlns:'): self.namespaces[attr.name[6:]] = attr.value self.update(**kwargs) def clone(self): dup = XPathContext() dup.default_namespace = self.default_namespace dup.namespaces.update(self.namespaces) dup.variables.update(self.variables) return dup def update(self, default_namespace=None, namespaces=None, variables=None, **kwargs): if default_namespace is not None: self.default_namespace = default_namespace if namespaces is not None: self.namespaces = namespaces if variables is not None: self.variables = variables self.variables.update(kwargs) @api def find(self, expr, node, **kwargs): return XPath.get(expr).find(node, context=self, **kwargs) @api def findnode(self, expr, node, **kwargs): return XPath.get(expr).findnode(node, context=self, **kwargs) @api def findvalue(self, expr, node, **kwargs): return XPath.get(expr).findvalue(node, context=self, **kwargs) @api def findvalues(self, expr, node, **kwargs): return XPath.get(expr).findvalues(node, context=self, **kwargs) class XPath(): _max_cache = 100 _cache = {} def __init__(self, expr): """Init docs. """ try: parser = P.XPath(P.XPathScanner(str(expr))) self.expr = parser.XPath() except Y.SyntaxError, e: raise XPathParseError(str(expr), e.pos, e.msg) @classmethod def get(cls, s): if isinstance(s, cls): return s try: return cls._cache[s] except KeyError: if len(cls._cache) > cls._max_cache: cls._cache.clear() expr = cls(s) cls._cache[s] = expr return expr @api def find(self, node, context=None, **kwargs): if context is None: context = XPathContext(node, **kwargs) elif kwargs: context = context.clone() context.update(**kwargs) return self.expr.evaluate(node, 1, 1, context) @api def findnode(self, node, context=None, **kwargs): result = self.find(node, context, **kwargs) if not E.nodesetp(result): raise XPathTypeError("expression is not a node-set") if len(result) == 0: return None return result[0] @api def findvalue(self, node, context=None, **kwargs): result = self.find(node, context, **kwargs) if E.nodesetp(result): if len(result) == 0: return None result = E.string(result) return result @api def findvalues(self, node, context=None, **kwargs): result = self.find(node, context, **kwargs) if not E.nodesetp(result): raise XPathTypeError("expression is not a node-set") return [E.string_value(x) for x in result] def __repr__(self): return '%s.%s(%s)' % (self.__class__.__module__, self.__class__.__name__, repr(str(self.expr))) def __str__(self): return str(self.expr)