########################################################################
# $Header: /var/local/cvsroot/4Suite/Ft/Xml/Xslt/XPatterns.py,v 1.7 2004/12/22 01:22:03 jkloth Exp $
"""
Implement Patterns according to the XSLT spec

Copyright 1999-2004 Fourthought, Inc. (USA).
Detailed license and copyright information: http://4suite.org/COPYRIGHT
Project home, documentation, distributions: http://4suite.org/
"""

from xml.dom import Node

ChildAxis = Node.ELEMENT_NODE
AttributeAxis = Node.ATTRIBUTE_NODE

class Patterns:
    def __init__(self, patterns):
        self.patterns = patterns

    def getShortcuts(self, namespaces):
        return [ (pattern.getShortcut(), pattern.getQuickKey(namespaces))
                 for pattern in self.patterns ]

    def match(self, context, node):
        for pattern in self.patterns:
            if pattern.match(context, node):
                return 1
        return 0

    def pprint(self, indent=''):
        print indent + str(self)
        for pattern in self.patterns:
            pattern.pprint(indent + '  ')
        return

    def __str__(self):
        return '<Patterns at %x: %s>' % (id(self), repr(self))

    def __repr__(self):
        result = repr(self.patterns[0])
        for pattern in self.patterns[1:]:
            result = result + ' | ' + repr(pattern)
        return result

class Pattern:
    def __init__(self, steps):
        # The steps are already in reverse order
        self.steps = steps
        self.priority = 0.5
        return

    def getShortcut(self):
        #FIXME: what's up with this description?
        # A shortcut is (pattern, (pattern, extra_arg))
        if len(self.steps) == 1:
            (axis_type, node_test, ancestor) = self.steps[0]
            shortcut = (node_test, axis_type)
        else:
            shortcut = (self, None)
        return shortcut

    def getQuickKey(self, namespaces):
        (axis_type, node_test, ancestor) = self.steps[0]
        (node_type, expanded_name) = node_test.getQuickKey(namespaces)
        if axis_type == Node.ATTRIBUTE_NODE:
            node_type = axis_type
        return (node_type, expanded_name)

    def match(self, context, node, dummy=None):
        (axis_type, node_test, ancestor) = self.steps[0]
        if not node_test.match(context, node, axis_type):
            return 0
        for (axis_type, node_test, ancestor) in self.steps[1:]:
            # Move up the tree
            if axis_type == Node.ATTRIBUTE_NODE:
                node = node.ownerElement
            else:
                node = node.parentNode
            if ancestor:
                while node:
                    if node_test.match(context, node, axis_type):
                        break
                    if axis_type == Node.ATTRIBUTE_NODE:
                        node = node.ownerElement
                    else:
                        node = node.parentNode
                else:
                    # We made it to the document without a match
                    return 0
            elif node is None: return 0
            elif not node_test.match(context, node, axis_type):
                return 0
        return 1

    def pprint(self, indent=''):
        print indent + str(self)

    def __str__(self):
        return '<Pattern at %x: %s>' % (id(self), repr(self))
    
    def __repr__(self):
        result = ''
        for (axis, test, ancestor) in self.steps:
            if axis == Node.ATTRIBUTE_NODE:
                step = '@' + repr(test)
            else:
                step = repr(test)
            result = step + (ancestor and '//' or '/') + result
        # remove trailing slash
        return result[:-1]


class PredicatedNodeTest:
    def __init__(self, nodeTest, predicateList):
        self.nodeTest = nodeTest
        self.predicates = predicateList
        self.priority = 0.5
        return

    def getQuickKey(self, namespaces):
        return self.nodeTest.getQuickKey(namespaces)

    def match(self, context, node, principalType):
        if principalType == Node.ATTRIBUTE_NODE:
            node_set = node.ownerElement.attributes.values()
        elif node.parentNode:
            node_set = node.parentNode.childNodes
        else:
            # Must be a document
            return 0

        # Pass through the NodeTest
        node_set = [ n for n in node_set if self.nodeTest.match(context, n, principalType) ]

        # Child and attribute axes are forward only
        node_set = self.predicates.filter(node_set, context, reverse=0)
        return node in node_set

    def __str__(self):
        return '<%s at %x: %s>' % (
            self.__class__.__name__,
            id(self),
            repr(self))

    def __repr__(self):
        return repr(self.nodeTest) + repr(self.predicates)

class DocumentNodeTest:
    def __init__(self):
        self.priority = 0.5

    def getQuickKey(self, namespaces):
        return (Node.DOCUMENT_NODE, None)

    def match(self, context, node, principalType):
        return node.nodeType == Node.DOCUMENT_NODE

    def __str__(self):
        return '<%s at %x: %s>' % (
            self.__class__.__name__,
            id(self),
            repr(self))

    def __repr__(self):
        return '/'

class IdKeyNodeTest:
    def __init__(self, idOrKey):
        self.priority = 0.5
        self.idOrKey = idOrKey

    def getQuickKey(self, namespaces):
        return (None, None)

    def match(self, context, node, principalType):
        return node in self.idOrKey.evaluate(context)

    def __str__(self):
        return '<%s at %x: %s>' % (
            self.__class__.__name__,
            id(self),
            repr(self))

    def __repr__(self):
        return repr(self.idOrKey)

