Viewing file: ParsedExpr.py (32.72 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
######################################################################## # $Header: /var/local/cvsroot/4Suite/Ft/Xml/XPath/ParsedExpr.py,v 1.44 2005/04/14 18:30:13 jkloth Exp $ """ The implementation of parsed XPath expression tokens.
Copyright 2005 Fourthought, Inc. (USA). Detailed license and copyright information: http://4suite.org/COPYRIGHT Project home, documentation, distributions: http://4suite.org/ """
import types, inspect from xml.dom import Node
from Ft.Lib import boolean, number from Ft.Lib.Set import Union, Unique from Ft.Xml import EMPTY_NAMESPACE from Ft.Xml.Lib.XmlString import SplitQName, XmlStrStrip from Ft.Xml.XPath import RuntimeException from Ft.Xml.XPath import ParsedStep, ParsedAxisSpecifier, ParsedNodeTest from Ft.Xml.XPath import Conversions, Types, _comparisons
class ParsedLiteralExpr: """ An object representing a string literal expression (XPath 1.0 grammar production 29: Literal) """ def __init__(self, literal): if len(literal) >= 2 and (literal[0] in ['\'', '"'] and literal[0] == literal[-1]): literal = literal[1:-1] self._literal = literal
def evaluate(self, context): return self._literal
def pprint(self, indent=''): print indent + str(self)
def __str__(self): return '<Literal at %x: %s>' % (id(self), repr(self))
def __repr__(self): return '"' + self._literal + '"'
class ParsedNLiteralExpr(ParsedLiteralExpr): """ An object representing a numeric literal expression (XPath 1.0 grammar production 30: Number) """ def __init__(self, nliteral): self._nliteral = nliteral self._literal = float(nliteral)
def pprint(self, indent=''): print indent + str(self)
def __str__(self): return '<Number at %x: %s>' % (id(self), repr(self))
def __repr__(self): if number.isnan(self._literal): return 'NaN' elif number.isinf(self._literal): if self._literal < 0: return '-Infinity' else: return 'Infinity' else: return str(self._nliteral)
class ParsedVariableReferenceExpr: """ An object representing a variable reference expression (XPath 1.0 grammar production 36: VariableReference) """ def __init__(self,name): self._name = name self._key = SplitQName(name[1:]) return
def evaluate(self, context): """Returns a string""" (prefix, local) = self._key if prefix: try: expanded = (context.processorNss[prefix], local) except KeyError: raise RuntimeException(RuntimeException.UNDEFINED_PREFIX, prefix) else: expanded = self._key try: return context.varBindings[expanded] except KeyError: raise RuntimeException(RuntimeException.UNDEFINED_VARIABLE, expanded[0], expanded[1])
def pprint(self, indent=''): print indent + str(self)
def __str__(self): return '<Variable at %x: %s>' % (id(self), repr(self))
def __repr__(self): return self._name
def ParsedFunctionCallExpr(name, args): """ Returns an object representing a function call expression (XPath 1.0 grammar production 16: FunctionCall) """ name = XmlStrStrip(name) key = SplitQName(name) if not args: return FunctionCall(name, key, args) count = len(args) if count == 1: return FunctionCall1(name, key, args) elif count == 2: return FunctionCall2(name, key, args) elif count == 3: return FunctionCall3(name, key, args) else: return FunctionCallN(name, key, args)
class FunctionCall: """ An object representing a function call expression (XPath 1.0 grammar production 16: FunctionCall) """ def __init__(self, name, key, args): self._name = name self._key = key self._args = args self._func = None
def pprint(self, indent=''): print indent + str(self) for arg in self._args: arg.pprint(indent + ' ')
def error(self, *args): raise RuntimeException(RuntimeException.UNDEFINED_FUNCTION, self._name)
def getArgumentError(self): if not inspect.isfunction(self._func): # We can only check for argument errors with Python functions return None
argcount = len(self._args)
args, vararg, kwarg = inspect.getargs(self._func.func_code) defaults = self._func.func_defaults or ()
# Don't count the context parameter in argument count maxargs = len(args) - 1 minargs = maxargs - len(defaults)
if argcount > maxargs and not vararg: if maxargs == 0: return RuntimeException(RuntimeException.ARGCOUNT_NONE, self._name, argcount) elif defaults: return RuntimeException(RuntimeException.ARGCOUNT_ATMOST, self._name, maxargs, argcount) else: return RuntimeException(RuntimeException.ARGCOUNT_EXACT, self._name, maxargs, argcount) elif argcount < minargs: if defaults or vararg: return RuntimeException(RuntimeException.ARGCOUNT_ATLEAST, self._name, minargs, argcount) else: return RuntimeException(RuntimeException.ARGCOUNT_EXACT, self._name, minargs, argcount)
# Not an error with arg counts for this function, use current error return None
def evaluate(self, context): """Returns the result of calling the function""" func = self._func if func is None: (prefix, local) = self._key if prefix: try: expanded = (context.processorNss[prefix], local) except: raise RuntimeException(RuntimeException.UNDEFINED_PREFIX, prefix) else: expanded = self._key func = context.functions.get(expanded, self.error) if not ('nocache' in func.__dict__ and func.nocache): self._func = func try: result = func(context) except TypeError: exception = self.getArgumentError() if not exception: # use existing exception (empty raise keeps existing backtrace) raise raise exception
#Expensive assert contributed by Adam Souzis. #No effect on Python running in optimized mode, #But would mean significant slowdown for developers, so disabled by default #assert not isinstance(result, list) or len(result) == len(Set.Unique(result)) return result
def __getinitargs__(self): return (self._name, self._key, self._args)
def __getstate__(self): state = vars(self).copy() del state['_func'] return state
def __str__(self): return '<%s at %x: %s>' % (self.__class__.__name__, id(self), repr(self))
def __repr__(self): result = self._name + '(' if len(self._args): result = result + repr(self._args[0]) for arg in self._args[1:]: result = result + ', ' + repr(arg) return result + ')'
class FunctionCall1(FunctionCall): # a function call with 1 argument def __init__(self, name, key, args): FunctionCall.__init__(self, name, key, args) self._arg0 = args[0]
def evaluate(self, context): arg0 = self._arg0.evaluate(context) func = self._func if func is None: (prefix, local) = self._key if prefix: try: expanded = (context.processorNss[prefix], local) except: raise RuntimeException(RuntimeException.UNDEFINED_PREFIX, prefix) else: expanded = self._key func = context.functions.get(expanded, self.error) if not ('nocache' in func.__dict__ and func.nocache): self._func = func try: result = func(context, arg0) except TypeError: exception = self.getArgumentError() if not exception: # use existing exception (empty raise keeps existing backtrace) raise raise exception
#Expensive assert contributed by Adam Souzis. #No effect on Python running in optimized mode, #But would mean significant slowdown for developers, so disabled by default #assert not isinstance(result, list) or len(result) == len(Set.Unique(result)) return result
class FunctionCall2(FunctionCall): # a function call with 2 arguments def __init__(self, name, key, args): FunctionCall.__init__(self, name, key, args) self._arg0 = args[0] self._arg1 = args[1]
def evaluate(self, context): arg0 = self._arg0.evaluate(context) arg1 = self._arg1.evaluate(context) func = self._func if func is None: (prefix, local) = self._key if prefix: try: expanded = (context.processorNss[prefix], local) except: raise RuntimeException(RuntimeException.UNDEFINED_PREFIX, prefix) else: expanded = self._key func = context.functions.get(expanded, self.error) if not ('nocache' in func.__dict__ and func.nocache): self._func = func try: result = func(context, arg0, arg1) except TypeError: exception = self.getArgumentError() if not exception: # use existing exception (empty raise keeps existing backtrace) raise raise exception
#Expensive assert contributed by Adam Souzis. #No effect on Python running in optimized mode, #But would mean significant slowdown for developers, so disabled by default #assert not isinstance(result, list) or len(result) == len(Set.Unique(result)) return result
class FunctionCall3(FunctionCall): # a function call with 3 arguments def __init__(self, name, key, args): FunctionCall.__init__(self, name, key, args) self._arg0 = args[0] self._arg1 = args[1] self._arg2 = args[2]
def evaluate(self, context): arg0 = self._arg0.evaluate(context) arg1 = self._arg1.evaluate(context) arg2 = self._arg2.evaluate(context) func = self._func if func is None: (prefix, local) = self._key if prefix: try: expanded = (context.processorNss[prefix], local) except: raise RuntimeException(RuntimeException.UNDEFINED_PREFIX, prefix) else: expanded = self._key func = context.functions.get(expanded, self.error) if not ('nocache' in func.__dict__ and func.nocache): self._func = func try: result = func(context, arg0, arg1, arg2) except TypeError: exception = self.getArgumentError() if not exception: # use existing exception (empty raise keeps existing backtrace) raise raise exception
#Expensive assert contributed by Adam Souzis. #No effect on Python running in optimized mode, #But would mean significant slowdown for developers, so disabled by default #assert not isinstance(result, list) or len(result) == len(Set.Unique(result)) return result
class FunctionCallN(FunctionCall): # a function call with more than 3 arguments def __init__(self, name, key, args): FunctionCall.__init__(self, name, key, args)
def evaluate(self, context): args = map(lambda arg, c=context: arg.evaluate(c), self._args) func = self._func if func is None: (prefix, local) = self._key if prefix: try: expanded = (context.processorNss[prefix], local) except: raise RuntimeException(RuntimeException.UNDEFINED_PREFIX, prefix) else: expanded = self._key func = context.functions.get(expanded, self.error) if not ('nocache' in func.__dict__ and func.nocache): self._func = func try: result = func(context, *args) except TypeError: exception = self.getArgumentError() if not exception: # use existing exception (empty raise keeps existing backtrace) raise raise exception
#Expensive assert contributed by Adam Souzis. #No effect on Python running in optimized mode, #But would mean significant slowdown for developers, so disabled by default #assert not isinstance(result, list) or len(result) == len(Set.Unique(result)) return result
######################################################################## # Nodeset Expressions # Expressions that only return nodesets
class ParsedUnionExpr: """ An object representing a union expression (XPath 1.0 grammar production 18: UnionExpr) """ def __init__(self,left,right): self._left = left self._right = right
def pprint(self, indent=''): print indent + str(self) self._left.pprint(indent + ' ') self._right.pprint(indent + ' ')
def evaluate(self, context): """Returns a node-set""" left = self._left.evaluate(context) if not isinstance(left, Types.NodesetType): raise TypeError("%s must be a node-set, not a %s" % (repr(self._left), Types.g_xpathPrimitiveTypes.get(type(left), type(left).__name__)))
right = self._right.evaluate(context) if not isinstance(right, Types.NodesetType): raise TypeError("%s must be a node-set, not a %s" % (repr(self._right), Types.g_xpathPrimitiveTypes.get(type(right), type(right).__name__)))
set = Union(left, right) return set
def __str__(self): return '<UnionExpr at %x: %s>' % (id(self), repr(self))
def __repr__(self): return repr(self._left) + ' | ' + repr(self._right)
class ParsedPathExpr: """ An object representing a path expression (XPath 1.0 grammar production 19: PathExpr) """ def __init__(self, descendant, left, right): self._descendant = descendant self._left = left self._right = right return
def pprint(self, indent=''): print indent + str(self) self._left.pprint(indent + ' ') self._right.pprint(indent + ' ')
def _descendants(self, context, nodeset): for child in context.node.childNodes: context.node = child results = self._right.select(context) if not isinstance(results, Types.NodesetType): raise TypeError("%r must be a node-set, not a %s" % ( self._right, Types.g_xpathPrimitiveTypes.get(type(results), type(results).__name__))) if results: nodeset.extend(results) if child.nodeType == Node.ELEMENT_NODE: nodeset = self._descendants(context, nodeset) return nodeset
def evaluate(self, context): """Returns a node-set""" #Evaluate the left, then, if op =//, the parsedStep. #Then evaluate the right, pushing the context each time. left = self._left.evaluate(context) if not isinstance(left, Types.NodesetType): raise TypeError("%r must be a node-set, not a %s" % ( self._left, Types.g_xpathPrimitiveTypes.get(type(left), type(left).__name__)))
state = context.copy()
results = [] for node in left: context.node = node nodeset = self._right.select(context) if not isinstance(nodeset, Types.NodesetType): raise TypeError("%r must be a node-set, not a %s" % ( self._right, Types.g_xpathPrimitiveTypes.get(type(nodeset), type(nodeset).__name__))) elif self._descendant: nodeset = self._descendants(context, nodeset) results.extend(nodeset)
results = Unique(results)
context.set(state) return results
def __str__(self): return '<PathExpr at %x: %s>' % (id(self), repr(self))
def __repr__(self): op = self._descendant and '//' or '/' return repr(self._left) + op + repr(self._right)
class ParsedFilterExpr: """ An object representing a filter expression (XPath 1.0 grammar production 20: FilterExpr) """ def __init__(self, filter_, predicates): self._filter = filter_ self._predicates = predicates return
def evaluate(self, context): """Returns a node-set""" #Evaluate our filter into a node set, filter that through the predicates. nodeset = self._filter.evaluate(context) if not isinstance(nodeset, Types.NodesetType): raise TypeError("%s must be a node-set, not a %s" % (repr(self._filter), Types.g_xpathPrimitiveTypes.get(type(nodeset), type(nodeset).__name__)))
if nodeset: #In filter expressions, predicates are evaluated #with respect to the child axis, (XPath 2.4) therefore we must #sort into doc order before applying nodeset.sort() nodeset = self._predicates.filter(nodeset, context, reverse=0) return nodeset
def pprint(self, indent=''): print indent + str(self) self._filter.pprint(indent + ' ') self._predicates.pprint(indent + ' ')
def shiftContext(self,context,index,set,len,func): return func(context)
def __str__(self): return '<FilterExpr at %x: %s>' % (id(self), repr(self))
def __repr__(self): return repr(self._filter) + repr(self._predicates)
######################################################################## # Boolean Expressions # Expressions that only return booleans
class ParsedOrExpr: """ An object representing an or expression (XPath 1.0 grammar production 21: OrExpr) """ def __init__(self, left, right): self._left = left self._right = right
def pprint(self, indent=''): print indent + str(self) self._left.pprint(indent + ' ') self._right.pprint(indent + ' ')
def evaluate(self, context): """Returns a boolean""" rt = Conversions.BooleanValue(self._left.evaluate(context)) if not rt: rt = Conversions.BooleanValue(self._right.evaluate(context)) return rt
def __str__(self): return '<OrExpr at %x: %s>' % (id(self), repr(self))
def __repr__(self): return repr(self._left) +' or ' + repr(self._right)
class ParsedAndExpr: """ An object representing an and expression (XPath 1.0 grammar production 22: AndExpr) """ def __init__(self,left,right): self._left = left self._right = right
def pprint(self, indent=''): print indent + str(self) self._left.pprint(indent + ' ') self._right.pprint(indent + ' ')
def evaluate(self, context): """Returns a boolean"""
rt = Conversions.BooleanValue(self._left.evaluate(context)) if rt: rt = Conversions.BooleanValue(self._right.evaluate(context)) return rt
def __str__(self): return '<AndExpr at %x: %s>' % (id(self), repr(self))
def __repr__(self): return repr(self._left) + ' and ' + repr(self._right)
def _nodeset_compare(compare, a, b, relational=False): """ Applies a comparison function to node-sets a and b in order to evaluate equality (=, !=) and relational (<, <=, >=, >) expressions in which both objects to be compared are node-sets.
Returns an XPath boolean indicating the result of the comparison. """ if isinstance(a, Types.NodesetType) and isinstance(b, Types.NodesetType): # From XPath 1.0 Section 3.4: # If both objects to be compared are node-sets, then the comparison # will be true if and only if there is a node in the first node-set # and a node in the second node-set such that the result of # performing the comparison on the string-values of the two nodes # is true. # # (Note, however, that relational comparisons convert their operands # to numbers.) if relational: if not(a and b): # relational, and at least one of the node-sets is empty return boolean.false else: # relational, and neither node-set is empty coerce = Conversions.NumberValue elif not(a and b): # not relational, and at least one of the node-sets is empty return compare(a, b) and boolean.true or boolean.false else: # not relational, and neither node-set is empty coerce = Conversions.StringValue
# convert the larger set to strings or numbers one time, # then iterate over the smaller set, comparing to each item in # the converted larger set. if len(a) > len(b): coerced = [coerce(node) for node in a] nodes = b else: coerced = [coerce(node) for node in b] nodes = a for node in nodes: current = coerce(node) for value in coerced: if compare(value, current): return boolean.true return boolean.false
# From XPath 1.0 Section 3.4: # If one object to be compared is a node-set and the other is a number, # then the comparison will be true if and only if there is a node in the # node-set such that the result of performing the comparison on the # number to be compared and on the result of converting the string-value # of that node to a number using the number function is true. If one # object to be compared is a node-set and the other is a string, then the # comparison will be true if and only if there is a node in the node-set # such that the result of performing the comparison on the string-value # of the node and the other string is true. If one object to be compared # is a node-set and the other is a boolean, then the comparison will be # true if and only if the result of performing the comparison on the # boolean and on the result of converting the node-set to a boolean using # the boolean function is true. # # (In other words, coerce each node to the same type as the other operand, # then compare them. Note, however, that relational comparisons convert # their operands to numbers.) if isinstance(a, Types.NodesetType): # a is nodeset if isinstance(b, Types.BooleanType): return compare(Conversions.BooleanValue(a), b) \ and boolean.true or boolean.false elif relational: b = Conversions.NumberValue(b) coerce = Conversions.NumberValue elif isinstance(b, Types.NumberType): coerce = Conversions.NumberValue else: b = Conversions.StringValue(b) coerce = Conversions.StringValue for node in a: if compare(coerce(node), b): return boolean.true else: # b is nodeset if isinstance(a, Types.BooleanType): return compare(Conversions.BooleanValue(b), a) \ and boolean.true or boolean.false elif relational: a = Conversions.NumberValue(a) coerce = Conversions.NumberValue elif isinstance(a, Types.NumberType): coerce = Conversions.NumberValue else: a = Conversions.StringValue(a) coerce = Conversions.StringValue for node in b: if compare(a, coerce(node)): return boolean.true
return boolean.false
class ParsedEqualityExpr: """ An object representing an equality expression (XPath 1.0 grammar production 23: EqualityExpr) """ def __init__(self, op, left, right): self._op = op self._left = left self._right = right if op == '=': self._cmp = _comparisons.eq else: self._cmp = _comparisons.ne
def __getstate__(self): return (self._op, self._left, self._right)
def __setstate__(self, state): self.__init__(*state)
def evaluate(self, context): """Returns a boolean""" left = self._left.evaluate(context) right = self._right.evaluate(context)
if isinstance(left, Types.NodesetType) or \ isinstance(right, Types.NodesetType): return _nodeset_compare(self._cmp, left, right)
# From XPath 1.0 Section 3.4: # order for equality expressions when neither is a node-set # 1. either is boolean, both are converted as if by boolean() # 2. either is number, both are converted as if by number() # otherwise, both are converted as if by string() if isinstance(left, Types.BooleanType) or \ isinstance(right, Types.BooleanType): coerce = Conversions.BooleanValue elif isinstance(left, Types.NumberType) or \ isinstance(right, Types.NumberType): coerce = Conversions.NumberValue else: coerce = Conversions.StringValue
result = self._cmp(coerce(left), coerce(right)) return result and boolean.true or boolean.false
def pprint(self, indent=''): print indent + str(self) self._left.pprint(indent + ' ') self._right.pprint(indent + ' ')
def __str__(self): return '<EqualityExpr at %x: %s>' % (id(self), repr(self))
def __repr__(self): if self._op == '=': op = ' = ' else: op = ' != ' return repr(self._left) + op + repr(self._right)
class ParsedRelationalExpr: """ An object representing a relational expression (XPath 1.0 grammar production 24: RelationalExpr) """ def __init__(self, opcode, left, right): self._op = opcode self._left = left self._right = right
if opcode == 0: self._cmp = _comparisons.lt elif opcode == 1: self._cmp = _comparisons.le elif opcode == 2: self._cmp = _comparisons.gt elif opcode == 3: self._cmp = _comparisons.ge return
def __getstate__(self): return (self._op, self._left, self._right)
def __setstate__(self, state): self.__init__(*state)
def evaluate(self, context): """Returns a boolean""" left = self._left.evaluate(context) right = self._right.evaluate(context)
if isinstance(left, Types.NodesetType) or \ isinstance(right, Types.NodesetType): return _nodeset_compare(self._cmp, left, right, relational=True)
a = Conversions.NumberValue(left) b = Conversions.NumberValue(right) return self._cmp(a, b) and boolean.true or boolean.false
def pprint(self, indent=''): print indent + str(self) if type(self._left) == types.InstanceType: self._left.pprint(indent + ' ') else: print indent + ' ' + '<Primitive: %s>' % str(self._left) if type(self._right) == types.InstanceType: self._right.pprint(indent + ' ') else: print indent + ' ' + '<Primitive: %s>' % str(self._right)
def __str__(self): return '<RelationalExpr at %x: %s>' % (id(self), repr(self))
def __repr__(self): if self._op == 0: op = ' < ' elif self._op == 1: op = ' <= ' elif self._op == 2: op = ' > ' elif self._op == 3: op = ' >= ' return repr(self._left) + op + repr(self._right)
######################################################################## # Number Expressions # Expressions that only return numbers (float)
class ParsedAdditiveExpr: """ An object representing an additive expression (XPath 1.0 grammar production 25: AdditiveExpr) """ def __init__(self, sign, left, right): self._sign = sign self._leftLit = 0 self._rightLit = 0 if isinstance(left, ParsedLiteralExpr): self._leftLit = 1 self._left = Conversions.NumberValue(left.evaluate(None)) else: self._left = left if isinstance(right, ParsedLiteralExpr): self._rightLit = 1 self._right = Conversions.NumberValue(right.evaluate(None)) else: self._right = right return
def evaluate(self, context): '''Returns a number''' if self._leftLit: lrt = self._left else: lrt = self._left.evaluate(context) lrt = Conversions.NumberValue(lrt) if self._rightLit: rrt = self._right else: rrt = self._right.evaluate(context) rrt = Conversions.NumberValue(rrt) return lrt + (rrt * self._sign)
def pprint(self, indent=''): print indent + str(self) if type(self._left) == types.InstanceType: self._left.pprint(indent + ' ') else: print indent + ' ' + '<Primitive: %s>' % str(self._left) if type(self._right) == types.InstanceType: self._right.pprint(indent + ' ') else: print indent + ' ' + '<Primitive: %s>' % str(self._right)
def __str__(self): return '<AdditiveExpr at %x: %s>' % (id(self), repr(self))
def __repr__(self): if self._sign > 0: op = ' + ' else: op = ' - ' return repr(self._left) + op + repr(self._right)
class ParsedMultiplicativeExpr: """ An object representing an multiplicative expression (XPath 1.0 grammar production 26: MultiplicativeExpr) """ def __init__(self, opcode, left, right): self._op = opcode self._left = left self._right = right
def evaluate(self, context): '''Returns a number''' lrt = self._left.evaluate(context) lrt = Conversions.NumberValue(lrt) rrt = self._right.evaluate(context) rrt = Conversions.NumberValue(rrt) res = 0 # multiply if self._op == 0: res = lrt * rrt # divide elif self._op == 1: try: res = lrt / rrt except ZeroDivisionError: if lrt < 0: res = -number.inf elif lrt == 0: res = number.nan else: res = number.inf # modulo elif self._op == 2: try: res = lrt % rrt except ZeroDivisionError: res = number.nan return res
def pprint(self, indent=''): print indent + str(self) if type(self._left) == types.InstanceType: self._left.pprint(indent + ' ') else: print indent + ' ' + '<Primitive: %s>' % str(self._left) if type(self._right) == types.InstanceType: self._right.pprint(indent + ' ') else: print indent + ' ' + '<Primitive: %s>' % str(self._right)
def __str__(self): return '<MultiplicativeExpr at %x: %s>' % (id(self), repr(self))
def __repr__(self): if self._op == 0: op = ' * ' elif self._op == 1: op = ' div ' elif self._op == 2: op = ' mod ' return repr(self._left) + op + repr(self._right)
class ParsedUnaryExpr: """ An object representing a unary expression (XPath 1.0 grammar production 27: UnaryExpr) """ def __init__(self, exp): self._exp = exp
def evaluate(self, context): '''Returns a number''' exp = self._exp.evaluate(context) exp = Conversions.NumberValue(exp) return exp * -1.0
def pprint(self, indent=''): print indent + str(self)
def __str__(self): return '<UnaryExpr at %x: %s>' % (id(self), repr(self))
def __repr__(self): return '-' + repr(self._exp)
|