[4suite-checkins] [XPATH_OPTIMIZATIONS-branch] In 4Suite/Ft/Xml/XPath, files BooleanExpressions.py, BooleanFunctions.py, CoreFunctions.py, FunctionCallExpressions.py, NodeSetExpressions.py, NodeSetFunctions.py, NumericExpressions.py, ParsedAbbreviatedAbsoluteLocationPath.py, ParsedAbbreviatedRelativeLocationPath.py, ParsedAbsoluteLocationPath.py, ParsedAxisSpecifier.py, ParsedExpr.py, ParsedNodeTest.py, ParsedPredicateList.py, ParsedRelativeLocationPath.py, ParsedStep.py, StringFunctions.py, XPathTypes.py

Jeremy Kloth jkloth at 4suite.org
Mon Dec 4 00:56:50 MST 2006


Branch: XPATH_OPTIMIZATIONS-branch

Modified Files:
    CoreFunctions.py ParsedAbbreviatedAbsoluteLocationPath.py
    ParsedAbbreviatedRelativeLocationPath.py
    ParsedAbsoluteLocationPath.py ParsedAxisSpecifier.py ParsedExpr.py
    ParsedNodeTest.py ParsedPredicateList.py
    ParsedRelativeLocationPath.py ParsedStep.py XPathTypes.py
Added Files:
    BooleanExpressions.py BooleanFunctions.py FunctionCallExpressions.py
    NodeSetExpressions.py NodeSetFunctions.py NumericExpressions.py
    StringFunctions.py

Log Message:
Preliminary optimizations for XPath. It may or may not work on others'
machines, but it is time that these optimizations see the light of day.
This will be the only way to ensure they work 100% and can be incorporated
into the trunk.

New file: BooleanExpressions.py
ViewCVS view:
  http://cvs.4suite.org/viewcvs/4Suite/Ft/Xml/XPath/BooleanExpressions.py?rev=1.1.2.1&content-type=text/vnd.viewcvs-markup

cvs checkout: cannot find module `BooleanExpressions.py' - ignored
New file: BooleanFunctions.py
ViewCVS view:
  http://cvs.4suite.org/viewcvs/4Suite/Ft/Xml/XPath/BooleanFunctions.py?rev=1.1.2.1&content-type=text/vnd.viewcvs-markup

cvs checkout: cannot find module `BooleanFunctions.py' - ignored
ViewCVS diff:
  http://cvs.4suite.org/viewcvs/4Suite/Ft/Xml/XPath/CoreFunctions.py.diff?r1=1.17.2.1&r2=1.17.2.2
ViewCVS view:
  http://cvs.4suite.org/viewcvs/4Suite/Ft/Xml/XPath/CoreFunctions.py?rev=1.17.2.2&content-type=text/vnd.viewcvs-markup

Index: CoreFunctions.py
===================================================================
RCS file: /var/local/cvsroot/4Suite/Ft/Xml/XPath/CoreFunctions.py,v
retrieving revision 1.17.2.1
retrieving revision 1.17.2.2
diff -U2 -r1.17.2.1 -r1.17.2.2
--- CoreFunctions.py	4 Dec 2006 07:06:59 -0000	1.17.2.1
+++ CoreFunctions.py	4 Dec 2006 07:56:49 -0000	1.17.2.2
@@ -26,4 +26,6 @@
     """Function: <number> last()"""
     return float(context.size)
+Last.arguments = ()
+Last.result = NumberType
 
 
@@ -31,12 +33,17 @@
     """Function: <number> position()"""
     return float(context.position)
+Position.arguments = ()
+Position.result = NumberType
 
 
 def Count(context, nodeSet):
     """Function: <number> count(<node-set>)"""
-    if not isinstance(nodeSet, NodesetType):
+    try:
+        return float(len(nodeSet))
+    except TypeError:
         raise RuntimeException(RuntimeException.WRONG_ARGUMENTS, 'count',
                                _("expected node-set argument"))
-    return float(len(nodeSet))
+Count.arguments = (NodesetType)
+Count.result = NumberType
 
 
@@ -63,11 +70,12 @@
     if nodeSet is None:
         node = context.node
-    elif not isinstance(nodeSet, NodesetType):
-        raise RuntimeException(RuntimeException.WRONG_ARGUMENTS, 'local-name',
-                               _("expected node-set"))
-    elif not nodeSet:
-        return u''
     else:
-        nodeSet.sort()
+        try:
+            nodeSet.sort()
+        except AttributeError:
+            raise RuntimeException(RuntimeException.WRONG_ARGUMENTS,
+                                   'local-name', _("expected node-set"))
+        if not nodeSet:
+            return u''
         node = nodeSet[0]
 
@@ -134,6 +142,4 @@
 def String(context, object_=None):
     """Function: <string> string(<object>?)"""
-    if isinstance(object_, XPathStringType):
-        return object_
     if object_ is None:
         object_ = context.node
@@ -260,6 +266,5 @@
     if st is None:
         st = context.node
-    if not isinstance(st, XPathStringType):
-        st = Conversions.StringValue(st)
+    st = Conversions.StringValue(st)
     return u' '.join(st.split())
 
@@ -350,8 +355,9 @@
 def Sum(context, nodeSet):
     """Function: <number> sum(<node-set>)"""
-    if not isinstance(nodeSet, NodesetType):
+    try:
+        nns = map(Conversions.NumberValue, nodeSet)
+    except TypeError:
         raise RuntimeException(RuntimeException.WRONG_ARGUMENTS, 'sum',
                                _("expected node-set argument"))
-    nns = map(Conversions.NumberValue, nodeSet)
     return reduce(lambda x, y: x + y, nns, 0)
 
New file: FunctionCallExpressions.py
ViewCVS view:
  http://cvs.4suite.org/viewcvs/4Suite/Ft/Xml/XPath/FunctionCallExpressions.py?rev=1.1.2.1&content-type=text/vnd.viewcvs-markup

cvs checkout: cannot find module `FunctionCallExpressions.py' - ignored
New file: NodeSetExpressions.py
ViewCVS view:
  http://cvs.4suite.org/viewcvs/4Suite/Ft/Xml/XPath/NodeSetExpressions.py?rev=1.1.2.1&content-type=text/vnd.viewcvs-markup

cvs checkout: cannot find module `NodeSetExpressions.py' - ignored
New file: NodeSetFunctions.py
ViewCVS view:
  http://cvs.4suite.org/viewcvs/4Suite/Ft/Xml/XPath/NodeSetFunctions.py?rev=1.1.2.1&content-type=text/vnd.viewcvs-markup

cvs checkout: cannot find module `NodeSetFunctions.py' - ignored
New file: NumericExpressions.py
ViewCVS view:
  http://cvs.4suite.org/viewcvs/4Suite/Ft/Xml/XPath/NumericExpressions.py?rev=1.1.2.1&content-type=text/vnd.viewcvs-markup

cvs checkout: cannot find module `NumericExpressions.py' - ignored
ViewCVS diff:
  http://cvs.4suite.org/viewcvs/4Suite/Ft/Xml/XPath/ParsedAbbreviatedAbsoluteLocationPath.py.diff?r1=1.5.2.3&r2=1.5.2.4
ViewCVS view:
  http://cvs.4suite.org/viewcvs/4Suite/Ft/Xml/XPath/ParsedAbbreviatedAbsoluteLocationPath.py?rev=1.5.2.4&content-type=text/vnd.viewcvs-markup

Index: ParsedAbbreviatedAbsoluteLocationPath.py
===================================================================
RCS file: /var/local/cvsroot/4Suite/Ft/Xml/XPath/ParsedAbbreviatedAbsoluteLocationPath.py,v
retrieving revision 1.5.2.3
retrieving revision 1.5.2.4
diff -U2 -r1.5.2.3 -r1.5.2.4
--- ParsedAbbreviatedAbsoluteLocationPath.py	4 Dec 2006 07:06:59 -0000	1.5.2.3
+++ ParsedAbbreviatedAbsoluteLocationPath.py	4 Dec 2006 07:56:49 -0000	1.5.2.4
@@ -10,60 +10,39 @@
 
 from xml.dom import Node
-
 from Ft.Lib.Set import Unique
-from Ft.Xml.XPath import NOLIMIT
-
+from Ft.Xml.XPath.ParsedExpr import NodeSetExpression
 
-class ParsedAbbreviatedAbsoluteLocationPath:
+class ParsedAbbreviatedAbsoluteLocationPath(NodeSetExpression):
     def __init__(self, rel):
         self._rel = rel
-        self.implicitLimit = self._rel.implicitLimit
         return
 
-    def _descendants(self, context, nodeset, limit=NOLIMIT):
-        # modifies nodeset in place, adding all the descendants
-        if limit == 0:  return 0
-        checklimit = limit is not None
-        found = 0
-        for child in context.node.childNodes:
+    def _descendants(self, context, nodeset):
+        for child in context.node:
             context.node = child
-            if checklimit:
-                results = self._rel.select(context, limit - found)
-                found += len(results)
-            else:
-                results = self._rel.select(context)
+            results = self._rel.select(context)
             # Ensure no duplicates
-            #FIXME: propagate forwardsOnly cheeck to this class, an you can elminate this dupe checking for the 80%+ case
-            nodeset.extend([ n for n in results if n not in nodeset ])
+            if results:
+                nodeset.extend(results)
+                nodeset = Unique(nodeset)
             if child.nodeType == Node.ELEMENT_NODE:
-                if checklimit:
-                    if found < limit:
-                        found += self._descendants(context, nodeset, limit - found)
-                else:
-                    self._descendants(context, nodeset)
-            if checklimit and found >= limit:
-                break
-        return found
-
-    def evaluate(self, context, limit=NOLIMIT):
-        limit = min(limit, self.implicitLimit)
-        state = context.copy()
+                nodeset = self._descendants(context, nodeset)
+        return nodeset
+
+    def evaluateAsNodeSet(self, context):
+        state = context.node, context.position, context.size
 
         # Start at the document node
         context.node = context.node.rootNode
 
-        nodeset = self._rel.select(context, limit)
-        self._descendants(context, nodeset, limit)
+        nodeset = self._descendants(context, self._rel.select(context))
 
-        context.set(state)
-        if self._rel.isForwardsOnly:
-            return nodeset
-        else:
-            return Unique(nodeset)
-    select = evaluate
-
-    def pprint(self, indent=''):
-        print indent + str(self)
-        self._rel.pprint(indent + '  ')
+        context.node, context.position, context.size = state
+        return nodeset
+    evaluate = evaluateAsNodeSet
+
+    def pprint(self, indent='', stream=None):
+        print >> stream, indent + str(self)
+        self._rel.pprint(indent + '  ', stream)
 
     def __str__(self):
ViewCVS diff:
  http://cvs.4suite.org/viewcvs/4Suite/Ft/Xml/XPath/ParsedAbbreviatedRelativeLocationPath.py.diff?r1=1.3.2.3&r2=1.3.2.4
ViewCVS view:
  http://cvs.4suite.org/viewcvs/4Suite/Ft/Xml/XPath/ParsedAbbreviatedRelativeLocationPath.py?rev=1.3.2.4&content-type=text/vnd.viewcvs-markup

Index: ParsedAbbreviatedRelativeLocationPath.py
===================================================================
RCS file: /var/local/cvsroot/4Suite/Ft/Xml/XPath/ParsedAbbreviatedRelativeLocationPath.py,v
retrieving revision 1.3.2.3
retrieving revision 1.3.2.4
diff -U2 -r1.3.2.3 -r1.3.2.4
--- ParsedAbbreviatedRelativeLocationPath.py	4 Dec 2006 07:06:59 -0000	1.3.2.3
+++ ParsedAbbreviatedRelativeLocationPath.py	4 Dec 2006 07:56:49 -0000	1.3.2.4
@@ -9,9 +9,10 @@
 """
 
-from xml.dom import Node
 from Ft.Lib.Set import Unique
-from Ft.Xml.XPath import NOLIMIT, XPathTypes as Types
+from Ft.Xml.XPath.ParsedExpr import NodeSetExpression
+from Ft.Xml.XPath.ParsedAxisSpecifier import \
+    ParsedDescendantOrSelfAxisSpecifier
 
-class ParsedAbbreviatedRelativeLocationPath:
+class AbbreviatedRelativeLocationPath(NodeSetExpression):
     def __init__(self, left, right):
         """
@@ -21,62 +22,22 @@
         self._left = left
         self._right = right
+        self._descendants = ParsedDescendantOrSelfAxisSpecifier().select
         return
 
-    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, limit=NOLIMIT):
+    def select(self, context, nodes):
         """Returns a node-set"""
-        left = self._left.select(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__)))
-            results.extend(self._descendants(context, nodeset))
-
-        results = Unique(results)
-
-        context.set(state)
-        return results
-
-    select = evaluate
-
-    def pprint(self, indent=''):
-        print indent + str(self)
-        self._left.pprint(indent + '  ')
-        self._right.pprint(indent + '  ')
-
-    def __str__(self):
-        return '<AbbreviatedRelativeLocationPath at %x: %s>' % (
-            id(self),
-            repr(self),
-            )
+        for node in self._left.select(context, nodes):
+            nodes = self._descendants(node)
+            results.extend(self._right.select(context, nodes))
+        return iter(Unique(results))
+
+    def pprint(self, indent='', stream=None):
+        print >> stream, indent + str(self)
+        self._left.pprint(indent + '  ', stream)
+        self._right.pprint(indent + '  ', stream)
 
     def __repr__(self):
-        return repr(self._left) + '//' + repr(self._right)
+        return '%r//%r' % (self._left, self._right)
 
+ParsedAbbreviatedRelativeLocationPath = AbbreviatedRelativeLocationPath
\ No newline at end of file
ViewCVS diff:
  http://cvs.4suite.org/viewcvs/4Suite/Ft/Xml/XPath/ParsedAbsoluteLocationPath.py.diff?r1=1.3.2.2&r2=1.3.2.3
ViewCVS view:
  http://cvs.4suite.org/viewcvs/4Suite/Ft/Xml/XPath/ParsedAbsoluteLocationPath.py?rev=1.3.2.3&content-type=text/vnd.viewcvs-markup

Index: ParsedAbsoluteLocationPath.py
===================================================================
RCS file: /var/local/cvsroot/4Suite/Ft/Xml/XPath/ParsedAbsoluteLocationPath.py,v
retrieving revision 1.3.2.2
retrieving revision 1.3.2.3
diff -U2 -r1.3.2.2 -r1.3.2.3
--- ParsedAbsoluteLocationPath.py	3 Apr 2003 14:58:13 -0000	1.3.2.2
+++ ParsedAbsoluteLocationPath.py	4 Dec 2006 07:56:49 -0000	1.3.2.3
@@ -1,47 +1,30 @@
 ########################################################################
-#
-# File Name:   ParsedAbsoluteLocationPath.py
-#
-# Docs:        http://docs.4suite.org/XPATH/ParsedAbsoluteLocationPath.py.html
-#
+# $Header$
 """
 A Parsed Token that represents a absolute location path in the parsed tree.
-WWW: http://4suite.org/XPATH        e-mail: support at 4suite.org
 
-Copyright (c) 2000-2001 Fourthought Inc, USA.   All Rights Reserved.
-See  http://4suite.org/COPYRIGHT  for license and copyright information
+Copyright 2006 Fourthought, Inc. (USA).
+Detailed license and copyright information: http://4suite.org/COPYRIGHT
+Project home, documentation, distributions: http://4suite.org/
 """
 
-from Ft.Xml.XPath import NOLIMIT
+from Ft.Xml.XPath.ParsedExpr import NodeSetExpression
 
-class ParsedAbsoluteLocationPath:
+class ParsedAbsoluteLocationPath(NodeSetExpression):
     def __init__(self, child):
         self._child = child
 
-    def evaluate(self, context, limit=NOLIMIT):
-        root = context.node.rootNode
-
-        if self._child is None:
-            return [root]
-
-        state = context.copy()
-
-        context.node, context.position, context.size = root, 1, 1
-        nodeset = self._child.select(context)
-
-        context.set(state)
-        return nodeset
-    select = evaluate
-    
-    def pprint(self, indent=''):
-        print indent + str(self)
-        self._child and self._child.pprint(indent + '  ')
-
+    def select(self, context, nodes):
+        nodes = [context.node.rootNode]
+        if self._child:
+            nodes = self._child.select(context, nodes)
+        return nodes
+
+    def pprint(self, indent='', stream=None):
+        print >> stream, indent + str(self)
+        self._child and self._child.pprint(indent + '  ', stream)
 
     def __str__(self):
-        return '<AbsoluteLocationPath at %x: %s>' % (
-            id(self),
-            repr(self),
-            )
+        return '<AbsoluteLocationPath at %x: %r>' % (id(self), self)
 
     def __repr__(self):
ViewCVS diff:
  http://cvs.4suite.org/viewcvs/4Suite/Ft/Xml/XPath/ParsedAxisSpecifier.py.diff?r1=1.11.2.3&r2=1.11.2.4
ViewCVS view:
  http://cvs.4suite.org/viewcvs/4Suite/Ft/Xml/XPath/ParsedAxisSpecifier.py?rev=1.11.2.4&content-type=text/vnd.viewcvs-markup

Index: ParsedAxisSpecifier.py
===================================================================
RCS file: /var/local/cvsroot/4Suite/Ft/Xml/XPath/ParsedAxisSpecifier.py,v
retrieving revision 1.11.2.3
retrieving revision 1.11.2.4
diff -U2 -r1.11.2.3 -r1.11.2.4
--- ParsedAxisSpecifier.py	4 Dec 2006 07:06:59 -0000	1.11.2.3
+++ ParsedAxisSpecifier.py	4 Dec 2006 07:56:49 -0000	1.11.2.4
@@ -11,258 +11,215 @@
 from xml.dom import Node
 
-from Ft.Xml.XPath import NAMESPACE_NODE, NOLIMIT
-
+from Ft.Xml.XPath import NAMESPACE_NODE
 
 def ParsedAxisSpecifier(axis):
     try:
-        return g_classMap[axis](axis)
+        return _axes[axis]()
     except KeyError:
         raise SyntaxError("Invalid axis: %s" % axis)
 
 
-class AxisSpecifier:
+class AxisSpecifier(object):
 
     principalType = Node.ELEMENT_NODE
-    forwards = 1
-
-    def __init__(self, axis):
-        self._axis = axis
-
-    def select(self, context, nodeTest, limit=NOLIMIT):
-        """
-        Always returns a node-set and 0 if forward, 1 if reverse.
-        """
-        return ([], 0)
+    reverse = False
 
-    def descendants(self, context, nodeTest, node, nodeSet, limit=NOLIMIT):
-        """Select all of the descendants from the context node"""
-        if context.node.nodeType != Node.ATTRIBUTE_NODE:
-            count = 0
-            for child in node.childNodes:
-                if nodeTest(context, child, self.principalType):
-                    nodeSet.append(child)
-                    count += 1
-                if count >= limit: break
-                if child.childNodes:
-                    count += self.descendants(context, nodeTest, child, nodeSet)[2]
-                if count >= limit: break
-        return (nodeSet, 0, count)
+    def select(self, contextNode):
+        raise NotImplementedError
 
-    def pprint(self, indent=''):
-        print indent + str(self)
+    def pprint(self, indent='', stream=None):
+        print >> stream, indent + str(self)
 
     def __str__(self):
-        return '<AxisSpecifier at %x: %s>' % (id(self), repr(self))
+        return '<AxisSpecifier at %x: %r>' % (id(self), self)
 
     def __repr__(self):
-        """Always displays verbose expression"""
-        return self._axis
+        return self.name
 
 
 class ParsedAncestorAxisSpecifier(AxisSpecifier):
-    forwards = 0
-    def select(self, context, nodeTest, limit=NOLIMIT):
+    name = 'ancestor'
+    reverse = True
+    def select(self, contextNode):
         """Select all of the ancestors including the root"""
-        nodeSet = []
-        parent = ((context.node.nodeType == Node.ATTRIBUTE_NODE) and
-                context.node.ownerElement or context.node.parentNode)
-        while parent:
-            if nodeTest(context, parent, self.principalType):
-                nodeSet.append(parent)
-            parent = parent.parentNode
-        nodeSet.reverse()
-        return (nodeSet, 1)
+        node = contextNode.parentNode
+        while node:
+            yield node
+            node = node.parentNode
+        return
+    try:
+        from cAxes import AncestorAxis as select
+    except ImportError:
+        pass
 
 
 class ParsedAncestorOrSelfAxisSpecifier(AxisSpecifier):
-    forwards = 0
-    def select(self, context, nodeTest, limit=NOLIMIT):
+    name = 'ancestor-or-self'
+    reverse = True
+    def select(self, contextNode):
         """Select all of the ancestors including ourselves through the root"""
-        node = context.node
-        if nodeTest(context, node, self.principalType):
-            nodeSet = [node]
-        else:
-            nodeSet = []
-        parent = ((node.nodeType == Node.ATTRIBUTE_NODE) and
-                node.ownerElement or node.parentNode)
-        while parent:
-            if nodeTest(context, parent, self.principalType):
-                nodeSet.append(parent)
-            parent = parent.parentNode
-        nodeSet.reverse()
-        return (nodeSet, 1)
+        yield contextNode
+        node = contextNode.parentNode
+        while node:
+            yield node
+            node = node.parentNode
+        return
+    try:
+        from cAxes import AncestorOrSelfAxis as select
+    except ImportError:
+        pass
 
 
 class ParsedAttributeAxisSpecifier(AxisSpecifier):
-
+    name = 'attribute'
     principalType = Node.ATTRIBUTE_NODE
-
-    def select(self, context, nodeTest, limit=NOLIMIT):
+    def select(self, contextNode):
         """Select all of the attributes from the context node"""
-        result = [ attr for attr in context.node.xpathAttributes
-                   if nodeTest(context, attr, self.principalType) ]
-        return (result, 0)
+        return (contextNode.xpathAttributes)
+    try:
+        from cAxes import AttributeAxis as select
+    except ImportError:
+        pass
 
 
 class ParsedChildAxisSpecifier(AxisSpecifier):
-
-    def select(self, context, nodeTest, limit=NOLIMIT):
+    name = 'child'
+    def select(self, contextNode):
         """Select all of the children of the context node"""
-        #Optimize the common limit=1 case
-        if limit == 1:
-            for node in context.node.childNodes:
-                if nodeTest(context, node, self.principalType):
-                    return ([node], 0)
-        else:
-            result = [ node for node in context.node.childNodes
-                       if nodeTest(context, node, self.principalType) ]
-        return (result, 0)
-
-
-class ParsedDescendantOrSelfAxisSpecifier(AxisSpecifier):
-
-    def select(self, context, nodeTest, limit=NOLIMIT):
-        """Select the context node and all of its descendants"""
-        if limit == 0: return ([], 0)
-        if nodeTest(context, context.node, self.principalType):
-            nodeSet = [context.node]
-            if limit == 1: return (nodeSet, 0)
-        else:
-            nodeSet = []
-        self.descendants(context, nodeTest, context.node, nodeSet, limit)
-        return (nodeSet, 0)
+        return iter(contextNode)
+    try:
+        from cAxes import ChildAxis as select
+    except ImportError:
+        pass
 
 
 class ParsedDescendantAxisSpecifier(AxisSpecifier):
+    name = 'descendant'
+    def select(self, contextNode):
+        descendants = self.select
+        nodeType = Node.ELEMENT_NODE
+        for node in contextNode:
+            yield node
+            if node.nodeType == nodeType:
+                for node in descendants(node):
+                    yield node
+        return
+    try:
+        from cAxes import DescendantAxis as select
+    except ImportError:
+        pass
 
-    def select(self, context, nodeTest, limit=NOLIMIT):
-        nodeSet = []
-        self.descendants(context, nodeTest, context.node, nodeSet)
-        return (nodeSet, 0)
-
-
-class ParsedFollowingSiblingAxisSpecifier(AxisSpecifier):
-
-    def select(self, context, nodeTest, limit=NOLIMIT):
-        """Select all of the siblings that follow the context node"""
-        result = []
-        sibling = context.node.nextSibling
-        while sibling:
-            if nodeTest(context, sibling, self.principalType):
-                result.append(sibling)
-            sibling = sibling.nextSibling
-        return (result, 0)
 
+class ParsedDescendantOrSelfAxisSpecifier(ParsedDescendantAxisSpecifier):
+    name = 'descendant-or-self'
+    _descendants = ParsedDescendantAxisSpecifier.select
+    def select(self, contextNode):
+        """Select the context node and all of its descendants"""
+        yield contextNode
+        for node in self._descendants(contextNode):
+            yield node
+        return
+    try:
+        from cAxes import DescendantOrSelfAxis as select
+    except ImportError:
+        pass
 
-class ParsedFollowingAxisSpecifier(AxisSpecifier):
 
-    def select(self, context, nodeTest, limit=NOLIMIT):
+class ParsedFollowingAxisSpecifier(ParsedDescendantAxisSpecifier):
+    name = 'following'
+    def select(self, contextNode):
         """
         Select all of the nodes the follow the context node,
         not including descendants.
         """
-        result = []
-        curr = context.node
-        while curr != (context.node.rootNode):
-            sibling = curr.nextSibling
+        descendants = ParsedDescendantAxisSpecifier.select
+        while contextNode:
+            sibling = contextNode.nextSibling
             while sibling:
-                if nodeTest(context, sibling, self.principalType):
-                    result.append(sibling)
-                self.descendants(context, nodeTest, sibling, result)
+                yield sibling
+                for node in descendants(self, sibling):
+                    yield node
                 sibling = sibling.nextSibling
-            curr = ((curr.nodeType == Node.ATTRIBUTE_NODE) and
-                    curr.ownerElement or curr.parentNode)
-        return (result, 0)
+            contextNode = contextNode.parentNode
+        return
 
 
+class ParsedFollowingSiblingAxisSpecifier(AxisSpecifier):
+    name = 'following-sibling'
+    def select(self, contextNode):
+        """Select all of the siblings that follow the context node"""
+        sibling = contextNode.nextSibling
+        while sibling:
+            yield sibling
+            sibling = sibling.nextSibling
+        return
+    try:
+        from cAxes import FollowingSiblingAxis as select
+    except ImportError:
+        pass
 
-class ParsedNamespaceAxisSpecifier(AxisSpecifier):
 
+class ParsedNamespaceAxisSpecifier(AxisSpecifier):
+    name = 'namespace'
     principalType = NAMESPACE_NODE
 
-    def select(self, context, nodeTest, limit=NOLIMIT):
+    def select(self, contextNode):
         """Select all of the namespaces from the context node."""
-        result = [ xns for xns in context.node.xpathNamespaces
-                   if nodeTest(context, xns, self.principalType) ]
-        return (result, 0)
+        return iter(contextNode.xpathNamespaces)
 
 
 class ParsedParentAxisSpecifier(AxisSpecifier):
-    forwards = 0
-    def select(self, context, nodeTest, limit=NOLIMIT):
+    name = 'parent'
+    reverse = True
+    def select(self, contextNode):
         """Select the parent of the context node"""
-        parent = ((context.node.nodeType == Node.ATTRIBUTE_NODE) and
-                  context.node.ownerElement or context.node.parentNode)
-        if parent and nodeTest(context, parent, self.principalType):
-            result = [parent]
-        else:
-            result = []
-        return (result, 1)
+        parentNode = contextNode.parentNode
+        if parentNode:
+            yield parentNode
+        return
+
+
+class ParsedPrecedingAxisSpecifier(AxisSpecifier):
+    name = 'preceding'
+    reverse = True
+    def select(self, contextNode):
+        """
+        Select all of the nodes the precede the context node, not
+        including ancestors.
+        """
+        def preceding(currentNode):
+            while currentNode:
+                if currentNode.lastChild:
+                    for node in preceding(currentNode.lastChild):
+                        yield node
+                yield currentNode
+                currentNode = currentNode.previousSibling
+            return
+
+        while contextNode:
+            for node in preceding(contextNode.previousSibling):
+                yield node
+            contextNode = contextNode.parentNode
+        return
 
 
 class ParsedPrecedingSiblingAxisSpecifier(AxisSpecifier):
-    forwards = 0
-    def select(self, context, nodeTest, limit=NOLIMIT):
+    name = 'preceding-sibling'
+    reverse = True
+    def select(self, contextNode):
         """Select all of the siblings that precede the context node"""
-        result = []
-        sibling = context.node.previousSibling
+        sibling = contextNode.previousSibling
         while sibling:
-            if nodeTest(context, sibling, self.principalType):
-                result.append(sibling)
+            yield sibling
             sibling = sibling.previousSibling
-        # Put the list in document order
-        result.reverse()
-        return (result, 1)
-
-
-class ParsedPrecedingAxisSpecifier(AxisSpecifier):
-    forwards = 0
-    def select(self, context, nodeTest, limit=NOLIMIT):
-        """Select all of the nodes the precede the context node, not including ancestors"""
-        # Create a list of lists of descendants of the nodes
-        # that precede the context node. (reverse doc order)
-        doc_list = []
-        curr = context.node
-        while curr:
-            sib = curr.previousSibling
-            while sib:
-                result = []
-                if nodeTest(context, sib, self.principalType):
-                    result = [sib]
-                self.descendants(context, nodeTest, sib, result)
-                doc_list.append(result)
-                sib = sib.previousSibling
-            curr = curr.nodeType == Node.ATTRIBUTE_NODE and curr.ownerElement or curr.parentNode
-
-        # Create a single list in document order
-        result = []
-        for i in xrange(1, len(doc_list)+1):
-            result.extend(doc_list[-i])
-        return (result, 1)
+        return
 
 
 class ParsedSelfAxisSpecifier(AxisSpecifier):
-
-    def select(self, context, nodeTest, limit=NOLIMIT):
+    name = 'self'
+    def select(self, contextNode):
         """Select the context node"""
-        if nodeTest(context, context.node, self.principalType):
-            return ([context.node], 0)
-        return ([], 0)
-
-
-g_classMap = {
-    'ancestor' : ParsedAncestorAxisSpecifier,
-    'ancestor-or-self' : ParsedAncestorOrSelfAxisSpecifier,
-    'child' : ParsedChildAxisSpecifier,
-    'parent' : ParsedParentAxisSpecifier,
-    'descendant' : ParsedDescendantAxisSpecifier,
-    'descendant-or-self' : ParsedDescendantOrSelfAxisSpecifier,
-    'attribute' : ParsedAttributeAxisSpecifier,
-    'following' : ParsedFollowingAxisSpecifier,
-    'following-sibling' : ParsedFollowingSiblingAxisSpecifier,
-    'preceding' : ParsedPrecedingAxisSpecifier,
-    'preceding-sibling' : ParsedPrecedingSiblingAxisSpecifier,
-    'namespace' : ParsedNamespaceAxisSpecifier,
-    'self' : ParsedSelfAxisSpecifier,
-    }
+        yield contextNode
+
 
+_axes = dict([ (cls.name, cls) for cls in AxisSpecifier.__subclasses__() ])
ViewCVS diff:
  http://cvs.4suite.org/viewcvs/4Suite/Ft/Xml/XPath/ParsedExpr.py.diff?r1=1.25.2.4&r2=1.25.2.5
ViewCVS view:
  http://cvs.4suite.org/viewcvs/4Suite/Ft/Xml/XPath/ParsedExpr.py?rev=1.25.2.5&content-type=text/vnd.viewcvs-markup

Index: ParsedExpr.py
===================================================================
RCS file: /var/local/cvsroot/4Suite/Ft/Xml/XPath/ParsedExpr.py,v
retrieving revision 1.25.2.4
retrieving revision 1.25.2.5
diff -U2 -r1.25.2.4 -r1.25.2.5
--- ParsedExpr.py	4 Dec 2006 07:06:59 -0000	1.25.2.4
+++ ParsedExpr.py	4 Dec 2006 07:56:49 -0000	1.25.2.5
@@ -9,840 +9,207 @@
 """
 
-import types, inspect
-from xml.dom import Node
+from Ft.Xml.Lib.XmlString import SplitQName
+from Ft.Xml.XPath import RuntimeException
+from Ft.Xml.XPath.cDataTypes import *
 
-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, NOLIMIT
-from Ft.Xml.XPath import ParsedStep, ParsedAxisSpecifier, ParsedNodeTest
-from Ft.Xml.XPath import Conversions, _comparisons
-from Ft.Xml.XPath import XPathTypes as Types
+class Expression(object):
 
+    resultType = Object
 
-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):
+        raise NotImplementedError
 
-    def evaluate(self, context, limit=NOLIMIT):
-        return self._literal
+    def evaluateAsBoolean(self, context):
+        return Boolean(self.evaluate(context))
 
-    def pprint(self, indent=''):
-        print indent + str(self)
+    def evaluateAsNumber(self, context):
+        return Number(self.evaluate(context))
 
-    def __str__(self):
-        return '<Literal at %x: %s>' % (id(self), repr(self))
+    def evaluateAsString(self, context):
+        return String(self.evaluate(context))
 
-    def __repr__(self):
-        return '"' + self._literal.encode('unicode_escape') + '"'
+    def evaluateAsNodeSet(self, context):
+        raise TypeError('cannot convert to a node-set')
 
-
-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 select(self, context, node):
+        return iter(self.evaluateAsNodeSet(context))
 
     def __str__(self):
-        return '<Number at %x: %s>' % (id(self), repr(self))
+        ptr = id(self)
+        if ptr < 0: ptr += 0x10000000L
+        return '<%s at 0x%x: %r>' % (self.__class__.__name__, ptr, 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:
+class Literal(Expression):
     """
-    An object representing a variable reference expression
-    (XPath 1.0 grammar production 36: VariableReference)
+    An object representing a string literal expression
+    (XPath 1.0 grammar production 29: Literal)
     """
-    def __init__(self,name):
-        self._name = name
-        self._key = SplitQName(name[1:])
-        return
-
-    def evaluate(self, context, limit=NOLIMIT):
-        """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,
-                                   name=self._name, key=self._key)
 
-    def pprint(self, indent=''):
-        print indent + str(self)
+    def evaluateAsBoolean(self, context):
+        return self._literal and Boolean.TRUE or Boolean.FALSE
 
-    def __str__(self):
-        return '<Variable at %x: %s>' % (id(self), repr(self))
+    def evaluateAsNumber(self, context):
+        return Number(self._literal)
 
-    def __repr__(self):
-        return self._name
+    def evaluateAsString(self, context):
+        return self._literal
+    evaluate = evaluateAsString
 
-
-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)
+    def pprint(self, indent='', stream=None):
+        print >> stream, indent + str(self)
 
 
-class FunctionCall:
+class StringLiteral(Literal):
     """
-    An object representing a function call expression
-    (XPath 1.0 grammar production 16: FunctionCall)
+    An object representing a string literal expression
+    (XPath 1.0 grammar production 29: Literal)
     """
-    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, limit=NOLIMIT):
-        """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, limit=NOLIMIT):
-        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, limit=NOLIMIT):
-        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, limit=NOLIMIT):
-        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, limit=NOLIMIT):
-        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
+    resultType = String
 
-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, limit=NOLIMIT):
-        """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__)))
+    def __init__(self, literal):
+        if literal[:1] in ("'", '"') and literal[:1] == literal[-1:]:
+            literal = literal[1:-1]
+        self._literal = String(literal)
 
-        set = Union(left, right)
-        return set
+    def evaluateAsNumber(self, context):
+        return Number(self._literal)
 
-    def __str__(self):
-        return '<UnionExpr at %x: %s>' % (id(self), repr(self))
+    def evaluateAsString(self, context):
+        return self._literal
+    evaluate = evaluateAsString
 
     def __repr__(self):
-        return repr(self._left) + ' | ' + repr(self._right)
+        literal = self._literal.encode('unicode_escape')
+        return '"%s"' % literal.replace('"', '\\"')
+ParsedLiteralExpr = StringLiteral
 
 
-class ParsedPathExpr:
+class NumericLiteral(Literal):
     """
-    An object representing a path expression
-    (XPath 1.0 grammar production 19: PathExpr)
+    An object representing a numeric literal expression
+    (XPath 1.0 grammar production 30: Number)
     """
-    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, limit=NOLIMIT):
-        """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)
+    resultType = Number
 
-        results = Unique(results)
+    def __init__(self, literal):
+        self._literal = Number(literal)
 
-        context.set(state)
-        return results
+    def evaluateAsNumber(self, context):
+        return self._literal
+    evaluate = evaluateAsNumber
 
-    def __str__(self):
-        return '<PathExpr at %x: %s>' % (id(self), repr(self))
+    def evaluateAsString(self, context):
+        return String(self._literal)
 
     def __repr__(self):
-        op = self._descendant and '//' or '/'
-        return repr(self._left) + op + repr(self._right)
+        return String(self._literal).encode('unicode_escape')
+ParsedNLiteralExpr = NumericLiteral
 
 
-class ParsedFilterExpr:
+class VariableReference(Expression):
     """
-    An object representing a filter expression
-    (XPath 1.0 grammar production 20: FilterExpr)
+    An object representing a variable reference expression
+    (XPath 1.0 grammar production 36: VariableReference)
     """
-    def __init__(self, filter_, predicates):
-        self._filter = filter_
-        self._predicates = predicates
+    def __init__(self, name):
+        self._name = SplitQName(name[1:])
+        self._key = None
         return
 
-    def evaluate(self, context, limit=NOLIMIT):
-        """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
-        self._identical_operands = repr(self._left) == repr(self._right)
-
-    def pprint(self, indent=''):
-        print indent + str(self)
-        self._left.pprint(indent + '  ')
-        self._right.pprint(indent + '  ')
-
-    def evaluate(self, context, limit=NOLIMIT):
-        """Returns a boolean"""
-        rt = Conversions.BooleanValue(self._left.evaluate(context))
-        if not rt:
-            # Shortcut evaluation if operands are same
-            if self._identical_operands:
-                return boolean.false
-            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
-        self._identical_operands = repr(self._left) == repr(self._right)
-
-    def pprint(self, indent=''):
-        print indent + str(self)
-        self._left.pprint(indent + '  ')
-        self._right.pprint(indent + '  ')
-
-    def evaluate(self, context, limit=NOLIMIT):
-        """Returns a boolean"""
-        rt = Conversions.BooleanValue(self._left.evaluate(context))
-        if rt:
-            # Shortcut evaluation if operands are same
-            if self._identical_operands:
-                return 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.
-        if not (a and b):
-            # One of the two node-sets is empty.  In this case, according to
-            # section 3.4 of the XPath rec, no node exists in one of the two
-            # sets to compare, so *any* comparison must be false.
-            return boolean.false
-
-        # If it is a relational comparison, the actual comparison is done on
-        # the string value of each of the nodes. This means that the values
-        # are then converted to numbers for comparison.
-        if relational:
-            # NumberValue internally coerces a node to a string before
-            # converting it to a number, so the "convert to string" clause
-            # is handled.
-            coerce = Conversions.NumberValue
-        else:
-            coerce = Conversions.StringValue
-
-        # Convert the nodesets into lists of the converted values.
-        a = map(coerce, a)
-        b = map(coerce, b)
-        # Now compare the items; if any compare True, we're done.
-        for left in a:
-            for right in b:
-                if compare(left, right):
-                    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):
-            coerce = Conversions.BooleanValue
-        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):
-            coerce = Conversions.BooleanValue
-        elif relational:
-            a = Conversions.NumberValue(a)
-            coerce = Conversions.NumberValue
-        elif isinstance(a, Types.NumberType):
-            coerce = Conversions.NumberValue
+    def _prime(self, context):
+        prefix, local = self._name
+        if prefix:
+            try:
+                key = (context.processorNss[prefix], local)
+            except KeyError:
+                raise RuntimeException(RuntimeException.UNDEFINED_PREFIX,
+                                 prefix)
         else:
-            a = Conversions.StringValue(a)
-            coerce = Conversions.StringValue
-        for node in b:
-            if compare(a, coerce(node)):
-                return boolean.true
-
-    return boolean.false
-
+            key = self._name
+        self._key = key
+        return key
 
-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
-        self._identical_operands = repr(self._left) == repr(self._right)
+    def evaluate(self, context):
+        """Returns an object"""
+        key = self._key or self._prime(context)
+        try:
+            return context.varBindings[key]
+        except KeyError:
+            name = self._name[0] and ':'.join(self._name) or self._name[1]
+            raise RuntimeException(RuntimeException.UNDEFINED_VARIABLE,
+                                   name=name, key=key)
 
-        if op == '=':
-            self._cmp = _comparisons.eq
-        else:
-            self._cmp = _comparisons.ne
+    def evaluateAsBoolean(self, context):
+        key = self._key or self._prime(context)
+        try:
+            return Boolean(context.varBindings[key])
+        except KeyError:
+            name = self._name[0] and ':'.join(self._name) or self._name[1]
+            raise RuntimeException(RuntimeException.UNDEFINED_VARIABLE,
+                                   name=name, key=key)
 
-    def __getstate__(self):
-        return (self._op, self._left, self._right)
+    def evaluateAsNumber(self, context):
+        key = self._key or self._prime(context)
+        try:
+            return Number(context.varBindings[key])
+        except KeyError:
+            name = self._name[0] and ':'.join(self._name) or self._name[1]
+            raise RuntimeException(RuntimeException.UNDEFINED_VARIABLE,
+                                   name=name, key=key)
 
-    def __setstate__(self, state):
-        self.__init__(*state)
+    def evaluateAsString(self, context):
+        key = self._key or self._prime(context)
+        try:
+            return String(context.varBindings[key])
+        except KeyError:
+            name = self._name[0] and ':'.join(self._name) or self._name[1]
+            raise RuntimeException(RuntimeException.UNDEFINED_VARIABLE,
+                                   name=name, key=key)
 
-    def evaluate(self, context, limit=NOLIMIT):
-        """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()
-
-        # Shortcut comparison if operands are same and not node-sets.
-        if self._identical_operands:
-            return self._op == '=' and boolean.true or boolean.false
-
-        if isinstance(left, Types.BooleanType):
-            right = Conversions.BooleanValue(right)
-        elif isinstance(right, Types.BooleanType):
-            left = Conversions.BooleanValue(left)
-        elif isinstance(left, Types.NumberType):
-            right = Conversions.NumberValue(right)
-        elif isinstance(right, Types.NumberType):
-            left = Conversions.NumberValue(left)
-        else:
-            left = Conversions.StringValue(left)
-            right = Conversions.StringValue(right)
-        return self._cmp(left, right) and boolean.true or boolean.false
-
-    def pprint(self, indent=''):
-        print indent + str(self)
-        self._left.pprint(indent + '  ')
-        self._right.pprint(indent + '  ')
+    def evaluateAsNodeSet(self, context):
+        key = self._key or self._prime(context)
+        try:
+            return NodeSet(context.varBindings[key])
+        except KeyError:
+            name = self._name[0] and ':'.join(self._name) or self._name[1]
+            raise RuntimeException(RuntimeException.UNDEFINED_VARIABLE,
+                                   name=name, key=key)
 
-    def __str__(self):
-        return '<EqualityExpr at %x: %s>' % (id(self), repr(self))
+    def pprint(self, indent='', stream=None):
+        print >> stream, indent + str(self)
 
     def __repr__(self):
-        if self._op == '=':
-            op = ' = '
-        else:
-            op = ' != '
-        return repr(self._left) + op + repr(self._right)
-
+        name = self._name[0] and ':'.join(self._name) or self._name[1]
+        return '$' + name.encode('unicode_escape')
+ParsedVariableReferenceExpr = VariableReference
 
 
-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
-        self._identical_operands = repr(self._left) == repr(self._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
+########################################################################
+# Function Call Expressions
 
-    def __getstate__(self):
-        return (self._op, self._left, self._right)
+from Ft.Xml.XPath.FunctionCallExpressions import \
+    FunctionCall as ParsedFunctionCallExpr
 
-    def __setstate__(self, state):
-        self.__init__(*state)
+########################################################################
+# Nodeset Expressions
+# Expressions that only return nodesets
 
-    def evaluate(self, context, limit=NOLIMIT):
-        """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)
-
-        # Shortcut comparison if operands are same and not node-sets.
-        left = Conversions.NumberValue(left)
-        if number.isnan(left):
-            return boolean.false
-        if self._identical_operands:
-            # < or >: false.  <= or >=: true.
-            return self._op % 2 and boolean.true or boolean.false
-        right = Conversions.NumberValue(right)
-        if number.isnan(right):
-            return boolean.false
-        return self._cmp(left, right) 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)
+from Ft.Xml.XPath.NodeSetExpressions import NodeSetExpression, \
+    UnionExpr as ParsedUnionExpr, \
+    PathExpr as ParsedPathExpr, \
+    FilterExpr as ParsedFilterExpr
 
-    def __str__(self):
-        return '<RelationalExpr at %x: %s>' % (id(self), repr(self))
+########################################################################
+# Boolean Expressions
+# Expressions that only return booleans
 
-    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)
+from Ft.Xml.XPath.BooleanExpressions import BooleanExpression, \
+    OrExpr as ParsedOrExpr, \
+    AndExpr as ParsedAndExpr, \
+    EqualityExpr as ParsedEqualityExpr, \
+    RelationalExpr as ParsedRelationalExpr
 
 ########################################################################
@@ -850,168 +217,6 @@
 # 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
-        self._identical_operands = repr(self._left) == repr(self._right)
-        return
-
-    def evaluate(self, context, limit=NOLIMIT):
-        '''Returns a number'''
-
-        # Shortcut subtraction if operands are same and are not NaN.
-        # We have to evaluate one first to make sure it's not NaN.
-        if self._leftLit:
-            lrt = self._left
-        else:
-            lrt = self._left.evaluate(context)
-            lrt = Conversions.NumberValue(lrt)
-        if number.isnan(lrt):
-            return lrt
-        if self._identical_operands and self._sign <= 0:
-            return 0.0
-
-        if self._rightLit:
-            rrt = self._right
-        else:
-            rrt = self._right.evaluate(context)
-            rrt = Conversions.NumberValue(rrt)
-        if number.isnan(rrt):
-            return 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
-        self._identical_operands = repr(self._left) == repr(self._right)
-
-    def evaluate(self, context, limit=NOLIMIT):
-        '''Returns a number'''
-
-        # Shortcut division and modulo if operands are same
-        # and are not NaN. We have to evaluate one first
-        # to make sure it's not NaN.
-        lrt = self._left.evaluate(context)
-        lrt = Conversions.NumberValue(lrt)
-        if number.isnan(lrt):
-            return lrt
-        if self._op > 0 and self._identical_operands:
-            return (1.0,0.0)[self._op - 1]
-
-        rrt = self._right.evaluate(context)
-        rrt = Conversions.NumberValue(rrt)
-        if number.isnan(rrt):
-            return 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, limit=NOLIMIT):
-        '''Returns a number'''
-        exp = self._exp.evaluate(context)
-        exp = Conversions.NumberValue(exp)
-        return exp * -1.0
-
-    def pprint(self, indent=''):
-        print indent + str(self)
-        return
-
-    def __str__(self):
-        return '<UnaryExpr at %x: %s>' % (id(self), repr(self))
-
-    def __repr__(self):
-        return '-' + repr(self._exp)
+from Ft.Xml.XPath.NumericExpressions import NumericExpression, \
+    AdditiveExpr as ParsedAdditiveExpr, \
+    MultiplicativeExpr as ParsedMultiplicativeExpr, \
+    UnaryExpr as ParsedUnaryExpr
ViewCVS diff:
  http://cvs.4suite.org/viewcvs/4Suite/Ft/Xml/XPath/ParsedNodeTest.py.diff?r1=1.5.2.2&r2=1.5.2.3
ViewCVS view:
  http://cvs.4suite.org/viewcvs/4Suite/Ft/Xml/XPath/ParsedNodeTest.py?rev=1.5.2.3&content-type=text/vnd.viewcvs-markup

Index: ParsedNodeTest.py
===================================================================
RCS file: /var/local/cvsroot/4Suite/Ft/Xml/XPath/ParsedNodeTest.py,v
retrieving revision 1.5.2.2
retrieving revision 1.5.2.3
diff -U2 -r1.5.2.2 -r1.5.2.3
--- ParsedNodeTest.py	4 Dec 2006 07:06:59 -0000	1.5.2.2
+++ ParsedNodeTest.py	4 Dec 2006 07:56:50 -0000	1.5.2.3
@@ -54,6 +54,6 @@
         return node.nodeType == self.nodeType
 
-    def pprint(self, indent):
-        print indent + str(self)
+    def pprint(self, indent='', stream=None):
+        print >> stream, indent + str(self)
 
     def __str__(self):
@@ -99,19 +99,19 @@
             if target[0] not in ['"', "'"]:
                 raise SyntaxError("Invalid literal: %s" % target)
-            self.target = target[1:-1]
+            self._name = target[1:-1]
         else:
             self.priority = -0.5
-            self.target = ''
+            self._name = None
 
     def match(self, context, node, principalType=Node.ELEMENT_NODE):
         if node.nodeType != self.nodeType:
             return 0
-        if self.target:
-            return node.target == self.target
+        if self._name:
+            return node.target == self._name
         return 1
 
     def __repr__(self):
-        if self.target:
-            target = repr(self.target)
+        if self._name:
+            target = repr(self._name)
         else:
             target = ''
@@ -122,5 +122,6 @@
 class PrincipalTypeTest(NodeTestBase):
 
-    nodeType = Node.ELEMENT_NODE
+    def getQuickKey(self, namespaces):
+        return (Node.ELEMENT_NODE, None)
 
     def match(self, context, node, principalType=Node.ELEMENT_NODE):
@@ -132,6 +133,4 @@
 class LocalNameTest(NodeTestBase):
 
-    nodeType = Node.ELEMENT_NODE
-
     def __init__(self, name):
         self.priority = 0
@@ -139,5 +138,5 @@
 
     def getQuickKey(self, namespaces):
-        return (self.nodeType, (None, self._name))
+        return (Node.ELEMENT_NODE, (None, self._name))
 
     def match(self, context, node, principalType=Node.ELEMENT_NODE):
@@ -152,6 +151,4 @@
 class NamespaceTest(NodeTestBase):
 
-    nodeType = Node.ELEMENT_NODE
-
     def __init__(self, prefix):
         self.priority = -0.25
@@ -161,5 +158,5 @@
         # By specifing a name of None, this test will fall into the 'general'
         # category for the principal type
-        return (self.nodeType, None)
+        return (Node.ELEMENT_NODE, None)
 
     def match(self, context, node, principalType=Node.ELEMENT_NODE):
@@ -178,6 +175,4 @@
 class QualifiedNameTest(NodeTestBase):
 
-    nodeType = Node.ELEMENT_NODE
-
     def __init__(self, prefix, localName):
         self.priority = 0
@@ -191,5 +186,5 @@
             raise RuntimeException(RuntimeException.UNDEFINED_PREFIX,
                                    self._prefix)
-        return (self.nodeType, (namespace, self._localName))
+        return (Node.ELEMENT_NODE, (namespace, self._localName))
 
     def match(self, context, node, principalType=Node.ELEMENT_NODE):
ViewCVS diff:
  http://cvs.4suite.org/viewcvs/4Suite/Ft/Xml/XPath/ParsedPredicateList.py.diff?r1=1.5.2.2&r2=1.5.2.3
ViewCVS view:
  http://cvs.4suite.org/viewcvs/4Suite/Ft/Xml/XPath/ParsedPredicateList.py?rev=1.5.2.3&content-type=text/vnd.viewcvs-markup

Index: ParsedPredicateList.py
===================================================================
RCS file: /var/local/cvsroot/4Suite/Ft/Xml/XPath/ParsedPredicateList.py,v
retrieving revision 1.5.2.2
retrieving revision 1.5.2.3
diff -U2 -r1.5.2.2 -r1.5.2.3
--- ParsedPredicateList.py	4 Dec 2006 07:06:59 -0000	1.5.2.2
+++ ParsedPredicateList.py	4 Dec 2006 07:56:50 -0000	1.5.2.3
@@ -9,69 +9,177 @@
 """
 
-from Ft.Lib import number
-from Ft.Xml.XPath import Conversions
-from Ft.Xml.XPath.XPathTypes import NumberTypes, g_xpathPrimitiveTypes
+from itertools import count, izip
+
+from Ft.Xml.XPath.cDataTypes import *
+from Ft.Xml.XPath.cNodeTests import Filter, PositionFilter
+from Ft.Xml.XPath.cPaths import PathIter
+from Ft.Xml.XPath.ParsedExpr import Literal, VariableReference
+from Ft.Xml.XPath.BooleanExpressions import EqualityExpr, RelationalExpr
+from Ft.Xml.XPath.NodeSetFunctions import Position
 
 __all__ = ['ParsedPredicateList']
 
-class ParsedPredicateList:
+class ParsedPredicateList(list):
     def __init__(self, preds):
-        if isinstance(preds, tuple):
-            preds = list(preds)
-        elif not isinstance(preds, list):
-            raise TypeError("Invalid Predicates: %s"%str(preds))
-
-        self._predicates = preds
-        self._length = len(preds)
-
-    def append(self, pred):
-        self._predicates.append(pred)
-        self._length += 1
+        list.__init__(self, map(Predicate, preds))
+        self.select = PathIter([ pred.select for pred in self ]).select
+        return
+
+    def append(self, expression):
+        list.append(self, Predicate(expression))
+        self.select = PathIter([ pred.select for pred in self ])
 
     def filter(self, nodeList, context, reverse):
-        if self._length:
-            state = context.copy()
-            for pred in self._predicates:
-                size = len(nodeList)
-                ctr = 0
-                current = nodeList
-                nodeList = []
-                for node in current:
-                    position = (reverse and size - ctr) or (ctr + 1)
-                    context.node, context.position, context.size = \
-                                  node, position, size
-                    res = pred.evaluate(context)
-                    if type(res) in NumberTypes:
-                        # This must be separate to prevent falling into
-                        # the boolean check.
-                        if not number.isnan(res) and res == position:
-                            nodeList.append(node)
-                    elif Conversions.BooleanValue(res):
-                        nodeList.append(node)
-                    ctr += 1
-            context.set(state)
+        if self:
+            state = context.node, context.position, context.size
+            for predicate in self:
+                nodeList = NodeSet(predicate.select(context, nodeList))
+            context.node, context.position, context.size = state
+        else:
+            nodeList = NodeSet(nodeList)
+        if reverse:
+            nodeList.reverse()
         return nodeList
 
-    def __getitem__(self, index):
-        return self._predicates[index]
+    def pprint(self, indent='', stream=None):
+        print >> stream, indent + str(self)
+        for pred in self:
+            pred.pprint(indent + '  ', stream)
+
+    def __str__(self):
+        return '<PredicateList at %x: %s>' % (id(self),
+                                              repr(self) or '(empty)')
+
+    def __repr__(self):
+        return ''.join(map(repr, self))
 
-    def __len__(self):
-        return self._length
 
-    def pprint(self, indent=''):
-        print indent + str(self)
-        for pred in self._predicates:
-            pred.pprint(indent + '  ')
+class Predicate:
+    def __init__(self, expression):
+        self._expr = expression
+        # Check for just "Number"
+        if isinstance(expression, Literal):
+            const = expression.evaluateAsNumber(None)
+            index = int(const)
+            if index == const and index >= 1:
+                self.select = PositionFilter(index)
+            else:
+                # FIXME: add warning that expression will not select anything
+                self.select = Filter()
+            return
+
+        # Check for "position() = Expr"
+        if isinstance(expression, EqualityExpr) and expression._equals:
+            if isinstance(expression._left, Position):
+                expression = expression._right
+                if isinstance(expression, Literal):
+                    const = expression.evaluateAsNumber(None)
+                    index = int(const)
+                    if index == const and index >= 1:
+                        self.select = PositionFilter(index)
+                    else:
+                        self.select = Filter()
+                else:
+                    self._filter = expression.evaluateAsNumber
+                    self.select = self._number
+                return
+            elif isinstance(expression._right, Position):
+                expression = expression._left
+                if isinstance(expression, Literal):
+                    const = expression.evaluateAsNumber(None)
+                    index = int(const)
+                    if index == const and index >= 1:
+                        self.select = PositionFilter(index)
+                    else:
+                        self.select = Filter()
+                else:
+                    self._filter = expression.evaluateAsNumber
+                    self.select = self._number
+                return
+
+        # Check for "position() >= Expr"
+        # FIXME - do full slice-type notation
+        if isinstance(expression, RelationalExpr):
+            opcode = expression._op
+            if (isinstance(expression._left, Position) and
+                isinstance(expression._right, (Literal, VariableReference)) and
+                expression._op >= 2):
+                self._start = expression._right.evaluateAsNumber
+                if opcode == 3:
+                    self._start -= 1
+                self.select = self._slice
+                return
+            elif (isinstance(expression._left, (Literal, VariableReference)) and
+                  isinstance(expression._right, Position) and
+                  expression._op <= 1):
+                self._start = expression._left.evaluateAsNumber
+                if opcode == 1:
+                    self._start -= 1
+                self.select = self._slice
+                return
+
+        if expression.resultType is Number:
+            self._filter = expression.evaluateAsNumber
+            self.select = self._number
+        elif expression.resultType is not Object:
+            self._filter = expression.evaluateAsBoolean
+            self.select = self._boolean
+        return
+
+    def _slice(self, context, nodes):
+        start = self._start(context)
+        if 1 >= start:
+            return nodes
+        position = 2
+        nodes = iter(nodes)
+        for node in nodes:
+            if position >= start:
+                break
+            position += 1
+        return nodes
+
+    def _number(self, context, nodes):
+        _filter = self._filter
+        position = 1
+        for node in nodes:
+            context.node, context.position = node, position
+            if _filter(context) == position:
+                yield node
+            position += 1
+        return
+
+    def _boolean(self, context, nodes):
+        _filter = self._filter
+        position = 1
+        for node in nodes:
+            context.node, context.position = node, position
+            if _filter(context):
+                yield node
+            position += 1
+        return
+
+    def select(self, context, nodes):
+        evaluate = self._expr.evaluate
+        position = 1
+        for node in nodes:
+            context.node, context.position = node, position
+            result = evaluate(context)
+            if isinstance(result, Number):
+                # This must be separate to prevent falling into
+                # the boolean check.
+                if result == position:
+                    yield node
+            elif result:
+                yield node
+            position += 1
+        return
+
+    def pprint(self, indent='', stream=None):
+        print >> stream, indent + str(self)
+        self._expr.pprint(indent + '  ', stream)
 
     def __str__(self):
-        return '<PredicateList at %x: %s>' % (
-            id(self),
-            repr(self) or '(empty)',
-            )
+        return '<Predicate at %x: %r>' % (id(self), self)
 
     def __repr__(self):
-        return reduce(lambda result, pred:
-                      result + '[%s]' % repr(pred),
-                      self._predicates,
-                      ''
-                      )
+        return '[%r]' % self._expr
ViewCVS diff:
  http://cvs.4suite.org/viewcvs/4Suite/Ft/Xml/XPath/ParsedRelativeLocationPath.py.diff?r1=1.3.2.3&r2=1.3.2.4
ViewCVS view:
  http://cvs.4suite.org/viewcvs/4Suite/Ft/Xml/XPath/ParsedRelativeLocationPath.py?rev=1.3.2.4&content-type=text/vnd.viewcvs-markup

Index: ParsedRelativeLocationPath.py
===================================================================
RCS file: /var/local/cvsroot/4Suite/Ft/Xml/XPath/ParsedRelativeLocationPath.py,v
retrieving revision 1.3.2.3
retrieving revision 1.3.2.4
diff -U2 -r1.3.2.3 -r1.3.2.4
--- ParsedRelativeLocationPath.py	4 Dec 2006 07:06:59 -0000	1.3.2.3
+++ ParsedRelativeLocationPath.py	4 Dec 2006 07:56:50 -0000	1.3.2.4
@@ -1,47 +1,37 @@
-########################################################################  
+########################################################################
 # $Header$
 """
 A parsed token that represents a relative location path in the parsed result tree.
-    
-Copyright 2005 Fourthought, Inc. (USA).
+
+Copyright 2006 Fourthought, Inc. (USA).
 Detailed license and copyright information: http://4suite.org/COPYRIGHT
 Project home, documentation, distributions: http://4suite.org/
 """
 
-from Ft.Xml.XPath import NOLIMIT
+from Ft.Xml.XPath.ParsedExpr import NodeSetExpression
+from Ft.Xml.XPath.cPaths import *
 
-class ParsedRelativeLocationPath:
-    def __init__(self, left, right):
-        self._left = left
-        self._right = right
+class RelativeLocationPath(NodeSetExpression):
+    def __init__(self, left, step):
+        if isinstance(left, RelativeLocationPath):
+            self._steps = left._steps
+            self._steps.append(step)
+        else:
+            self._steps = [left, step]
+        self.select = PathIter([ step.select for step in self._steps ]).select
         return
 
-    def evaluate(self, context, limit=NOLIMIT):
-        nodeset = self._left.select(context)
-
-        state = context.copy()
-
-        result = []
-        size = len(nodeset)
-        for pos in xrange(size):
-            context.node, context.position, context.size = \
-                          nodeset[pos], pos + 1, size
-            result.extend(self._right.select(context))
-
-        context.set(state)
-        return result
-    select = evaluate
-
-    def pprint(self, indent=''):
-        print indent + str(self)
-        self._left.pprint(indent + '  ')
-        self._right.pprint(indent + '  ')
-
-    def __str__(self):
-        return '<RelativeLocationPath at %x: %s>' % (
-            id(self),
-            repr(self),
-            )
+    def select(self, context, nodes):
+        for step in self._steps:
+            nodes = step.select(context, nodes)
+        return nodes
+
+    def pprint(self, indent='', stream=None):
+        print >> stream, indent + str(self)
+        for step in self._steps:
+            step.pprint(indent + '  ', stream)
 
     def __repr__(self):
-        return repr(self._left) + '/' + repr(self._right)
+        return  '/'.join(map(repr, self._steps))
+
+ParsedRelativeLocationPath = RelativeLocationPath
\ No newline at end of file
ViewCVS diff:
  http://cvs.4suite.org/viewcvs/4Suite/Ft/Xml/XPath/ParsedStep.py.diff?r1=1.4.2.3&r2=1.4.2.4
ViewCVS view:
  http://cvs.4suite.org/viewcvs/4Suite/Ft/Xml/XPath/ParsedStep.py?rev=1.4.2.4&content-type=text/vnd.viewcvs-markup

Index: ParsedStep.py
===================================================================
RCS file: /var/local/cvsroot/4Suite/Ft/Xml/XPath/ParsedStep.py,v
retrieving revision 1.4.2.3
retrieving revision 1.4.2.4
diff -U2 -r1.4.2.3 -r1.4.2.4
--- ParsedStep.py	4 Dec 2006 07:06:59 -0000	1.4.2.3
+++ ParsedStep.py	4 Dec 2006 07:56:50 -0000	1.4.2.4
@@ -11,76 +11,131 @@
 from xml.dom import Node
 
-from Ft.Lib import number
-from Ft.Xml.XPath import XPathTypes as Types, Context, NOLIMIT
-import types
-NumberTypes = [types.IntType, types.LongType, types.FloatType]
+from Ft.Xml.XPath.NodeSetExpressions import NodeSetExpression
+from Ft.Xml.XPath.cDataTypes import *
+from Ft.Xml.XPath.cNodeTests import *
+from Ft.Xml.XPath.cPaths import *
+from Ft.Xml.XPath.ParsedAxisSpecifier import *
+from Ft.Xml.XPath.ParsedNodeTest import *
 
-DUMMY_CONTEXT = Context.Context(None)
+__all__ = ['ParsedStep', 'ParsedAbbreviatedStep']
+
+class Step(NodeSetExpression):
 
-class ParsedStep:
     def __init__(self, axis, nodeTest, predicates=None):
         self._axis = axis
         self._nodeTest = nodeTest
         self._predicates = predicates
-        self.isForwardsOnly = axis.forwards
-        self.implicitLimit = NOLIMIT
-        if self._predicates:
-            first_pred_eval = self._predicates[0].evaluate(DUMMY_CONTEXT)
-            if type(first_pred_eval) in NumberTypes:
-                if number.isnan(first_pred_eval):
-                    self.implicitLimit = 0
-                else:
-                    self.implicitLimit = first_pred_eval
-        #print "ParsedStep<%s>.isForwardsOnly"%id(self), self.isForwardsOnly
+        node_type = nodeTest.nodeType or axis.principalType
+        if isinstance(nodeTest, LocalNameTest):
+            node_filter = NameFilter(node_type, None, nodeTest._name)
+        elif isinstance(nodeTest, (PrincipalTypeTest, TextNodeTest,
+                                   CommentNodeTest)):
+            node_filter = TypeFilter(node_type)
+        elif isinstance(nodeTest, (QualifiedNameTest, NamespaceTest)):
+            self.select = self._prime
+            return
+        elif isinstance(nodeTest, ProcessingInstructionNodeTest):
+            node_name = nodeTest._name
+            if node_name:
+                node_filter = NameFilter(node_type, None, node_name)
+            else:
+                node_filter = TypeFilter(node_type)
+        else:
+            assert isinstance(nodeTest, NodeTest)
+            node_filter = None
+        if node_filter:
+            node_filter = node_filter.select
+        if predicates:
+            predicates = [ predicate.select for predicate in predicates ]
+        self.select = StepIter(axis.select, axis.reverse, node_filter,
+                               predicates).select
         return
 
-    def evaluate(self, context, limit=NOLIMIT):
+    def _prime(self, context, nodes):
+        try:
+            namespace = context.processorNss[self._nodeTest._prefix]
+        except KeyError:
+            raise RuntimeException(RuntimeException.UNDEFINED_PREFIX, prefix)
+        node_type = self._nodeTest.nodeType or self._axis.principalType
+        name = getattr(self._nodeTest, '_localName', None)
+        node_filter = NameFilter(node_type, namespace, name)
+        if self._predicates:
+            predicates = [ predicate.select for predicate in self._predicates ]
+        else:
+            predicates = None
+        # Restore normal class behavior
+        self.select = StepIter(self._axis.select, self._axis.reverse,
+                               node_filter.select, predicates)
+        return self.select(context, nodes)
+
+    def select(self, context, nodes):
         """
         Select a set of nodes from the axis, then filter through the node
         test and the predicates.
         """
-        limit = min(limit, self.implicitLimit)
-        (node_set, reverse) = self._axis.select(
-            context, self._nodeTest.match, limit)
-        if self._predicates and len(node_set):
-            node_set = self._predicates.filter(node_set, context, reverse)
-        return node_set
-    select = evaluate
+        axis_iter, reverse, node_filter, predicates = self._funcs
+        for node in nodes:
+            nodes = axis_iter(node)
+            if node_filter:
+                nodes = node_filter(context, nodes)
+            if predicates:
+                for pred_filter in predicates:
+                    nodes = pred_filter(context, nodes)
+            if reverse:
+                nodes = reversed(list(nodes))
+            for node in nodes:
+                yield node
+        return
 
-    def pprint(self, indent=''):
-        print indent + str(self)
-        self._axis.pprint(indent + '  ')
-        self._nodeTest.pprint(indent + '  ')
-        self._predicates and self._predicates.pprint(indent + '  ')
-
-    def __str__(self):
-        return '<Step at %x: %s>' % (id(self), repr(self))
+    def pprint(self, indent='', stream=None):
+        print >> stream, indent + str(self)
+        self._axis.pprint(indent + '  ', stream)
+        self._nodeTest.pprint(indent + '  ', stream)
+        self._predicates and self._predicates.pprint(indent + '  ', stream)
 
     def __repr__(self):
-        result = repr(self._axis) + '::' + repr(self._nodeTest)
+        result = '%r::%r' % (self._axis, self._nodeTest)
         if self._predicates:
             result = result + repr(self._predicates)
         return result
+ParsedStep = Step
 
-class ParsedAbbreviatedStep:
+class AbbreviatedStep(Step):
+    _axis = None
+    _nodeTest = NodeTest()
+    predicates = None
     def __init__(self, parent):
-        self.parent = parent
+        self._parent = parent
+        if parent:
+            self._axis = ParsedParentAxisSpecifier()
+            self.select = self.select_parent
+        else:
+            self._axis = ParsedSelfAxisSpecifier()
+            self.select = self.select_self
+        return
+
+    def select_parent(self, context, nodes):
+        for node in nodes:
+            node = node.parentNode
+            if node:
+                yield node
+        return
 
-    def evaluate(self, context, limit=NOLIMIT):
-        if self.parent:
-            if context.node.nodeType == Node.ATTRIBUTE_NODE:
-                return [context.node.ownerElement]
-            return context.node.parentNode and [context.node.parentNode] or []
-        return [context.node]
-    select = evaluate
+    def select_self(self, context, nodes):
+        return nodes
 
-    def pprint(self, indent=''):
-        print indent + str(self)
+    def evaluateAsNodeSet(self, context):
+        if self._parent:
+            parentNode = context.node.parentNode
+            return parentNode and NodeSet([parentNode]) or NodeSet()
+        return NodeSet([context.node])
+    evaluate = evaluateAsNodeSet
 
-    def __str__(self):
-        return '<AbbreviatedStep at %x: %s>' % (id(self), repr(self))
+    def pprint(self, indent='', stream=None):
+        print >> stream, indent + str(self)
 
     def __repr__(self):
-        return self.parent and '..' or '.'
+        return self._parent and '..' or '.'
+ParsedAbbreviatedStep = AbbreviatedStep
 
 # From the XPath 2.0 Working Draft
@@ -92,20 +147,14 @@
         return
 
-    def evaluate(self, context, limit=NOLIMIT):
+    def evaluateAsNodeSet(self, context):
         """
         Select a set of nodes from the node-set function then filter
         through the predicates.
         """
-        nodeset = self._function.evaluate(context)
-        if not isinstance(nodeset, Types.NodesetType):
-            raise TypeError("%s must be a node-set, not a %s" % (repr(self._function),
-                            Types.g_xpathPrimitiveTypes.get(type(nodeset),
-                            type(nodeset).__name__)))
-
-        if self._predicates and len(nodeset):
-            reverse = 0
-            nodeset = self._predicates.filter(nodeset, context, reverse)
+        nodeset = self._function.evaluateAsNodeSet(context)
+        if self._predicates and nodeset:
+            nodeset = self._predicates.filter(nodeset, context, False)
         return nodeset
-    select = evaluate
+    evaluate = evaluateAsNodeSet
 
     def pprint(self, indent=''):
New file: StringFunctions.py
ViewCVS view:
  http://cvs.4suite.org/viewcvs/4Suite/Ft/Xml/XPath/StringFunctions.py?rev=1.1.2.1&content-type=text/vnd.viewcvs-markup

cvs checkout: cannot find module `StringFunctions.py' - ignored
ViewCVS diff:
  http://cvs.4suite.org/viewcvs/4Suite/Ft/Xml/XPath/XPathTypes.py.diff?r1=1.1.6.1&r2=1.1.6.2
ViewCVS view:
  http://cvs.4suite.org/viewcvs/4Suite/Ft/Xml/XPath/XPathTypes.py?rev=1.1.6.2&content-type=text/vnd.viewcvs-markup

Index: XPathTypes.py
===================================================================
RCS file: /var/local/cvsroot/4Suite/Ft/Xml/XPath/XPathTypes.py,v
retrieving revision 1.1.6.1
retrieving revision 1.1.6.2
diff -U2 -r1.1.6.1 -r1.1.6.2
--- XPathTypes.py	4 Dec 2006 07:06:59 -0000	1.1.6.1
+++ XPathTypes.py	4 Dec 2006 07:56:50 -0000	1.1.6.2
@@ -9,6 +9,8 @@
 """
 
-__all__ = ['NodesetType', 'StringType', 'NumberType', 'BooleanType',
-           'g_xpathPrimitiveTypes', 'g_xpathRecognizedNodes']
+__all__ = [
+    'ObjectType', 'NodesetType', 'StringType', 'NumberType', 'BooleanType',
+    'g_xpathPrimitiveTypes', 'g_xpathRecognizedNodes',
+    ]
 
 from xml.dom import Node
@@ -39,10 +41,6 @@
     }
 
-NumberTypes = {
-    int: True,
-    long: True,
-    float: True
-    }
-
+# Ordered based on occurances
+NumberTypes = (float, int, long)
 
 # -- DOM node types usable as XPath node types -------------------------


More information about the 4suite-checkins mailing list