"""
$RCSfile: LibXsltProcessor.py,v $

This class encapsulates an XSLT Processor for use by ZopeXMLMethods.
This is the GNOME libxslt version, including support for XSLT
parameters.  It does not yet include support for URN resolution.

Author: <a href="mailto:cstrong@arielpartners.com">Craeg Strong</a>
Release: 1.0
"""

__cvstag__  = '$Name:  $'[6:-2]
__date__    = '$Date: 2003/06/16 05:47:53 $'[6:-2]
__version__ = '$Revision: 1.3 $'[10:-2]

# GNOME libxslt
import libxml2
import libxslt

# Zope
from Acquisition import aq_get
from zope.interface import implements

# python
import sys

# local peer classes
from interfaces import IXSLTProcessor

################################################################
# Defaults
################################################################

namespacesPropertyName        = 'URNnamespaces'
catalogPropertyName           = 'XMLcatalog'

################################################################
# LibXsltProcessor class
################################################################

class LibXsltProcessor:
    """
    
    This class encapsulates an XSLT Processor for use by ZopeXMLMethods.
    This is the GNOME libxslt version, including support for XSLT
    parameters.  It does not yet include support for URN resolution.

    """

    implements(IXSLTProcessor)
    name           = 'libxslt 1.0.27'
        
    def __init__(self):
        "Initialize a new instance of LibXsltProcessor"
        self.debugLevel = 0

        libxml2.registerErrorHandler(self.errorHandler, "")
        libxslt.registerErrorHandler(self.errorHandler, "")

        libxml2.lineNumbersDefault(1)
        libxml2.substituteEntitiesDefault(1)

    ################################################################
    # Methods implementing the IProcessor interface
    ################################################################

    def setDebugLevel(self, level):
        """

        Set debug level from 0 to 3.
        0 = silent
        3 = extra verbose
        Debug messages go to Zope server log.

        """
        self.debugLevel   = level
        
    ################################################################
    # Methods implementing the IXSLTProcessor interface
    ################################################################

    def transform(self, xmlContents, xmlURL, xsltContents, xsltURL,
                  transformObject = None, params = {}, REQUEST = None):
        """

        Transforms the passed in XML into the required output (usually
        HTML) using the passed in XSLT.  Both the XML and XSLT strings
        should be well-formed.  Returns the output as a string.
        transformObject and REQUEST params may be used to acquire Zope
        content such as XSLT parameters and URN namespaces, if
        required.

        Catches any libxslt specific exceptions and raises an Exception

        """

        if self.debugLevel > 1:
            print "params:", params

        if self.debugLevel > 1:
            print "xsltContents:"
            print xsltContents
            print "xmlContents:"
            print xmlContents

        #
        # URN Resolution not yet supported, so no use looking up namespaces yet
        # @@ CKS 10/14/2002
        #
        #namespaceMap   = {}
        #if transformObject is not None:
        #    namespaceMap   = self.retrieveNamespaces(transformObject)
        #if self.debugLevel > 1:
        #    print "namespaces:", namespaceMap

        #
        # Catalogs not yet adequately supported (see below)
        #
#         catalog = None
#         if transformObject is not None:
#             catalog = self.getXMLCatalog(transformObject)
#         if catalog is None:
#             print "no XML catalog registered"
#         else:
#             print "catalog:", catalog

        # Convert the parameters to the form expected by this processor
        xsltParams = {}
        for key, value in params.items():
            self.addParam(xsltParams, key, value)

        try:
            styleDoc  = libxml2.parseDoc(xsltContents)
            styleDoc.setBase(xsltURL)
        except libxml2.parserError, e:
            message = "XML parse error for XSLT file %s: %s" % (xsltURL, str(e))
            raise Exception(message)

        style     = libxslt.parseStylesheetDoc(styleDoc)

        try:
            xmlDoc    = libxml2.parseDoc(xmlContents)
            xmlDoc.setBase(xmlURL)
        except libxml2.parserError, e:
            message = "XML parse error for XML document: %s" % (str(e))
            raise Exception(message)

        resultDoc = style.applyStylesheet(xmlDoc, xsltParams)
        result    = style.saveResultToString(resultDoc)
        
        style.freeStylesheet()
        xmlDoc.freeDoc()
        resultDoc.freeDoc()
        
        return result

    def addParam(self, paramMap, name, value):
        """

        This is a convenience function for adding parameters in the
        correct format to the parameter map to be used for the
        'params' parameter in transformGuts.
        
        """
        paramMap[ name ] = "'%s'" % (value)
        return paramMap


#     def getXMLCatalog(self, transformObject):
#         """
#         Find the OASIS TR9401 and XML Input Resolver, if any.  They
#         are registered by defining a property called 'XMLcatalog'
#         somewhere in the acquisition path, pointing to a zope object
#         whose contents is the catalog.
#
#         Two libxslt limitations stymied my efforts: (1) no python
#         binding for thread-safe xmlCatalogAddLocal() (2) Can't load
#         catalog from URI instead of filename oh well, no catalogs
#         yet.  CKS 3/18/2003
#         """
#         catalogFileName = aq_get(transformObject,
#                                  catalogPropertyName,None)
#         if catalogFileName is not None:
#             catalogObject = aq_get(transformObject, catalogFileName)
#             if catalogObject is not None:
#                 print "loading catalog", catalogObject.absolute_url(), catalogObject()
#                 #catalogDoc = libxml2.parseDoc(catalogObject())
#                 #catalog = libxml2.newCatalog(catalogDoc)
#                 catalog = libxml2.loadCatalog(catalogObject.absolute_url())
#                 return catalog
#         return None

    ################################################################
    # LibXml API Hooks
    ################################################################
    
    def errorHandler(self, ctx, error):
        """
        
        The default error handler for libxml2 and libxslt prints out
        messages to stderr.  Throw an exception instead.
        
        """
        raise Exception(error)

    ################################################################
    # Utility methods
    ################################################################

    def retrieveNamespaces(self, transformObject):
        """

        retrieves Namespaces defined for URI Resolution

        """
        NIDs   = aq_get(transformObject,namespacesPropertyName,None)
        result = {}
        if NIDs is not None:
            for n in NIDs:
                value = aq_get(transformObject,n,None)
                # I use callable() to determine if it is not a scalar.
                # If not, it must be a Zope object (I think) - WGM
                if callable(value):
                    result[n] = value
                else:
                    result[n] = str(value)
        return result

################################################################
# Register ourselves with the Processor Registry
################################################################

from ProcessorRegistry import ProcessorRegistry
klass = LibXsltProcessor
try:
    proc = klass()
    ProcessorRegistry.register(proc)
    #print "Registered processor", klass.name, "for use with ZopeXMLMethods"
except:
    print "Processor", klass.name, "not available for use with ZopeXMLMethods"
