"""
$RCSfile: SabPythProcessor.py,v $

This class encapsulates an XSLT Processor for use by ZopeXMLMethod.
This is the SabPyth 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]

# Sablot
import Sablot

# Zope
from Acquisition import aq_get
from zope.interface import implements

# python
import os.path, sys, urllib

# local peer classes
from interfaces import IXSLTProcessor

################################################################
# Defaults
################################################################

namespacesPropertyName        = 'URNnamespaces'
parametersPropertyName        = 'XSLparameters'

################################################################
# PyanaProcessor class
################################################################

class SabPythProcessor:
    """
    This class encapsulates an XSLT Processor for use by
    ZopeXMLMethod.  This is the SabPyth version, including support for
    XSLT parameters.  It does not yet include support for URN
    resolution.
    """

    implements(IXSLTProcessor)
    name           = 'Sab-Pyth-0.52'
        
    def __init__(self):
        "Initialize a new instance of SabPythProcessor"
        self.debugLevel = 0

    ################################################################
    # 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 Sablotron specific exceptions and raises an Exception

        """

        if self.debugLevel > 0:
            print "params:", params

        if self.debugLevel > 1:
            print "xsltContents:"
            print xsltContents
            print "xmlContents:"
            print xmlContents

        # Convert the parameters to the form expected by this processor
        xsltParams = {}
        for key, value in params.items():
            self.addParam(xsltParams, key, value)

        namespaceMap   = {}
        if transformObject is not None:
            namespaceMap   = self.retrieveNamespaces(transformObject)
        if self.debugLevel > 1:
            print "namespaces:", namespaceMap

        # create a processor object:
        sp = Sablot.CreateProcessor()

        paramList = xsltParams.items()

        sp.regHandler(Sablot.HLR_SCHEME, UrnSchemeHandler(namespaceMap,
                                                          REQUEST))
        sp.regHandler(Sablot.HLR_MESSAGE, MessageHandler())
        
        # call the run() method:
        sp.run('arg:sheet', 'arg:input', 'arg:output',
               paramList,
               [('input', xmlContents),
                ('sheet', xsltContents)])

        # print the output, assuming it is encoded in utf-8 (the default
        # encoding output by Sablotron):
        text   = unicode(sp.getResultArg('output'), 'utf8')
        result = text.encode('ISO-8859-1')
        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


    ################################################################
    # 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

            
################################################################
# SabPyth API Hooks
################################################################

class MessageHandler:
    """

    MessageHandlers are called by Sablotron to receive error reports,
    display them, keep a log, trace etc.

    """

    def makeCode (self, severity, facility, code):
        """
    
        makes the 'external' error code to report with log() or
        error() call with facility = module id; severity = 1 iff
        critical. 'code' is the error code internal to Sablotron.

        """
        pass
        #print "makeCode", severity, facility, code

    def log (self, code, level, fields):
        """

        pass code created by makeCode, level as necessary. fields is a
        list of strings in form "field:contents". distinguished fields
        include: msg, file, line, token

        """
        pass
        #print "log", code, level, fields

    def error (self, code, level, fields):
        "for reporting errors, meaning as with log()"
        errorMessage = " ".join(fields)
        #print "error", errorMessage
        raise Exception(errorMessage)

class UrnSchemeHandler:
    """
    
    SchemeHandlers are used by Sablotron to resolve URLs that use
    other schemes than the builtin file: and arg: (eg. http: or
    ftp:). An example scheme handler that uses the standard python
    module urllib.py is provided in the file urlhandler.py.
    
    """

    def __init__(self, namespaceMap, REQUEST):
	self.handles      = {}
        self.namespaceMap = namespaceMap
        self.req          = REQUEST

    def getAll (self, scheme, rest, bytecount):
        """

        open the URI and return the whole string
        scheme = URI scheme (e.g. "http")
        rest = the rest of the URI (without colon)

        """
	#print 'getall', scheme, rest, bytecount

        if self.isRecognizedURN(scheme, rest):

            uriParts = rest.split(':')
            nid      = uriParts[0][1:] # for some reason, sablotron pre-pends a slash
            nss      = uriParts[1]
            #print "resolving urn", nid, nss
            
            base = self.namespaceMap.get(nid, None)
            st   = self.acquireObjectContents(base, nss, self.req)            
            return st
        else:
            # Maybe it is a URL.  Give it a try
            url = scheme + ':' + rest
            #print "resolving url", url
            
            return urllib.urlopen(url).read()

    def open (self, scheme, rest):
        """

        open: open the URI and return a handle
        scheme = URI scheme (e.g. "http")
        rest = the rest of the URI (without colon)
        the resulting handle is returned in '*handle'

        """
	#print 'open', scheme, rest

        handle = self.gethandle()

        if self.isRecognizedURN(scheme, rest):
            uriParts = rest.split(':')
            nid      = uriParts[0][1:] # for some reason, sablotron pre-pends a slash
            nss      = uriParts[1]
            #print "resolving urn", nid, nss
            
            base   = self.namespaceMap.get(nid, None)
            self.handles[handle] = self.acquireObjectContents(base, nss, self.req)
        else:
            # Maybe it is a URL.  Give it a try            
            url = scheme + ':' + rest
            #print "resolving url", url
            
            self.handles[handle] = urllib.urlopen(url)            
        return handle
    
    def get (self, handle, bytecount):
        """

        get: retrieve data from the URI
        handle = the handle assigned on open
        buffer = pointer to the data
        *byteCount = number of bytes to read (the number actually read is returned here) 

        """
	#print 'get'
	return self.handles[handle].read(bytecount)
        
    def put (self, handle, bytecount):
        """
        
        put: save data to the URI (if possible)
        handle = the handle assigned on open
        buffer = pointer to the data
        *byteCount = number of bytes to write (the number actually written is returned here)

        """
	#print 'put', handle, len(buffer)
	pass

    def close (self, handle):
        """
        close: close the URI with the given handle
        handle = the handle assigned on open 
        """
	#print 'close', handle
	self.handles[handle] = None

    def gethandle(self):
        "get next available handle"
        next = '%s' % (len(self.handles))
        self.handles[next] = ""
        return next

    def acquireObjectContents(self, base, contextURL, REQUEST):
        """

        Obtain the contents of the Zope object indicated by the passed
        in context, starting from the passed in base object.

        """
        #print "acquire contents for:", contextURL, base
        zObject     = base
        #print "base", zObject.getId()
        #
        # why doesn't the below work?  Is this a bug?
        # (see com/arielpartners/website/scripts/resolver.dtml)
        # zObject = base.restrictedTraverse(contextURL)
        
        # sigh.   Do it the hard way.
        contextList = contextURL.split('/')
        for context in contextList:
            zObject = aq_get(zObject,context,None)
            if zObject is None:
                return None
        contents = zObject(zObject, REQUEST)
        return contents

    def isRecognizedURN(self, scheme, uri):
        "Return true if this uri is of a format we recognize"
        uriParts = uri.split(':')
        return scheme == 'urn' and len(uriParts) == 2

################################################################
# Register ourselves with the Processor Registry
################################################################

from ProcessorRegistry import ProcessorRegistry
klass = SabPythProcessor
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"
