""" $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: Craeg Strong Release: 1.0 """ __cvstag__ = '$Name: $'[6:-2] __date__ = '$Date: 2003/03/30 03:45:47 $'[6:-2] __version__ = '$Revision: 1.12 $'[10:-2] # Sablot import Sablot # Zope from Acquisition import aq_get # python import os.path, sys, urllib # local peer classes from IXSLTProcessor 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 IXSLTProcessor 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 def transform(self, xmlContents, xmlURL, xsltContents, xsltURL, transformObject = None, 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 exceptions thrown by transformGuts and sends the error output to stderr, returns empty string to the caller. The idea is that web site users will at worst see an empty page. """ topLevelParams = [] if transformObject is not None: topLevelParams = self.getXSLParameters(transformObject) if self.debugLevel > 0: print "params:", topLevelParams if self.debugLevel > 1: print "xsltContents:" print xsltContents print "xmlContents:" print xmlContents try: result = self.transformGuts(xmlContents, xmlURL, xsltContents, xsltURL, transformObject, topLevelParams, REQUEST) except Exception, e: sys.stderr.write(str(e) + '\n') return "" 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 transformGuts(self, xmlContents, xmlURL, xsltContents, xsltURL, transformObject = None, params = [], REQUEST = None): """ Actually performs the transformation. Throws an Exception if there are any errors. """ 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 = [] if params: paramList = params.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 ################################################################ # 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 def getXSLParameters(self, transformObject): """ Return XSL Transformer parameters as a dictionary of strings in the form 'name:value' as would be passed to an XSLT engine like Saxon, 4suite, etc. The values are obtained by looking for a property in the current context called 'XSLparameters', which should be a list of strings. Each name on the list is looked up in the current context. If its value is a scalar, then the pair 'name:value' is returned. If the value is an object, then the pair 'name:url' is returned where url is the absolute URL of the object. The key (name) is actually a tuple of two strings, the first of which is an optional namespace (we don't use this today). """ parms = aq_get(transformObject,parametersPropertyName,None) result = {} if parms is not None: for p in parms: value = aq_get(transformObject,p,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): self.addParam(result, p, value.absolute_url()) else: self.addParam(result, p, 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