""" $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: Craeg Strong Release: 1.0 """ __cvstag__ = '$Name: $'[6:-2] __date__ = '$Date: 2003/03/30 03:45:47 $'[6:-2] __version__ = '$Revision: 1.17 $'[10:-2] # GNOME libxslt import libxml2 import libxslt # Zope from Acquisition import aq_get # python import sys # local peer classes from IXSLTProcessor import IXSLTProcessor ################################################################ # Defaults ################################################################ namespacesPropertyName = 'URNnamespaces' parametersPropertyName = 'XSLparameters' 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 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 = None if transformObject is not None: topLevelParams = self.getXSLParameters(transformObject) if self.debugLevel > 1: 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 = None, REQUEST = None): """ Actually performs the transformation. Throws an Exception if there are any errors. """ # # 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 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, params) result = style.saveResultToString(resultDoc) style.freeStylesheet() xmlDoc.freeDoc() resultDoc.freeDoc() return result # 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 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