
#############################################################################
# 
# 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 management support for DOM classes.
"""

import Globals
import Acquisition
from Acquisition import aq_parent
import App.Management
from OFS.Traversable import Traversable
from DateTime import DateTime # for manage_edit cookie expire
import marshal # for ftp

import DOMProxy
import DOM
import ExtraDOM

import string
from xml.parsers import expat

import xml.dom
import types

from StrIO import StringIO

from NodePath import registry

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


# 
# Management mixin classes
#
   
class DOMTraversable(Traversable):
    "Mixin class for DOM classes to provide Zope traversability."

    getPhysicalPath__roles__ = None # Public
    def getPhysicalPath(self):
        """Returns a path that can be used to access this object again
        later."""
        # first get path to persistent document
        doc = self._persistentDoc._earlyAqChain()
        path = doc.getPhysicalPath()
        # then get path to node and attach it to that path steps
        nodepath = registry.create_path(doc, self, 'child')
        if nodepath:
            return path + (nodepath,)
        else:
            return path

    def getNodePath(self, scheme_name):
        """Create a node path for this node.
        FIXME: this has the same name but other signature as the
        one in ParsedXML for document.
        """
        return registry.create_path(self._persistentDoc._earlyAqChain(),
                                    self, scheme_name)
    
    # sequence interface, for backwards compatibility.
    # normally the document's __getitem__ takes care of this
    def __getitem__(self, key):
        return self.childNodes[int(key)].__of__(self)

    # other helpers
    def _earlyAqChain(self):
        """Return the earliest ref to self in self's aq_chain.  Helper function
        for getPhysicalPath."""
        chain = self.aq_chain
        chain.reverse()
        for parent in chain:
            if parent == self:
                return parent
        return self
    
class DOMPublishable(DOMTraversable):
    "Mixin class for DOM classes to provide Zope publishability."
    # tree tag methods
    
    def tpURL(self):
        """
        Return a string used for an URL relative to parent.  Used by
        the dtml-tree tag.
        """
        i = 0
        node = self.getDOMObj().previousSibling
        while node:
            i = i + 1
            node = node.previousSibling
        return str(i)

    def tpValues(self):
        "Return a list of immediate subobjects.  Used by the dtml-tree tag."
        retList = [] # dtml-tree wants to own this
        for i in self.childNodes:
            retList.append(i)
        return retList

    def tpId(self):
        "Return a value to be used as an id in tree state."
        return self.tpURL()

    # partial ObjectManagerItem interface

    def getId(self):
        "Return the id of the object as a string."
        if hasattr(self.aq_base):
            base = self.aq_base
        else:
            base = self
        name=getattr(base, 'id', None)
        if name is not None:
            return name
        return self.tpURL()

    # I don't want to add PrincipiaSearchSource, because it'd be expensive
    # to trigger printing every node in a document.  Look into this when
    # we have a better caching plan.        

    # Partial ObjectManager interface.  Nodes are *not* ObjectManagers,
    # these are for other Zope tools to be useful.

    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'.
        """
        if spec is not None:
            if isinstance(spec, type('s')):
                spec=[spec]
            set=[]
            for ob in self.childNodes:
                if ob['meta_type'] in spec:
                    set.append(ob)
            return set
        return list(self.childNodes)
    
    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 map(lambda i: i.getId(), self.objectValues(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'
        """
        r=[]
        a=r.append
        for ob in self.objectValues(spec): a((ob.getId(), ob))
        return r

    # FTP interface
    
    def manage_FTPget(self):
        """Returns the source content of an object. For example, the
        source text of a Document, or the data of a file."""
        return self.__str__()
        
    #def manage_FTPstat(self,REQUEST):
    #    """Returns a stat-like tuple. (marshalled to a string) Used by
    #    FTP for directory listings, and MDTM and SIZE"""
    #    # mode = 0100000 | 0004 | 0002 XXX open
    #    from AccessControl.User import nobody
    #    mode=0100000
    #    
    #    # read permissions
    #    if (hasattr(self.aq_base,'manage_FTPget') and 
    #        hasattr(self.manage_FTPget, '__roles__')):
    #        try:
    #            if getSecurityManager().validateValue(self.manage_FTPget):
    #                mode=mode | 0440
    #        except: pass
    #        if nobody.allowed(self.manage_FTPget,
    #                          self.manage_FTPget.__roles__):
    #            mode=mode | 0004
    #            
    #    # write permissions
    #    if hasattr(self.aq_base,'PUT') and hasattr(self.PUT, '__roles__'):
    #        try:
    #            if getSecurityManager().validateValue(self.PUT):
    #                mode=mode | 0220
    #        except: pass
    #        
    #        if nobody.allowed(self.PUT, self.PUT.__roles__):
    #            mode=mode | 0002
    #    
    #    size = len(self.manage_FTPget())
    #    # modification time
    #    mtime = self.bobobase_modification_time().timeTime()
    #    # owner and group
    #    owner = group = 'Zope'
    #    for user, roles in self.get_local_roles():
    #        if 'Owner' in roles:
    #            owner=user
    #            break
    #    return marshal.dumps((mode,0,0,1,owner,group,size,mtime,mtime,mtime))
    #
    #def manage_FTPlist(self,REQUEST):
    #    """Directory listing for FTP. In the case of non-Foldoid objects,
    #    the listing should contain one object, the object itself."""
    #    # check to see if we are being acquiring or not
    #    ob=self
    #    while 1:
    #        if App.Common.is_acquired(ob):
    #            raise ValueError('FTP List not supported on acquired objects')
    #        if not hasattr(ob,'aq_parent'):
    #            break
    #        ob=ob.aq_parent
    #        
    #    stat=marshal.loads(self.manage_FTPstat(REQUEST))
    #    id = self.getId()
    #    return marshal.dumps((id,stat))
        

class DOMIO:
    "Mixin class for DOM classes to provide parsing, writing."

    def writeStream(self, stream = None, encoding = None, html = 0,
                    contentType = None, prettyPrint=0):
        "Write the XML representation of this object to stream."
        # work thru DOM object to avoid making proxy nodes
        return ExtraDOM.writeStream(self.getDOMObj(), stream, encoding,
                                    html, contentType, prettyPrint=prettyPrint)
    
    def __str__(self):
        "Return the XML representation of this object."
        return self.writeStream().getvalue()

    # This is a workaround for newer browser behavior, and will probably
    # break older browsers.  Recent browsers put '<' in the textarea when the
    # source is '&lt;", and when the form is sent they send '<', and so on.
    # This quotes the relevent refs; when the textarea is sent back the
    # browser unquotes them, so the end result is WYSIWYG.  Yuck!
    # If a browser doesn't do this __str__ should be used.
    def textareaStr(self, prettyPrint=0):
        """Return the XML representation of this object in a format safe for
        a textarea, with certain entity references quoted."""
        # FIXME
        # perhaps we should output text in the encoding the document
        # was saved in, but this is too complicated for now, so we just
        # output UTF-8. Unfortunately this is not very pretty in the browser
        # unless you set it to display in UTF-8.
        outStr = self.writeStream(
            encoding=None, prettyPrint=prettyPrint).getvalue().encode('UTF-8')
        outStr = string.replace(outStr, '&amp;', '&amp;amp;')        
        outStr = string.replace(outStr, '&lt;', '&amp;lt;')
        outStr = string.replace(outStr, '&gt;', '&amp;gt;')
        outStr = string.replace(outStr, '<', '&lt;')
        outStr = string.replace(outStr, '>', '&gt;') 
        return outStr

    # kinda silly that index_html can be xml, eh?
    # I need to figure out what the convention is for multimode docs.
    def index_html(self, REQUEST = None, RESPONSE = None):
        "Returns publishable source according to content type"
        if self._persistentDoc:
            contentType = self._persistentDoc.contentType
        else:
            contentType = 'text/xml'
        if RESPONSE:
            RESPONSE.setHeader('Content-type', contentType)
        type = string.split(contentType, '/')[1]
        # the printer can do html mode xml, but we set according to type
        isHtml = (type == "html")
        return self.writeStream(None, None, isHtml, type).getvalue()

    # Parsing a node removes self & subtree from document; users of other
    # refs to subnodes have to take care to notice this.
    def parseXML(self, file):
        """Parse file as XML, replace myself with the resulting tree,
        return node replacing self."""
        errorStr = ("Parsing at the document node must be done on the "
                    "persistent proxy node, which can't be found for some "
                    "reason.\n"
                    "Traverse to the persistent node and try there.")
        doc = self._persistentDoc
        namespaces = not (doc and doc.noNamespaces) # default true
        node = ExtraDOM.parseFile(self.getDOMObj(), file, namespaces)
        if self.nodeType == xml.dom.Node.DOCUMENT_NODE:
            raise RuntimeError, errorStr
            # This probably isn't worth it.  If we had an easy way to get to
            # a securityfriendly persistent object... (see getPersistentDoc)
            # This is overridden by ParsedXML.ParsedXML if we're persistent
            #doc_self = getattr(doc, "aq_self", doc)
            #self_self = getattr(self, "aq_self", self)
            #if doc and self_self is doc_self:
            #    # we're a nonpersistent proxy node; the node needs parents
            #    # to attach to if we're not persistent.
            #    # we should probably KISS & bail here...
            #    pdoc = self.getPersistentDoc()
            #    if pdoc: # need to publish from this object, need security
            #        return pdoc.parseXML(file)
            #    raise RuntimeError, errorStr                
            ## ExtraDOM can't insert node wihtout parents; we have to do it.
            #ManageableDocument.__init__(self, node, self._persistentDoc)
            ## we may still be a nonpersistent proxy node
            #self._p_changed = 1
            #return self # proxy still part of tree, DOM node isn't
        else:
            return doc.wrapDOMObj(node) # proxy and node not part of tree


class DOMManageable(DOMIO, DOMPublishable, App.Management.Tabs):
    "Mixin class for DOM classes to provide Zope management interfaces."

    manage_editForm = Globals.HTMLFile('dtml/transEdit',globals())
    #pretty_html = Globals.HTMLFile('dtml/pretty',globals())
    manage_DOMTree = Globals.HTMLFile('dtml/DOMTree',globals())
    try: 
        manage_DOMTree._setName('manage_DOMTree')
    except AttributeError:
        pass # _setName only exists in Zope 2.4+
    manage_main = manage_DOMTree
 
    manage_options = ({'label':'DOM', 'action':'manage_DOMTree',
                       'help': ('ParsedXML', 'ParsedXML_DOM.stx')},
                      {'label':'Edit', 'action':'manage_editForm',
                       'help': ('ParsedXML', 'ParsedXML_Edit.stx')},
                      {'label':'Raw', 'action':'index_html'})

    def makeErrorOutput(self, data, offset, lineno):
        """return a HTML-renderable output of data with a text pointer
        to the position at offset, lineno"""
        # make pointer
        pointerLine = ('<font color="red">' +
                       '-' * (offset - 1) + '^' + '\n\n' +
                       '</font>')
        # add rest of data
        # the parser saw a normalized version; normalize data
        data = string.replace(data, "\r\n", "\n")
        data = string.replace(data, "\r", "\n")
        # make reconstruced data rep with break at error
        dataA = ''
        dataB = ''
        # add earlier data
        for line in range(lineno - 1):
            dataA = dataA + data[0:string.index(data, '\n') + 1]
            data = data[string.index(data, '\n') + 1:]
        dataA = dataA + data[0:offset] + '\n\n'
        # add later data
        dataB = dataB + ' ' * (offset)
        dataB = dataB + data[offset:] + '\n'
        # strip brackets, format
        dataA = string.replace(dataA, "<", "&lt;")
        dataA = string.replace(dataA, ">", "&gt;")
        dataB = string.replace(dataB, "<", "&lt;")
        dataB = string.replace(dataB, ">", "&gt;")
        dataOut = ('<div align=left><br><br><pre>' +
                   dataA + pointerLine + dataB +
                   '</pre><br><br></div>')
        return dataOut
    
    # dict to help notice, handle size change request in manage_editForm
    _size_changes={
        'Bigger': (5,5),
        'Smaller': (-5,-5),
        'Narrower': (0,-5),
        'Wider': (0,5),
        'Taller': (5,0),
        'Shorter': (-5,0),
        }

    # helper function to set size cookies and return edit form
    def _er(self, data, title, contentType,
            SUBMIT, dtpref_cols, dtpref_rows, REQUEST):
        dr,dc = self._size_changes[SUBMIT]
        rows=max(1,string.atoi(dtpref_rows)+dr)
        cols=max(40,string.atoi(dtpref_cols)+dc)
        e=(DateTime('GMT') + 365).rfc822()
        resp=REQUEST['RESPONSE']
        resp.setCookie('dtpref_rows',str(rows),path='/',expires=e)
        resp.setCookie('dtpref_cols',str(cols),path='/',expires=e)
        return self.manage_editForm(self, REQUEST,
                                    dtpref_cols = cols, dtpref_rows = rows)

    def manage_edit(self, data, title = '', contentType = None,
                    useNamespaces = 1,
                    SUBMIT = 'Change',
                    dtpref_cols = '50', dtpref_rows = '20', REQUEST = None):
        """
        If SUBMIT is a size pref variable, handle a textarea size change.
        Otherwise parse the given text and handle the result.
        """
        # just get back to the dtml if we're changing size
        if self._size_changes.has_key(SUBMIT):
            return self._er(data, title, contentType,
                            SUBMIT, dtpref_cols, dtpref_rows, REQUEST)

        # if we are pretty printing, just redraw the form
        if SUBMIT == 'PrettyPrint':
            return self.manage_editForm(
                self, REQUEST,
                textareaStr=self.textareaStr(prettyPrint=1))
                                        
        if (  hasattr(self, 'nodeType')
              and self.nodeType == xml.dom.Node.DOCUMENT_NODE):
            #if we're the main doc
            self.title = str(title)
            if contentType:
                self.contentType = str(contentType)
            self.noNamespaces = not useNamespaces
                    
        text=StringIO(data)
        try:
            newNode = self.parseXML(text) # self not in doc if this succeeds
        except expat.error, e:
            get_transaction().abort()
            if REQUEST:
                dataOut = self.makeErrorOutput(data, e.offset, e.lineno)
                err = "%s%s%s" % (parserr, '<font color="red">%s</font>'
                                % getattr(e, 'args', ''), dataOut)
                return Globals.MessageDialog(
                    title = 'XML Parsing Error',
                    message = err,
                    action = 'manage_editForm')
            raise
        if REQUEST:
            message = "Saved changes."
            # wish I knew why we have to set textareaStr for the form
            return newNode.manage_editForm(self, REQUEST,
                                           textareaStr=newNode.textareaStr(),
                                           management_view="Edit",
                                           manage_tabs_message=message)

    def manage_upload(self, file, REQUEST=None):
        "Parse the given file and handle the result."
        try:
            newNode = self.parseXML(file)
        except expat.error, e:
            get_transaction().abort()
            if REQUEST:
                file.seek(0)
                dataOut = self.makeErrorOutput(file.read(),
                                               e.offset, e.lineno)
                err = "%s%s%s" % (parserr, '<font color="red">%s</font>'
                                % getattr(e, 'args', ''), dataOut)
                return Globals.MessageDialog(
                    title  = 'XML Parsing Error',
                    message = err,
                    action = 'manage_main')
            raise
        if REQUEST:
            return newNode.manage_main(self, REQUEST,
                                       manage_tabs_message='Saved changes.')

Globals.default__class_init__(DOMManageable) # activate perms

#
# And finally, classes to mix management and DOM proxies.
#

class ManageableWrapper:
    """
    Mixin class to go alongside ManageableNode classes.
    Provides the wrapDOMObj function to create ManageableNode classes.
    """

    # anything that returns subobjs must grant access to them
    __allow_access_to_unprotected_subobjects__ = 1

    def wrapNamedNodeMap(self, obj):
        if obj is None:
            return None
        parent = aq_parent(self) or self
        return ManageableNamedNodeMap(obj, self._persistentDoc).__of__(parent)

    def wrapNodeList(self, obj):
        parent = aq_parent(self) or self
        return ManageableNodeList(obj, self._persistentDoc).__of__(parent)
    
    def wrapDOMObj(self, node):
        """Return the appropriate manageable class wrapped around the node."""
        if node is None:
            return
        wrapper_type = WRAPPER_TYPES[node._get_nodeType()]
        parent = aq_parent(self) or self
        return wrapper_type(node, self._persistentDoc).__of__(parent)
        
# According to DOM Erratum Core-14, the empty string should be
# accepted as equivalent to null for hasFeature().

_MANAGEABLE_DOM_FEATURES = (
    ("org.zope.dom.persistence", None),
    ("org.zope.dom.persistence", ""),
    ("org.zope.dom.persistence", "1.0"),
    ("org.zope.dom.acquisition", None),
    ("org.zope.dom.acquisition", ""),
    ("org.zope.dom.acquisition", "1.0"),
    )

_MANAGEABLE_DOM_NON_FEATURES = (
    ("load", None),
    ("load", ""),
    ("load", "3.0"),
    )

class ManageableDOMImplementation(DOMProxy.DOMImplementationProxy):
    """A proxy of a DOMImplementation node that defines createDocument
    to return a ManageableDocument.
    """
    
    def hasFeature(self, feature, version):
        feature = string.lower(feature)
        if (feature, version) in _MANAGEABLE_DOM_FEATURES:
            return 1
        if (feature, version) in _MANAGEABLE_DOM_NON_FEATURES:
            return 0
        return DOMProxy.DOMImplementationProxy.hasFeature(self, feature,
                                                          version)

    def createDocumentType(self, qualifiedName, publicId, systemId):
        DOMDocumentType = self._createDOMDocumentType(qualifiedName,
                                                      publicId, systemId)
        return ManageableDocumentType(DOMDocumentType)
    
    def createDocument(self, namespaceURI, qualifiedName, docType=None):
        if docType is not None:
            if docType.ownerDocument is not None:
                raise xml.dom.WrongDocumentErr
            mdocType = docType.getDOMObj()
        else:
            mdocType = None
        DOMDocument = self._createDOMDocument(namespaceURI, qualifiedName,
                                              mdocType)
        return ManageableDocument(DOMDocument, DOMDocument)

theDOMImplementation = ManageableDOMImplementation()

# XXX We're implicitly acquiring so we can get ZopeTime (and probably a
# jillion other things) in our DTML methods.  We should be explicit.
class ManageableNode(ManageableWrapper, DOMProxy.NodeProxy, DOMManageable,
                     Acquisition.Implicit):
    "A wrapper around a DOM Node."

    # this is mainly here to make later inheritance safer
    def __init__(self, node, persistentDocument):
        # inherit from DOMProxy.NodeProxy
        ManageableNode.inheritedAttribute('__init__')(self, node,
                                                      persistentDocument)

class ManageableNodeList(ManageableWrapper, DOMProxy.NodeListProxy,
                         Acquisition.Implicit):
    "A wrapper around a DOM NodeList."
    meta_type = "Manageable NodeList"

    # redefine to get back the [] syntax with acquisition, eh?
    def __getslice__(self, i, j):
        return self.wrapNodeList(self._node.__getslice__(i,j))

    # redefine to get back the [] syntax with acquisition, eh?
    def __getitem__(self, i):
        return self.wrapDOMObj(self._node.__getitem__(i))        

class ManageableNamedNodeMap(ManageableWrapper, DOMProxy.NamedNodeMapProxy,
                             Acquisition.Implicit):
    "A wrapper around a DOM NamedNodeMap."
    meta_type = "Manageable NamedNodeMap"

    # redefine to get back the [] syntax with acquisition, eh?
    def __getitem__(self, i):
        return self.wrapDOMObj(self._node.__getitem__(i))        
    
class ManageableDocumentFragment(ManageableWrapper,
                                 DOMProxy.DocumentFragmentProxy,
                                 ManageableNode):
    "A wrapper around a DOM DocumentFragment."
    meta_type = "Manageable Document Fragment"
    
class ManageableElement(ManageableWrapper, DOMProxy.ElementProxy,
                        ManageableNode):
    "A wrapper around a DOM Element."
    meta_type = "Manageable Element"

class ManageableCharacterData(ManageableWrapper,
                              DOMProxy.CharacterDataProxy, ManageableNode):
    "A wrapper around a DOM CharacterData."
    meta_type = "Manageable Character Data"

class ManageableCDATASection(ManageableWrapper,
                             DOMProxy.CDATASectionProxy, ManageableNode):
    "A wrapper around a DOM CDATASection."
    meta_type = "Manageable CDATASection"

class ManageableText(ManageableWrapper,
                     DOMProxy.TextProxy, ManageableCharacterData):
    "A wrapper around a DOM Text."
    meta_type = "Manageable Text"

class ManageableComment(ManageableWrapper,
                        DOMProxy.CommentProxy, ManageableCharacterData):
    "A wrapper around a DOM Comment."
    meta_type = "Manageable Comment"    

class ManageableProcessingInstruction(ManageableWrapper,
                                      DOMProxy.ProcessingInstructionProxy,
                                      ManageableNode):
    "A wrapper around a DOM ProcessingInstruction."
    meta_type = "Manageable Processing Instruction"    

class ManageableAttr(ManageableWrapper, DOMProxy.AttrProxy, ManageableNode):
    "A wrapper around a DOM Attr."
    meta_type = "Manageable Attr"    

#ManageableDocument is not necessarily a persistent object, even when a
#persistent subclass such as ParsedXML has been instantiated.  Traversing
#up to the document can create a new transient proxy.  Persistent attributes
#must be set on the persistent version.
class ManageableDocument(ManageableWrapper, DOMProxy.DocumentProxy,
                         ManageableNode):
    "A wrapper around a DOM Document."
    meta_type = "Manageable Document"    

    implementation = theDOMImplementation

    def __init__(self, node, persistentDocument):
        ManageableNode.__init__(self, node, persistentDocument)

    def _get_implementation(self):
        return self.implementation
    
    #block set of implementation, since we don't proxy it the same
    def __setattr__(self, name, value):
        if name == "implementation":
            raise xml.dom.NoModificationAllowedErr()
        ManageableDocument.inheritedAttribute('__setattr__')(self, name, value)

# DOM extended interfaces

class ManageableEntityReference(ManageableWrapper,
                                DOMProxy.EntityReferenceProxy,
                                ManageableNode):
    "A wrapper around a DOM EntityReference."
    meta_type = "Manageable Entity Reference"

class ManageableEntity(ManageableWrapper, DOMProxy.EntityProxy,
                       ManageableNode):
    "A wrapper around a DOM Entity."
    meta_type = "Manageable Entity"

class ManageableNotation(ManageableWrapper, DOMProxy.NotationProxy,
                         ManageableNode):
    "A wrapper around a DOM Notation."
    meta_type = "Manageable Notation"

class ManageableDocumentType(ManageableWrapper, DOMProxy.DocumentTypeProxy,
                             ManageableNode):
    "A wrapper around a DOM DocumentType."
    meta_type = "Manageable Document Type"    


Node = xml.dom.Node
WRAPPER_TYPES = {
    Node.ELEMENT_NODE: ManageableElement,
    Node.ATTRIBUTE_NODE: ManageableAttr,
    Node.TEXT_NODE: ManageableText,
    Node.CDATA_SECTION_NODE: ManageableCDATASection,
    Node.ENTITY_REFERENCE_NODE: ManageableEntityReference,
    Node.ENTITY_NODE: ManageableEntity,
    Node.PROCESSING_INSTRUCTION_NODE: ManageableProcessingInstruction,
    Node.COMMENT_NODE: ManageableComment,
    Node.DOCUMENT_NODE: ManageableDocument,
    Node.DOCUMENT_TYPE_NODE: ManageableDocumentType,
    Node.DOCUMENT_FRAGMENT_NODE: ManageableDocumentFragment,
    Node.NOTATION_NODE: ManageableNotation,
    }
del Node
