"""
$RCSfile: XPathMethod.py,v $

ZopeXMLMethods provides methods to apply to Zope objects for XML/XSLT
processing.  XPathMethod applies XPath expressions to XML documents.
XPathMethod automatically filters an XML document via XPath, where the
XML document is obtained from another Zope object (the 'source'
object) via acquisition.

Author: Craeg Strong <cstrong@arielpartners.com>
Release: 1.0

$Id: XPathMethod.py,v 1.2 2003/06/16 05:47:53 arielpartners Exp $
"""

__cvstag__  = '$Name:  $'[6:-2]
__date__    = '$Date: 2003/06/16 05:47:53 $'[6:-2]
__version__ = '$Revision: 1.2 $'[10:-2]

# peer classes/modules
from interfaces import IXPathMethod
from processors.interfaces import IXPathProcessor
from processors.ProcessorRegistry import ProcessorRegistry
from GeneratorRegistry import GeneratorRegistry

# our base class
from XMLMethod import XMLMethod, getPublishedResult, \
     PERM_VIEW, PERM_EDIT, PERM_FTP, PERM_CONTENT, PERM_MANAGE

# Zope builtins
import Globals
from Globals import MessageDialog
from AccessControl import ClassSecurityInfo
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from zope.interface import implements

################################################################
# Defaults
################################################################
xpathExpressionPropertyName        = 'XPathexpression'

################################################################
# Module Scoped Convenience Methods
################################################################

def addInstance(folder,
                id, title='', description='', selected_processor='',
                xpathExpression='', content_type='text/xml',
                behave_like='', debugLevel=0):
    """
    This is a convenience factory method for creating an instance of
    XPathMethod.  It returns the object created, and may therefore be
    more convenient than the addXPathMethod() method it calls.  It is
    used by the unit testing programs.
    """
    folder.manage_addProduct['ZopeXMLMethods'].addXPathMethod(
        id, title, description, selected_processor, xpathExpression,
        content_type, behave_like, debugLevel)
    return folder[id]

################################################################
# Contructors
################################################################

manage_addXPathMethodForm = PageTemplateFile('www/createXPath.pt', globals())
def manage_addXPathMethod(self, id, title='', description='',
                          selected_processor='', xpathExpression='',
                          content_type='text/xml',
                          behave_like='', debugLevel=0,
                          REQUEST=None, RESPONSE=None):
    """
    Factory method to create an instance of XPathMethod, called from
    a GUI in the Zope Management Interface.  It calls addXPathMethod
    to actually do the work.
    """
    try:
        self.addXPathMethod(id, title, description, selected_processor,
                            xpathExpression, content_type,
                            behave_like, debugLevel)
        message = 'Successfully created ' + id + ' XPathMethod object.'

        if REQUEST is None:
            return

        try:
            url = self.DestinationURL()
        except:
            url = REQUEST['URL1']
        REQUEST.RESPONSE.redirect(url + "/manage_main")
        
    except Exception, e:
        message = str(e)
        message.replace('\n','<br/>')
        return MessageDialog(title   = 'Error',
                             message = message,
                             action  = 'manage_main')

def addXPathMethod(self, id, title='', description='',
                   selected_processor='', xpathExpression='',
                   content_type='text/xml', behave_like='', debugLevel=0):
    """
    Factory method to actually create an instance of XPathMethod and
    return it.

    You should call this method directly if you are creating an
    instance of XPathMethod programatically.
    """

    if not id or not xpathExpression:
        raise Exception('Required fields must not be blank')

    # Insert test for invalid XPath here
    if 0:
        message = "Invalid XPath expression %s" % (xpathExpression)
        raise Exception(message)

    self._setObject(id, XPathMethod(id, title, description,
                                    selected_processor, xpathExpression,
                                    content_type, behave_like, debugLevel))
    self._getOb(id).reindex_object()

def availableProcessors(self):
    """
    Return names of currently available XPath processor libraries
    """
    return ProcessorRegistry.names(IXPathProcessor)        


################################################################
# Main class
################################################################

class XPathMethod(XMLMethod):
    """
    Automatically filters an XML document via XPath, where the XML
    document is obtained from another Zope object (the 'source'
    object) via acquisition.
    """

    meta_type = 'XPath Method'

    implements(IXPathMethod)

    _security = ClassSecurityInfo()

    _properties = XMLMethod._properties + (
        {'id':'xpathExpression',    'type':'string',    'mode': 'w' },        
        )
    
    # manage_options inherited from XMLMethod

    def __init__(self, id, title, description, selected_processor,
                 xpathExpression, content_type="text/html",
                 behave_like='', debugLevel=0):

        XMLMethod.__init__(self, id, title, description,
                           selected_processor, content_type,
                           behave_like, debugLevel)

        # string attributes
        self.xpathExpression    = xpathExpression

        # if selected_processor is blank, get the default
        if not self.selected_processor:
            self.selected_processor = ProcessorRegistry.defaultName(IXPathProcessor)

    ################################################################
    # Methods implementing the IXPathMethod interface below
    ################################################################

    _security.declarePublic('availableProcessors')
    def availableProcessors(self):
        """
        Return names of currently available XPath processor libraries
        """
        return ProcessorRegistry.names(IXPathProcessor)        

    _security.declarePublic('processor')
    def processor(self):
        """
        Obtain the object encapsulating the selected XPath processor.
        """
        return ProcessorRegistry.item(IXPathProcessor,
                                      self.selected_processor)

    _security.declareProtected(PERM_VIEW, 'evaluateString')
    def evaluateString(self, REQUEST):
        """
        Generate result of applying XPath against source XML and
        return it as a string
        """

        processor   = self.processor()
        xmlObject   = self.getXmlSourceObject()
        xmlContents = getPublishedResult("XML source", xmlObject, REQUEST)
        xmlURL      = xmlObject.absolute_url()
        
        processor.setDebugLevel(self.debugLevel)

        # Q: why don't we precompile the expression?
        # A: because we don't want to pickle a processor-specific type
        # CKS 6/13/2003
        expr = processor.compile(self.xpathExpression)
        return expr.evaluateString(xmlContents, xmlURL, prettyPrint = 1)

    _security.declareProtected(PERM_VIEW, 'evaluateDOM')
    def evaluateDOM(self, REQUEST):
        """
        Generate result of applying XPath against source XML and
        return it as a list of DOM nodes
        """

        processor   = self.processor()
        xmlObject   = self.getXmlSourceObject()
        xmlContents = getPublishedResult("XML source", xmlObject, REQUEST)
        xmlURL      = xmlObject.absolute_url()
        
        processor.setDebugLevel(self.debugLevel)

        # Q: why don't we precompile the expression?
        # A: because we don't want to pickle a processor-specific type
        # CKS 6/13/2003
        expr = processor.compile(self.xpathExpression)
        return expr.evaluateDOM(xmlContents, xmlURL)

    ################################################################
    # Standard Zope stuff
    ################################################################

    _security.declareProtected(PERM_VIEW, '__call__')
    def __call__(self, client=None, REQUEST=None, RESPONSE=None):
        """
        Render self by processing its content with the named XPath
        """
        rawResult = self.evaluateString(REQUEST)
            
        if self.behave_like == "":
            behave_like = self.getXmlSourceObject().meta_type
        else:
            behave_like = self.behave_like

        gen = GeneratorRegistry.getGenerator(behave_like)
        if gen is None:
            gen = GeneratorRegistry.getDefaultGenerator()

        # explicitly set the Content-Type here because calling the XML
        # source object or the XPath method might have
        # changed it and the call of gen.getResult() below doesn't
        # guarantee that it will be changed back.
        if RESPONSE is not None:
            RESPONSE.setHeader("Content-Type", self.content_type)

        obj = gen.createObject(self.id, self.title, rawResult,
                               content_type = self.content_type)

        if client is None:
            client = self

        return gen.getResult(obj, client, REQUEST, RESPONSE)

    ################################################################
    # Support for Schema Migration
    ################################################################

    def repair(self):
        """
        Repair this object.  This method is used for schema migration,
        when the class definition changes and existing instances of
        previous versions must be updated.
        """
        pass
        # nothing to repair at the moment

# register security information
Globals.InitializeClass(XPathMethod)
