########################################################################
# $Header: /var/local/cvsroot/4Suite/Ft/Xml/Xslt/__init__.py,v 1.30 2005/04/03 04:14:16 jkloth Exp $
"""
4XSLT initialization and XSLT pattern tools

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

# the order of imports here is very important

XSL_NAMESPACE = u'http://www.w3.org/1999/XSL/Transform'

import MessageSource
Error = MessageSource.Error

# -- XSLT exceptions -------------------------------------------------

from Ft import FtException
class XsltException(FtException):
    def __init__(self, errorCode, *args):
        FtException.__init__(self, errorCode,
                             MessageSource.g_errorMessages, args)
        return

class XsltParserException(XsltException):
    """
    The exception raised when an error is encountered during the parsing
    of a stylesheet.  This eliminates the need for location information
    within each error message.
    """
    def __init__(self, code, locator, *args):
        XsltException.__init__(self, code, *args)

        # Add location information to the message
        msg = MessageSource.POSITION_INFO % (locator.getSystemId(),
                                             locator.getLineNumber(),
                                             locator.getColumnNumber(),
                                             self.message)
        self.message = msg
        return

class XsltRuntimeException(XsltException):
    def __init__(self, code, xsltelement, *args):
        XsltException.__init__(self, code, *args)

        # Add location information to the message
        baseUri = xsltelement.baseUri or '?'
        line = xsltelement.lineNumber or '?'
        col = xsltelement.columnNumber or '?'
        msg = MessageSource.POSITION_INFO % (baseUri, line, col, self.message)
        self.message = msg
        return


# -- element classifications -----------------------------------------

class CategoryTypes:
    """Collection of constants making up the categories of XSLT element"""
    INSTRUCTION = 0
    TOP_LEVEL_ELEMENT = 1
    RESULT_ELEMENT = 2

import XPatternParserc as XPatternParser
parser = XPatternParser

# -- XPattern API ----------------------------------------------------

from xml.dom import Node
class PatternList:
    """
    PatternList is a class that is useful for people writing code to
    process XSLT patterns, especially in groups.
    """
    PARSER = parser.new()
    def __init__(self, patterns, namespaces=None):
        """
        patterns - A list of strings that make up either compiled pattern
                   objects or valid XSLT patterns in string form.
                   It must be either all of one form or all of another
        namespaces - A namespace dictionary - { prefix: uri, ... } -
                     to be used for setting up expressions in the pattern
        """
        self.namespaces = namespaces or {}
        if hasattr(patterns[0], "match"):
            self._patterns = patterns
        else:
            self._patterns = [ self.PARSER.parse(p) for p in patterns ]
        self._shortcuts = [ p.getShortcuts(self.namespaces) for p in self._patterns ]
        self._lookup_table = {}
        self._patternMapping = {}
        i = 0
        for shortcut_list in self._shortcuts:
            for ((subpattern, axis_type), (node_type, expanded_name)) in shortcut_list:
                self._patternMapping[subpattern] = self._patterns[i]
                if node_type == Node.ELEMENT_NODE:
                    if not self._lookup_table.has_key(node_type):
                        self._lookup_table[node_type] = {}
                    if not self._lookup_table[node_type].has_key(expanded_name):
                        self._lookup_table[node_type][expanded_name] = []
                    self._lookup_table[node_type][expanded_name].append((subpattern, axis_type, self._patterns[i]))
                else:
                    if not self._lookup_table.has_key(node_type):
                        self._lookup_table[node_type] = []
                    self._lookup_table[node_type].append((subpattern, axis_type, self._patterns[i]))
            i = i + 1
        self.length = len(self._patterns)
        return

    #def matchAll(nodes):
    #    """Returns the subset of given nodes that match all patterns"""
    #    return [ n for n in nodes if [ ]
    #        ]

    def lookup(self, node, context=None):
        """Return the patterns that match the node (as a list)"""
        if node.nodeType == Node.ELEMENT_NODE:
            #lookup result is a dict for elements
            narrowed_namecheck = self._lookup_table.get(Node.ELEMENT_NODE, {})
            narrowed = narrowed_namecheck.get((node.namespaceURI, node.localName), [])
            #lookup of (ns,local) None is for the element wildcard case '*'
            narrowed.extend(narrowed_namecheck.get(None, []))
        else:
            #lookup result is a list  for non-elements
            narrowed = self._lookup_table.get(node.nodeType, [])
        if node.nodeType not in [ Node.DOCUMENT_NODE, Node.ATTRIBUTE_NODE ]:
            #lookup of nodeType None is for the wildcard case 'node()'
            narrowed.extend(self._lookup_table.get(None, []))
        if not narrowed: return []
        if not context:
            from Ft.Xml.XPath.Context import Context
            context = Context(node.ownerDocument, 1, 1, processorNss=self.namespaces)
        return [ p[2] for p in narrowed if p[0].match(context, node, p[1]) ]

    def lookupAsSet(self, node, context=None):
        """Returns the patterns that match the node (as a set [dictionary])"""
        if node.nodeType == Node.ELEMENT_NODE:
            #lookup result is a dict for elements
            narrowed_namecheck = self._lookup_table.get(Node.ELEMENT_NODE, {})
            narrowed = narrowed_namecheck.get((node.namespaceURI, node.localName), [])
            #lookup of (ns,local) None is for the element wildcard case '*'
            narrowed.extend(narrowed_namecheck.get(None, []))
        else:
            #lookup result is a list  for non-elements
            narrowed = self._lookup_table.get(node.nodeType, [])
        if node.nodeType not in [ Node.DOCUMENT_NODE, Node.ATTRIBUTE_NODE ]:
            #lookup of nodeType None is for the wildcard case 'node()'
            narrowed.extend(self._lookup_table.get(None, []))
        if not narrowed: return {}
        matched_patterns = {}
        if not context: context = Context(node.ownerDocument, 1, 1)
        for p in narrowed:
            if p[0].match(context, node, p[1]):
                matched_patterns[p[2]] = None
        return matched_patterns

    def xsltKeyPrep(self, context, node):
        """
        A special utility used for XSLT key preparation.
        A list of lists is returned.  The outer list corresponds
        to the patterns.  Each inner list is either [node] or []
        depending on whether or not the node matched the corresponding
        pattern.
        """
        matching_patterns = self.lookupAsSet(node, context)
        return [ [node]*matching_patterns.has_key(p) for p in self._patterns ]
        #return [ [node]*(s[1][0] == node.nodeType and (s[1][0] != Node.ELEMENT_NODE or s[1][1] == (node.namespaceURI, node.localName)) and s[0][0].match(context, node)) for s in self._shortcuts ]

# this import must come after all the above
from StylesheetTree import XsltElement
