##############################################################################
# 
# Zope Public License (ZPL) Version 1.0
# -------------------------------------
# 
# Copyright (c) Digital Creations.  All rights reserved.
# 
# This license has been certified as Open Source(tm).
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
# 
# 1. Redistributions in source code must retain the above copyright
#    notice, this list of conditions, and the following disclaimer.
# 
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions, and the following disclaimer in
#    the documentation and/or other materials provided with the
#    distribution.
# 
# 3. Digital Creations requests that attribution be given to Zope
#    in any manner possible. Zope includes a "Powered by Zope"
#    button that is installed by default. While it is not a license
#    violation to remove this button, it is requested that the
#    attribution remain. A significant investment has been put
#    into Zope, and this effort will continue if the Zope community
#    continues to grow. This is one way to assure that growth.
# 
# 4. All advertising materials and documentation mentioning
#    features derived from or use of this software must display
#    the following acknowledgement:
# 
#      "This product includes software developed by Digital Creations
#      for use in the Z Object Publishing Environment
#      (http://www.zope.org/)."
# 
#    In the event that the product being advertised includes an
#    intact Zope distribution (with copyright and license included)
#    then this clause is waived.
# 
# 5. Names associated with Zope or Digital Creations must not be used to
#    endorse or promote products derived from this software without
#    prior written permission from Digital Creations.
# 
# 6. Modified redistributions of any form whatsoever must retain
#    the following acknowledgment:
# 
#      "This product includes software developed by Digital Creations
#      for use in the Z Object Publishing Environment
#      (http://www.zope.org/)."
# 
#    Intact (re-)distributions of any official Zope release do not
#    require an external acknowledgement.
# 
# 7. Modifications are encouraged but must be packaged separately as
#    patches to official Zope releases.  Distributions that do not
#    clearly separate the patches from the original work must be clearly
#    labeled as unofficial distributions.  Modifications which do not
#    carry the name Zope may be packaged in any form, as long as they
#    conform to all of the clauses above.
# 
# 
# Disclaimer
# 
#   THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
#   EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
#   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
#   PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
#   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
#   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
#   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
#   USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
#   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
#   OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
#   OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
#   SUCH DAMAGE.
# 
# 
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations.  Specific
# attributions are listed in the accompanying credits file.
# 
##############################################################################
"""
Zope Product creation for Parsed XML.
"""

from OFS.SimpleItem import SimpleItem
from AccessControl.Role import RoleManager
from Persistence import Persistent
from Acquisition import Implicit
from OFS.Cache import Cacheable
import Globals

from ManageableDOM import ManageableDocument, DOMManageable, \
     theDOMImplementation
from StrIO import StringIO
from xml.parsers import expat
import DOM, ExtraDOM

from types import FileType, StringType
from urllib import quote

from NodePath import registry

_marker = []  # dummy default object for cache return

parserr=('Sorry, an XML parsing error occurred.  Check '
         'your XML document for well-formedness and try '
         'to upload it again after modification.<br><br>')

def manage_addParsedXML(self,id,title='',file='',
                        useNamespaces = 1, contentType = "text/xml",
                        REQUEST=None,submit=None):
    "Add a Parsed XML instance with optional file content."

    if not file or not isinstance(file, StringType):
        file ='<?xml version = "1.0"?><emptydocumentElement/>'
    try:
        ob = ParsedXML(id, file, useNamespaces, contentType)
    except expat.error, e:
        if REQUEST is not None:
            err = "%s%s" % (parserr, '<font color="red">%s</font>'
                            % getattr(e, 'args', ''))
            return Globals.MessageDialog(
                title= 'XML Parsing Error',
                message = err,
                action='manage_main')
        raise
    ob.title = str(title)
    id = self._setObject(id, ob)
    if REQUEST is not None:
        try: u=self.DestinationURL()
        except: u=REQUEST['URL1']
        if submit==" Add and Edit ": u="%s/%s" % (u,quote(id))
        REQUEST.RESPONSE.redirect(u+'/manage_main')


manage_addParsedXMLForm = Globals.DTMLFile('dtml/documentAdd', globals())


def createDOMDocument(XMLstring = None, namespaces = 1):
    "Helper function to create a DOM document, without any proxy wrappers."
    if XMLstring:
        XMLstring=StringIO(XMLstring)
        # more efficient to not use ExtraDOM here
        return DOM.ExpatBuilder.parse(XMLstring, namespaces)
    # we use DOM.theDOMImplementation, not ManageableDOMs, for efficiency
    return DOM.theDOMImplementation.createDocument(
        None, "mydocument", None)

contentTypes = ['text/html', 'application/html', 'text/xml']
    
class ParsedXML(SimpleItem, ManageableDocument, Cacheable):
    "The Parsed XML top object, and the persistent head of the tree."
     
    meta_type = 'Parsed XML'

    manage_editForm = Globals.HTMLFile('dtml/persEdit',globals())
    
    manage_options = (DOMManageable.manage_options +
                      RoleManager.manage_options +
                      Cacheable.manage_options)

    __ac_permissions__ = (('View management screens', ('manage_main',),
                           ('Manager',)),
                          ('View DOM hierarchy', ('manage_DOMTree',),
                           ('Manager',)),
                          ('Edit ParsedXML', ('manage_editForm',),
                           ('Manager',)),
                          ('View source', ('index_html',),
                           ('Manager',)),
                          ('Access contents information',
                           ('objectIds', 'objectValues', 'objectItems',
                            ''),
                           ('Manager',)),)

    icon = 'misc_/ParsedXML/pxml.gif'

    def __init__(self, id, XMLstring = None, namespaces = 1,
                 contentType = "text/xml"):
        "Initialize a Parsed XML object"
        self.id = id
        self._persistentDoc = self # used by transient proxies
        self.noNamespaces = not namespaces
        if contentType not in contentTypes:
            import string
            raise RuntimeError, (
                "Bad content type %s; valid types are %s"
                % (str(contentType), string.join(contentTypes)))
        self.contentType = contentType
        self.initFromDOMDocument(createDOMDocument(XMLstring, namespaces))
        if XMLstring:
            self._lenCache = len(XMLstring)
        else:
            self._lenCache = len(str(self))
        self._lenCorrect = 1

    initFromDOMDocument__roles__ = ()  # Private
    def initFromDOMDocument(self, DOMdoc):
        "Initialize a Parsed XML from a DOM Document"
        # inherit from ManageableDocument
        ParsedXML.inheritedAttribute('__init__')(self, DOMdoc, self)

    def manage_afterAdd(self, object, container):
        """Store the container that we're being added to.  This is used
        to traverse back to the persistent document."""
        self._container = container

    #
    # methods that deal with persistence
    #

    # DOM nodes use __changed__ because its acquirable
    # Setting _p_changed is equivalent.
    def __changed__(self, *args):
        """Override the acquired __changed__ method used by the non-DB
        DOM nodes, update length cache, and mark this object as dirty."""
        if getattr(self, '_v_lenCorrect', None):
            self._lenCorrect = 1
        else:
            self._lenCorrect = 0
        self.ZCacheable_invalidate()            
        self._p_changed = 1

    # wrap DOMIO to provide cacheing
    def index_html(self, REQUEST = None, RESPONSE = None):
        "Returns publishable source according to content type"
        if RESPONSE:
            RESPONSE.setHeader('Content-type', self.contentType)
        data = self.ZCacheable_get(default = _marker)
        if data is not _marker:
            return data
        # inherit from DOMIO
        data = ParsedXML.inheritedAttribute('index_html')(self,
                                                          REQUEST, RESPONSE)
        self.ZCacheable_set(data)
        return data
    
    def get_size(self):
        "Length of the XML string representing this node in characters."
        if not getattr(self, '_lenCorrect', 0):
            self._lenCache = len(str(self))
            self._lenCorrect = 1
        return self._lenCache

    # methods that override ZDOM methods that are incorrect
    #
    
    def getElementsByTagName(self, tagName):
        return self.__getattr__("getElementsByTagName")(tagName)

    def hasChildNodes(self):
        return self.__getattr__("hasChildNodes")()

    # 
    # methods that override SimpleItem; sigh, multiple inheritance.
    #

    def objectValues(self, spec=None):
        """
        Returns a list of actual subobjects of the current object.
        If 'spec' is specified, returns only objects whose meta_type
        match 'spec'.
        """
        return ManageableDocument.objectValues(self, spec)
    
    def objectIds(self, spec=None):
        """
        Returns a list of subobject ids of the current object.
        If 'spec' is specified, returns objects whose meta_type
        matches 'spec'.
        """
        return ManageableDocument.objectIds(self, spec)

    def objectItems(self, spec=None):
        """
        Returns a list of (id, subobject) tuples of the current object.
        If 'spec' is specified, returns only objects whose meta_type match
        'spec'
        """
        return ManageableDocument.objectItems(self, spec)
    
    def tpValues(self):
        "Return a list of immediate subobjects.  Used by the dtml-tree tag."
        return ManageableDocument.tpValues(self)

    # override ManageableDocument's method; we can't persist new DOM node by
    # hanging off of parents
    def parseXML(self, file):
        "parse file as XML, replace DOM node with resulting tree, return self"
        namespaces = not self.noNamespaces
        node = ExtraDOM.parseFile(self.getDOMObj(), file, namespaces)
        self.initFromDOMDocument(node)
        self.__changed__(1)
        return self

    def getDOM(self):
        """Get the Document node of the DOM tree.
        """
        return self

    def getNodePath(self, scheme_name, node):
        """Create the node path for a particular node in the tree.
        """
        # if we're asking for node of this document itself
        if node is self:
            return 'scheme_name'
        # otherwise ask for nodepath of node
        return node.getNodePath(scheme_name)
        
    def resolveNodePath(self, path):
        """Resolve node path from top of the tree to node.
        """
        # start resolving from the document node
        doc = self._persistentDoc._earlyAqChain()
        # FIXME: could use raw DOM instead of management wrappers
        return registry.resolve_path(doc, path)
        
    def __getitem__(self, s):
        """Handle node paths.
        """
        # backwards compatibility -- handle classic 0/1/2 paths
        try:
            return self.childNodes[int(s)].__of__(self)
        except ValueError:
            pass
        
        # start resolving from the document node
        doc = self._persistentDoc._earlyAqChain()
        # FIXME: could use raw DOM instead of management wrappers
        result = registry.resolve_path(doc, s)
        # FIXME: does this convince ZPublisher to show NotFound?
        if result is None:
            raise KeyError, "Could not resolve node path."
        return result
    
Globals.default__class_init__(ParsedXML) # activate perms
