########################################################################
#
# File Name:            pDomlette.py
#
# Documentation:        http://docs.4suite.org/4XSLT/pDomlette.py.html
#
"""
A lightweight, auo-normalized DOM optimized for XPath.
WWW: http://4suite.org        e-mail: support@4suite.org

Copyright (c) 2000-2001, 2001 Fourthought Inc, USA.   All Rights Reserved.
See  http://4suite.org/COPYRIGHT  for license and copyright information
"""

import sys, string, os, urllib
import UserDict, cStringIO, cPickle
from xml.dom.ext import SplitQName
from Ft.Lib import Uri, PdomletteException, PDOMLETTE
from xml.dom import XML_NAMESPACE, XMLNS_NAMESPACE


#DOM_IMP = Ft.Lib.PDOMLETTE

class DOMImplementation:

    _features = {'CORE' : 2.0,
                 'XML' : 2.0,
                 }

    def hasFeature(self, feature, version=''):
        fv = self._features.get(string.upper(feature))
        if fv:
            if version:
                try:
                    return float(version) == fv
                except:
                    return 0
            return 1
        return 0

    def createDocument(self, namespaceURI, qualifiedName, doctype):
        doc = Document(doctype)
        if qualifiedName:
            el = doc.createElementNS(namespaceURI, qualifiedName)
            doc.appendChild(el)
        return doc
    
    def createDocumentType(self, qualifiedName, publicId, systemId):
        return DocumentType(qualifiedName,
                            publicId,
                            systemId)


implementation = DOMImplementation()

class Node:
    # Node types
    ELEMENT_NODE                = 1
    ATTRIBUTE_NODE              = 2
    TEXT_NODE                   = 3
    CDATA_SECTION_NODE          = 4
    ENTITY_REFERENCE_NODE       = 5
    ENTITY_NODE                 = 6
    PROCESSING_INSTRUCTION_NODE = 7
    COMMENT_NODE                = 8
    DOCUMENT_NODE               = 9
    DOCUMENT_TYPE_NODE          = 10
    DOCUMENT_FRAGMENT_NODE      = 11
    NOTATION_NODE               = 12

    def __init__(self, ownerDocument, nodeType, namespaceURI=None, localName=None, prefix=None):
        self.__dict__['childNodes'] = []
        self.__dict__['nodeName'] = ''
        self.__dict__['nodeValue'] = None
        self.__dict__['parentNode'] = None
        self.__dict__['firstChild'] = None
        self.__dict__['lastChild'] = None
        self.__dict__['previousSibling'] = None
        self.__dict__['nextSibling'] = None
        self.__dict__['attributes'] = None
        self.__dict__['ownerDocument'] = ownerDocument
        self.__dict__['namespaceURI'] = namespaceURI
        self.__dict__['prefix'] = prefix
        self.__dict__['localName'] = localName
        self.__dict__['readOnly'] = 0
        self.__dict__['nodeType'] = nodeType
        return

    def hasChildNodes(self):
        return self.childNodes and 1 or 0

    def normalize(self):
        # This one needs to join all adjacent text nodes
        node = self.firstChild
        while node:
            if node.nodeType == Node.TEXT_NODE:
                next = node.nextSibling
                while next and next.nodeType == Node.TEXT_NODE:
                    node.data = node.data + next.data
                    node.parentNode.removeChild(next)
                    next = node.nextSibling
                if not node.data:
                    # Remove any empty text nodes
                    node.parentNode.removeChild(node)
            elif node.nodeType == Node.ELEMENT_NODE:
                node.normalize()
            node = node.nextSibling
        return

    def appendChild(self, newChild):
        if newChild.nodeType == Node.DOCUMENT_FRAGMENT_NODE:
            # Only for non-empty document fragments
            for node in newChild.childNodes:
                node.parentNode = self
            if self.lastChild:
                # Graft the docfrag's childNodes to the end of ours
                self.lastChild.nextSibling = newChild.firstChild
                self.lastChild = newChild.lastChild
                newChild.firstChild.previousSibling = self.lastChild
                self.childNodes.extend(newChild.childNodes)
            else:
                # We are empty, just take the whole thing
                self.childNodes = newChild.childNodes
                self.firstChild = newChild.firstChild
                self.lastChild = newChild.lastChild

            # Empty the document fragment
            newChild.childNodes = []
            newChild.firstChild = None
            newChild.lastChild = None
        else:
            if newChild.parentNode:
                newChild.parentNode.removeChild(newChild)
            newChild.parentNode = self
            newChild.ownerDocument = self.ownerDocument or self
            if not self.firstChild:
                self.firstChild = newChild
            else:
                self.lastChild.nextSibling = newChild
                newChild.previousSibling = self.lastChild
            self.lastChild = newChild
            self.childNodes.append(newChild)
        return newChild

    def removeChild(self, childNode):
        self.childNodes.remove(childNode)

        if childNode.nextSibling:
            childNode.nextSibling.previousSibling = childNode.previousSibling
        else:
            self.lastChild = childNode.previousSibling

        if childNode.previousSibling:
            childNode.previousSibling.nextSibling = childNode.nextSibling
        else:
            self.firstChild = childNode.nextSibling

        childNode.parentNode = None
        childNode.previousSibling = None
        childNode.nextSibling = None
        return childNode

    def replaceChild(self, newChild, oldChild):
        sibling = oldChild.nextSibling
        self.removeChild(oldChild)
        if sibling:
            self.insertBefore(newChild, sibling)
        else:
            self.appendChild(newChild)
        return oldChild

    def insertBefore(self, newChild, refChild):
        if refChild is None:
            return self.appendChild(newChild)

        index = self.childNodes.index(refChild)
        if newChild.nodeType == Node.DOCUMENT_FRAGMENT_NODE:
            # Only for non-empty document fragments
            for node in newChild.childNodes:
                node.parentNode = self
            if index:
                # It's in the middle
                refChild.previousSibling.nextSibling = newChild.firstChild
                newChild.firstChild.previousSibling = refChild.previousSibling
                refChild.previousSibling = newChild.lastChild
                newChild.lastChild.nextSibling = refChild
                children = self.childNodes[:index]
                children.extend(newChild.childNodes)
                children.extend(self.childNodes[index:])
                self.childNodes = children
            else:
                # At the beginning
                newChild.childNodes.extend(self.childNodes)
                newChild.lastChild.nextSibling = self.firstChild
                self.firstChild.previousSibling = newChild.lastChild
                self.firstChild = newChild.firstChild
                self.childNodes = newChild.childNodes
            # Empty out the document fragment
            newChild.childNodes = []
            newChild.firstChild = None
            newChild.lastChild = None
        else:
            self.childNodes.insert(index, newChild)
            if newChild.parentNode:
                newChild.parentNode.removeChild(newChild)
            newChild.parentNode = self
            if refChild.previousSibling:
                refChild.previousSibling.nextSibling = newChild
            else:
                self.firstChild = newChild

            newChild.previousSibling = refChild.previousSibling
            refChild.previousSibling = newChild
            newChild.nextSibling = refChild
        return newChild

    def isSameNode(self,other):
        return self == other


class Document(Node):
    def __init__(self, doctype=None):
        Node.__init__(self, None, Node.DOCUMENT_NODE)
        self.documentElement = None
        self.doctype = doctype
        self.nodeName = '#document'
        if doctype:
            doctype.ownerDocument = self

        self.implementation = implementation

        #import traceback, sys
        #traceback.print_stack()
        #sys.stderr.write("Created Document %x\n" % id(self))

    def createElementNS(self, namespaceURI, qualifiedName):
        prefix, localName = SplitQName(qualifiedName)
        return Element(self, namespaceURI, localName, prefix)

    def createElement(self, name):
        return Element(self, None, name, None)

    def createAttributeNS(self, namespaceURI, qualifiedName):
        prefix, localName = SplitQName(qualifiedName)
        if localName == 'xmlns':
            namespaceURI = XMLNS_NAMESPACE
        return Attribute(self, namespaceURI, localName, prefix)

    def createAttribute(self, name):
        return Attribute(self, None, name, None)

    def createTextNode(self, data):
        return Text(self,data)

    def createProcessingInstruction(self, target, data):
        pi = ProcessingInstruction(self)
        pi.target = target
        pi.data = data
        return pi

    def createComment(self, data):
        c = Comment(self)
        c.data = data
        return c

    def createDocumentFragment(self):
        df = DocumentFragment(self)
        return df

    def createNodeIterator(self, root, whatToShow, filter, entityReferenceExpansion):
        from xml.dom import NodeIterator
        nodi = NodeIterator.NodeIterator(root, whatToShow, filter, entityReferenceExpansion)
        return nodi

    def appendChild(self, newChild):
        if not self.documentElement and newChild.nodeType == Node.ELEMENT_NODE:
            self.documentElement = newChild
        Node.appendChild(self, newChild)
        if not self.documentElement and newChild.nodeType == Node.DOCUMENT_FRAGMENT_NODE:
            #See if one was added
            for c in self.childNodes:
                if c.nodeType == Node.ELEMENT_NODE:
                    self.documentElement = c
                    break

    def cloneNode(self, deep):
        raise NotImplementedError('cloning of Document not supported')

    def isHtml(self):
        return 0

    def isXml(self):
        return 1

    #Range support
    def createRange(self):
        from xml.dom import Range
        return Range.Range(self)

    def __repr__(self):
        return '<Domlette Document Node at %x>' % id(self)

    def __getinitargs__(self):
        raise PdomletteException(PdomletteException.PICKLE_DOCUMENT)


class DocumentType(Node):
    def __init__(self, name, publicId, systemId):
        Node.__init__(self, None, DOCUMENT_TYPE_NODE)
        self.notations = NamedNodeMap(None)
        self.entities = NamedNodeMap(None)
        self.publicId = publicId
        self.systemId = systemId
        self.internalSubset = ''
        self.nodeName = name
        

class Element(Node):
    def __init__(self, ownerDocument, namespaceURI='', localName='',
                 prefix=''):
        Node.__init__(self, ownerDocument, Node.ELEMENT_NODE, namespaceURI,
                      localName, prefix)
        self.attributes = NamedNodeMap(ownerDocument)
        self.nodeName = prefix and prefix + ':' + localName or localName
        self.tagName = self.nodeName

    def getAttributeNodeNS(self, namespaceURI, localName):
        return self.attributes.get((namespaceURI, localName), None)

    def getAttributeNode(self, name):
        return self.attributes.get((None, name), None)

    def getAttributeNS(self, namespaceURI, localName):
        attr = self.getAttributeNodeNS(namespaceURI, localName)
        return attr and attr.value or ''

    def getAttribute(self, name):
        attr = self.getAttributeNode(name)
        return attr and attr.value or ''

    def setAttributeNS(self, namespaceURI, qualifiedName, value):
        attr = self.ownerDocument.createAttributeNS(namespaceURI,
                                                    qualifiedName)
        attr.value = value
        return self.setAttributeNodeNS(attr)

    def setAttributeNodeNS(self, attr):
        attr.ownerElement = self
        # Namespace nodes are indexed by (ns, prefix)
        if attr.name == 'xmlns':
            # default namespace
            self.attributes[(XMLNS_NAMESPACE, '')] = attr
        elif attr.prefix == 'xmlns':
            self.attributes[(XMLNS_NAMESPACE, attr.localName)] = attr
        else:
            self.attributes[(attr.namespaceURI, attr.localName)] = attr
        #self.attributes.length = self.attributes.length + 1
        return

    def removeAttributeNode(self,attr):
        for n,a in self.attributes.items():
            if a == attr:
                del self.attributes[n]
                return

    def hasAttributeNS(self, namespaceURI, localName):
        attr = self.attributes.get((namespaceURI, localName), None)
        return attr and 1 or 0

    def hasAttribute(self, name):
        attr = self.attributes.get((None, name), None)
        return attr and 1 or 0


    def cloneNode(self, deep):
        element = self.__class__(self.ownerDocument,
                                 self.namespaceURI,
                                 self.localName,
                                 self.prefix)

        for attr in self.attributes.values():
            clone = attr.__class__(attr.ownerDocument,
                                   attr.namespaceURI,
                                   attr.localName,
                                   attr.prefix)
            clone.value = attr.value
            element.setAttributeNodeNS(clone)

        if deep:
            for child in self.childNodes:
                clone = child.cloneNode(deep)
                element.appendChild(clone)
            
        return element
    
    def __repr__(self):
        return "<Domlette Element Node at %x: name='%s' with %d attributes and %d children>" % (
            id(self),
            self.nodeName,
            len(self.attributes),
            len(self.childNodes)
            )

    def __getinitargs__(self):
        return (None, self.namespaceURI, self.localName, self.prefix)

    def __getstate__(self):
        attrs = []
        for attr in self.attributes.values():
            attrs.append((attr.namespaceURI, attr.localName, attr.prefix,
                          attr.value))
        return attrs

    def __setstate__(self, attrs):
        for data in attrs:
            attr = Attribute(self.ownerDocument, data[0], data[1], data[2])
            attr.value = data[3]
            if data[1] == 'xmlns':
                self.attributes[(data[0], data[2])] = attr
            else:
                self.attributes[(data[0], data[1])] = attr
        return


class Attribute(Node):
    value = ''
    specified = 0
    def __init__(self, ownerDocument, namespaceURI='', localName='', prefix=''):
        if localName == 'xmlns' and prefix:
            # SplitQName returns 'xmlns' always as localName
            localName = prefix
            prefix = 'xmlns'
        Node.__init__(self, ownerDocument, Node.ATTRIBUTE_NODE, namespaceURI, localName, prefix)

        self.__dict__['ownerElement'] = None
        self.appendChild(Text(self.ownerDocument))

        if prefix:
            self.__dict__['name'] = self.__dict__['nodeName'] = prefix + ':' + localName
        else:
            self.__dict__['name'] = self.__dict__['nodeName'] = localName
        return

    def __setattr__(self,name,value):
        dict = self.__dict__
        if name == 'value':
            dict['firstChild'].data = value
            dict['nodeValue'] = value
        dict[name] = value

    def __repr__(self):
         return "<Domlette Attribute Node at %x: name='%s', value='%s'>" % (
             id(self),
             self.name,
             self.value
             )

    def __getinitargs__(self):
        return (None, self.namespaceURI, self.localName, self.prefix)

    def __getstate__(self):
        return {'value':self.value}


class DocumentFragment(Node):
    def __init__(self, ownerDocument):
        Node.__init__(self, ownerDocument, Node.DOCUMENT_FRAGMENT_NODE)
        self.nodeName = '#document-fragment'
        return

    def __repr__(self):
        return '<Domlette DocumentFragment Node at %x with %d children>' % (
                id(self),
                len(self.childNodes)
                )

    def __getinitargs__(self):
        raise PdomletteException(PdomletteException.PICKLE_DOCUMENT_FRAGMENT)


class Text(Node):
    def __init__(self, ownerDocument,data=''):
        Node.__init__(self, ownerDocument, Node.TEXT_NODE)
        #Avoid set attr
        self.__dict__['data'] = data
        self.__dict__['nodeValue'] = data
        self.__dict__['nodeName'] = '#text'

    def insertData(self, offset, data):
        self.data = self.data[:int(offset)] + data + self.data[int(offset):]

    def appendData(self, data):
        self.data = self.data + data

    def deleteData(self,offset,count):
        self.data = self.data[:int(offset)] + self.data[int(offset+count):]

    def appendData(self,data):
        self.data = self.data = self.data + data

    def substringData(self, offset, count):
        return self.data[int(offset):int(offset+count)]

    def cloneNode(self,deep):
        text = self.__class__(self.ownerDocument)
        text.data = self.data
        return text

    def __setattr__(self, name, value):
        if name == 'data':
            self.__dict__['nodeValue'] = value
        elif name == 'nodeValue':
            self.__dict__['data'] = value
        self.__dict__[name] = value


    def __repr__(self):
        # Trim to a managable size
        if len(self.data) > 20:
            data = self.data[:20] + '...'
        else:
            data = self.data

        # Escape unprintable chars
        import string
        for ws in ['\t','\n','\r']:
            data = string.replace(data, ws, '\\0x%x' % ord(ws))

        st = "<Domlette Text Node at %x: data='%s'>" % (
                id(self),
                data
                )
        return st

    def __getinitargs__(self):
        return (None,)

    def __getstate__(self):
        return {'data':self.data}


class ProcessingInstruction(Node):
    def __init__(self, ownerDocument):
        Node.__init__(self, ownerDocument, Node.PROCESSING_INSTRUCTION_NODE)
        self.__dict__['nodeName'] = ''
        self.__dict__['target'] = ''
        self.data = ''

    def __setattr__(self,name,value):
        if name  =='target':
            self.__dict__['nodeName'] = value
        elif name  =='nodeName':
            self.__dict__['target'] = value
        elif name  =='data':
            self.__dict__['nodeValue'] = value
        elif name  =='nodeValue':
            self.__dict__['data'] = value
        self.__dict__[name] = value


    def cloneNode(self, deep):
        pi = self.__class__(self.ownerDocument)
        pi.target = self.target
        pi.data = self.data
        return pi

    def __repr__(self):
        return "<Domlette Processing Instruction Node at %x: target='%s%s', data='%s%s'>" % (
            id(self),
            self.target[:20],
            len(self.target) > 20 and "..." or "",
            self.data[:20],
            len(self.data) > 20 and "..." or ""
            )

    def __getinitargs__(self):
        return (None,)

    def __getstate__(self):
        return {'target':self.target, 'data':self.data}


class Comment(Node):
    def __init__(self, ownerDocument):
        Node.__init__(self, ownerDocument, Node.COMMENT_NODE)
        self.nodeName = '#comment'
        self.data = ''

    def cloneNode(self, deep):
        comment = self.__class__(self.ownerDocument)
        comment.data = self.data
        return comment

    def __setattr__(self,name,value):
        if name  =='data':
            self.__dict__['nodeValue'] = value
        elif name  =='nodeValue':
            self.__dict__['data'] = value
        self.__dict__[name] = value

    def __repr__(self):
        return "<Domlette Comment Node at %x: data='%s%s'>" % (
            id(self),
            self.data[:20],
            len(self.data) > 20 and "..." or ""
            )

    def __getinitargs__(self):
        return (None,)

    def __getstate__(self):
        return {'data':self.data}

class NamedNodeMap(UserDict.UserDict):
    def __init__(self, ownerDocument=None):
        UserDict.UserDict.__init__(self)
        self.ownerDocument = ownerDocument

    def item(self, index):
        try:
            return self[self.keys()[int(index)]]
        except IndexError:
            return None

    def __getitem__(self, index):
        if type(index) == type(0):
            return self[self.keys()[index]]
        else:
            return UserDict.UserDict.__getitem__(self, index)

    def __getattr__(self,name):
        if name == 'length':
            return len(self)
        raise AttributeError(name)

    def __repr__(self):
        st = "<Domlette NamedNodeMap at %x: {"% id(self)
        st = reduce(lambda s, (k,v): s+repr(k)+':'+repr(v)+', ', self.items(), st)
        if len(self):
            st = st[:-2]
        return st + '}>'


def ReleaseNode(node):
    for child in node.childNodes:
        ReleaseNode(child)
    node.childNodes = []
    if node.nodeType == Node.ELEMENT_NODE:
        for attr in node.attributes.values():
            ReleaseNode(attr)
        node.attributes = {}
    node.parentNode = None
    node.firstChild = None
    node.lastChild = None
    node.previousSibling = None
    node.nextSibling = None
    node.ownerDocument = None
    if node.nodeType == Node.ATTRIBUTE_NODE:
        node.ownerElement = None
    return


def PickleDocument(doc):
    stream = cStringIO.StringIO()
    pickler = cPickle.Pickler(stream, 1)
    _PickleChildren(pickler, doc.childNodes)
    return stream.getvalue()

def PickleNode(node):
    stream = cStringIO.StringIO()
    pickler = cPickle.Pickler(stream, 1)
    pickler.dump(node)
    _PickleChildren(pickler, node.childNodes)
    return stream.getvalue()


# Helper function for pickling
def _PickleChildren(pickler, children):
    pickler.dump(len(children))
    for child in children:
        pickler.dump(child)
        _PickleChildren(pickler, child.childNodes)

def UnpickleDocument(pickledXml):
    doc = Document()
    stream = cStringIO.StringIO(pickledXml)
    unpickler = cPickle.Unpickler(stream)
    _UnpickleChildren(unpickler, doc)
    return doc

def UnpickleNode(pickledXml, doc=None):
    doc = doc or Document()
    stream = cStringIO.StringIO(pickledXml)
    unpickler = cPickle.Unpickler(stream)
    topLevelNode = unpickler.load()
    doc.appendChild(topLevelNode)
    if topLevelNode.attributes:
        for attr in topLevelNode.attributes.values():
            attr.ownerDocument = topLevelNode.ownerDocument
    _UnpickleChildren(unpickler, topLevelNode)
    return topLevelNode

## Helper function for unpickling ##


def _UnpickleChildren(unpickler, node):
    children = unpickler.load()
    while children:
        child = unpickler.load()
        node.appendChild(child)
        if child.nodeType == Node.ELEMENT_NODE:
            for attr in child.attributes:
                attr.ownerDocument = child.ownerDocument
        _UnpickleChildren(unpickler, child)
        children = children - 1


from pDomletteReader import *

