163 lines
5.0 KiB
Python
163 lines
5.0 KiB
Python
from xpath.exceptions import *
|
|
import xpath.exceptions
|
|
import xpath.expr
|
|
import xpath.parser
|
|
import xpath.yappsrt
|
|
|
|
__all__ = ['find', 'findnode', 'findvalue', 'XPathContext', 'XPath']
|
|
__all__.extend((x for x in dir(xpath.exceptions) if not x.startswith('_')))
|
|
|
|
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.find(expr, node, context=self, **kwargs)
|
|
|
|
@api
|
|
def findnode(self, expr, node, **kwargs):
|
|
return xpath.findnode(expr, node, context=self, **kwargs)
|
|
|
|
@api
|
|
def findvalue(self, expr, node, **kwargs):
|
|
return xpath.findvalue(expr, node, context=self, **kwargs)
|
|
|
|
@api
|
|
def findvalues(self, expr, node, **kwargs):
|
|
return xpath.findvalues(expr, node, context=self, **kwargs)
|
|
|
|
class XPath():
|
|
_max_cache = 100
|
|
_cache = {}
|
|
|
|
def __init__(self, expr):
|
|
"""Init docs.
|
|
"""
|
|
try:
|
|
parser = xpath.parser.XPath(xpath.parser.XPathScanner(str(expr)))
|
|
self.expr = parser.XPath()
|
|
except xpath.yappsrt.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 xpath.expr.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 xpath.expr.nodesetp(result):
|
|
if len(result) == 0:
|
|
return None
|
|
result = xpath.expr.string(result)
|
|
return result
|
|
|
|
@api
|
|
def findvalues(self, node, context=None, **kwargs):
|
|
result = self.find(node, context, **kwargs)
|
|
if not xpath.expr.nodesetp(result):
|
|
raise XPathTypeError("expression is not a node-set")
|
|
return [xpath.expr.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)
|
|
|
|
@api
|
|
def find(expr, node, **kwargs):
|
|
return XPath.get(expr).find(node, **kwargs)
|
|
|
|
@api
|
|
def findnode(expr, node, **kwargs):
|
|
return XPath.get(expr).findnode(node, **kwargs)
|
|
|
|
@api
|
|
def findvalue(expr, node, **kwargs):
|
|
return XPath.get(expr).findvalue(node, **kwargs)
|
|
|
|
@api
|
|
def findvalues(expr, node, **kwargs):
|
|
return XPath.get(expr).findvalues(node, **kwargs)
|