#
# genxmlif, Release 0.9.0
# file: xmlifDom.py
#
# XML interface base class for Python DOM implementations
#
# history:
# 2005-04-25 rl   created
# 2007-07-02 rl   complete re-design, internal wrapper 
#                 for DOM trees and elements introduced
# 2008-07-01 rl   Limited support of XInclude added
#
# Copyright (c) 2005-2008 by Roland Leuthe.  All rights reserved.
#
# --------------------------------------------------------------------
# The generic XML interface is
#
# Copyright (c) 2005-2008 by Roland Leuthe
#
# By obtaining, using, and/or copying this software and/or its
# associated documentation, you agree that you have read, understood,
# and will comply with the following terms and conditions:
#
# Permission to use, copy, modify, and distribute this software and
# its associated documentation for any purpose and without fee is
# hereby granted, provided that the above copyright notice appears in
# all copies, and that both that copyright notice and this permission
# notice appear in supporting documentation, and that the name of
# the author not be used in advertising or publicity
# pertaining to distribution of the software without specific, written
# prior permission.
#
# THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
# ABILITY AND FITNESS.  IN NO EVENT SHALL THE AUTHOR
# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
# OF THIS SOFTWARE.
# --------------------------------------------------------------------

import string
import copy
import urllib
from types                import TupleType
from xml.dom              import Node, getDOMImplementation, XMLNS_NAMESPACE
from genxmlif             import XINC_NAMESPACE, GenXmlIfError
from xmlifUtils           import nsNameToQName, processWhitespaceAction, collapseString, NsNameTupleFactory, convertToAbsUrl
from xmlifBase            import XmlIfBuilderExtensionBase
from xmlifApi             import XmlInterfaceBase


class XmlInterfaceDom (XmlInterfaceBase):
    """Derived interface class for handling of DOM parsers.
    
    For description of the interface methods see xmlifbase.py.
    """
    
    def xInclude (self, elementWrapper, baseUrl, ownerDoc):
        filePath = elementWrapper.getFilePath()
        for childElementWrapper in elementWrapper.getChildren():
            line = childElementWrapper.getStartLineNumber()
            if childElementWrapper.getNsName() == (XINC_NAMESPACE, "include"):
                href = childElementWrapper["href"]
                parse = childElementWrapper.getAttributeOrDefault ("parse", "xml")
                encoding = childElementWrapper.getAttribute ("encoding")
                if self.verbose:
                    print "Xinclude: %s" %href
                try:
                    if parse == "xml":
                        subTreeWrapper = self.parse (href, baseUrl, ownerDoc)
                        elementWrapper.replaceChildBySubtree (childElementWrapper, subTreeWrapper)
                    elif parse == "text":
                        absUrl = convertToAbsUrl (href, baseUrl)
                        fp = urllib.urlopen (absUrl)
                        data = fp.read()
                        if encoding:
                            data = data.decode(encoding)
                        newTextNode = ownerDoc.xmlIfExtCreateTextNode(data)
                        elementWrapper.element.element.insertBefore (newTextNode, childElementWrapper.element.element)
                        elementWrapper.removeChild (childElementWrapper)
                        fp.close()
                    else:
                        raise GenXmlIfError, "%s: line %s: XIncludeError: Invalid 'parse' Attribut: '%s'" %(filePath, line, parse)
                except IOError, errInst:
                    raise GenXmlIfError, "%s: line %s: IOError: %s" %(filePath, line, str(errInst))
            elif childElementWrapper.getNsName() == (XINC_NAMESPACE, "fallback"):
                raise GenXmlIfError, "%s: line %s: XIncludeError: xi:fallback tag must be child of xi:include" %(filePath, line)
            else:
                self.xInclude(childElementWrapper, baseUrl, ownerDoc)



class InternalDomTreeWrapper:
    """Internal wrapper for a DOM Document class.
    """
    def __init__ (self, document):
        self.document   = document
    
    def xmlIfExtGetRootNode (self):
        domNode = self.document
        if domNode.nodeType == Node.DOCUMENT_NODE:
            return domNode.documentElement.xmlIfExtInternalWrapper
        elif domNode.nodeType == Node.DOCUMENT_FRAGMENT_NODE:
            for node in domNode.childNodes:
                if node.nodeType == Node.ELEMENT_NODE:
                    return node.xmlIfExtInternalWrapper
            else:
                return None
        else:
            return None


    def xmlIfExtCreateElement (self, nsName, attributeDict, curNs):
        elementNode = self.document.createElementNS (nsName[0], nsName[1])
        intElementWrapper = self.internalElementWrapperClass(elementNode, self)
        for attrName, attrValue in attributeDict.items():
            intElementWrapper.xmlIfExtSetAttribute (NsNameTupleFactory(attrName), attrValue, curNs)
        return intElementWrapper


    def xmlIfExtCreateTextNode (self, data):
        return self.document.createTextNode(data)
    

    def xmlIfExtImportNode (self, node):
        return self.document.importNode (node, 0) 
        

    def xmlIfExtCloneTree (self, rootElementCopy):
        domImpl = getDOMImplementation()
#        documentCopy = domImpl.createDocument(rootElementCopy.xmlIfExtGetNamespaceURI(), rootElementCopy.xmlIfExtGetTagName(), None)
        documentCopy = domImpl.createDocument(None, None, None)
#        documentCopy = copy.copy(self.document)
        documentCopy.documentElement = rootElementCopy.element
        return self.__class__(documentCopy)

    

#########################################################
# Internal Wrapper class for a Dom Element class

class InternalDomElementWrapper:
    """Internal Wrapper for a Dom Element class.
    """
    
    def __init__ (self, element, internalDomTreeWrapper):
        self.element = element
        element.xmlIfExtInternalWrapper = self
        self.internalDomTreeWrapper = internalDomTreeWrapper
        

    def xmlIfExtUnlink (self):
        self.xmlIfExtElementWrapper = None
        

    def xmlIfExtCloneNode (self):
        nodeCopy = self.__class__(self.element.cloneNode(deep=0), self.internalDomTreeWrapper)
        for childTextNode in self.__xmlIfExtGetChildTextNodes():
            childTextNodeCopy = childTextNode.cloneNode(0)
            nodeCopy.element.appendChild (childTextNodeCopy)
#        for nsAttrName, attrValue in self.xmlIfExtGetAttributeDict().items():
#            nodeCopy.xmlIfExtSetAttribute(nsAttrName, attrValue, self.xmlIfExtElementWrapper.getCurrentNamespaces())
        return nodeCopy

    
    def xmlIfExtGetTagName (self):
        return self.element.tagName


    def xmlIfExtGetNamespaceURI (self):
        return self.element.namespaceURI


    def xmlIfExtGetParentNode (self):
        parentNode = self.element.parentNode
        if parentNode.nodeType == Node.ELEMENT_NODE:
            return self.element.parentNode.xmlIfExtInternalWrapper
        else:
            return None

    
    def xmlIfExtSetParentNode (self, parentElement):
        pass # nothing to do since parent is provided by DOM interface
    

    def xmlIfExtGetChildren (self, tagFilter=None):
        # TODO: Handle also wildcard tagFilter = (namespace, None)
        children = filter (lambda e: (e.nodeType == Node.ELEMENT_NODE) and          # - only ELEMENTs
                                      (tagFilter == None or 
                                       (e.namespaceURI == tagFilter[0] and e.localName == tagFilter[1])), # - if tagFilter given --> check
                           self.element.childNodes )                                 # from element's nodes

        return map(lambda element: element.xmlIfExtInternalWrapper, children)


    def xmlIfExtGetFirstChild (self, tagFilter=None):
        children = self.xmlIfExtGetChildren (tagFilter)
        if children != []:
            return children[0]
        else:
            None


    def xmlIfExtGetElementsByTagName (self, tagFilter=('*','*')):
        elementList = self.element.getElementsByTagNameNS( tagFilter[0], tagFilter[1] )
        return map( lambda element: element.xmlIfExtInternalWrapper, elementList )


    def xmlIfExtGetIterator (self, tagFilter=('*','*')):
        elementList = []
        if tagFilter in (('*','*'), (self.element.namespaceURI, self.element.localName)):
            elementList.append(self.element)
        elementList.extend(self.element.getElementsByTagNameNS( tagFilter[0], tagFilter[1] ))
        return map( lambda element: element.xmlIfExtInternalWrapper, elementList )

    
    def xmlIfExtAppendChild (self, childElement):
        self.element.appendChild (childElement.element)


    def xmlIfExtInsertBefore (self, childElement, refChildElement):
        self.element.insertBefore (childElement.element, refChildElement.element)


    def xmlIfExtRemoveChild (self, childElement):
        self.element.removeChild (childElement.element)


    def xmlIfExtInsertSubtree (self, refChildElement, subTree, insertSubTreeRootNode):
        if insertSubTreeRootNode:
            childElementList = [subTree.xmlIfExtGetRootNode(),]
        else:
            childElementList = subTree.xmlIfExtGetRootNode().xmlIfExtGetChildren()

        for childElement in childElementList:
            if refChildElement != None:
                self.element.insertBefore(childElement.element, refChildElement.element)
            else:
                self.element.appendChild(childElement.element)


    def xmlIfExtGetAttributeDict (self):
        attribDict = {}
        for nsAttrName, attrNodeOrValue in self.element.attributes.items():
            attribDict[NsNameTupleFactory(nsAttrName)] = attrNodeOrValue.nodeValue
        return attribDict


    def xmlIfExtGetAttribute (self, nsAttrName):
        if self.element.attributes.has_key (nsAttrName):
            return self.element.getAttributeNS (nsAttrName[0], nsAttrName[1])
        elif nsAttrName[1] == "xmlns" and self.element.attributes.has_key(nsAttrName[1]):
            # workaround for minidom for correct access of xmlns attribute
            return self.element.getAttribute (nsAttrName[1])
        else:
            return None


    def xmlIfExtSetAttribute (self, nsAttrName, attributeValue, curNs):
        if nsAttrName[0] != None:
            qName = nsNameToQName (nsAttrName, curNs)
        else:
            qName = nsAttrName[1]
        
        self.element.setAttributeNS (nsAttrName[0], qName, attributeValue)


    def xmlIfExtRemoveAttribute (self, nsAttrName):
        self.element.removeAttributeNS (nsAttrName[0], nsAttrName[1])


    def xmlIfExtGetElementValueFragments (self, ignoreEmtpyStringFragments):
        elementValueList = []
        for childTextNode in self.__xmlIfExtGetChildTextNodes():
            elementValueList.append(childTextNode.data)
        if ignoreEmtpyStringFragments:
            elementValueList = filter (lambda s: collapseString(s) != "", elementValueList)
        if elementValueList == []:
            elementValueList = ["",]
        return elementValueList


    def xmlIfExtGetElementText (self):
        elementTextList = ["",]
        if self.element.childNodes != []:
            for childNode in self.element.childNodes:
                if childNode.nodeType in (Node.TEXT_NODE, Node.CDATA_SECTION_NODE):
                    elementTextList.append (childNode.data)
                else:
                    break
        return "".join(elementTextList)

    
    def xmlIfExtGetElementTailText (self):
        tailTextList = ["",]
        nextSib = self.element.nextSibling
        while nextSib:
            if nextSib.nodeType in (Node.TEXT_NODE, Node.CDATA_SECTION_NODE):
                tailTextList.append (nextSib.data)
                nextSib = nextSib.nextSibling
            else:
                break
        return "".join(tailTextList)
        

    def xmlIfExtSetElementValue (self, elementValue):
        if self.__xmlIfExtGetChildTextNodes() == []:
            textNode = self.internalDomTreeWrapper.xmlIfExtCreateTextNode (elementValue)
            self.element.appendChild (textNode)
        else:
            self.__xmlIfExtGetChildTextNodes()[0].data = elementValue
            if len (self.__xmlIfExtGetChildTextNodes()) > 1:
                for textNode in self.__xmlIfExtGetChildTextNodes()[1:]:
                    textNode.data = ""
            

    def xmlIfExtProcessWsElementValue (self, wsAction):
        textNodes = self.__xmlIfExtGetChildTextNodes()

        if len(textNodes) == 1:
            textNodes[0].data = processWhitespaceAction (textNodes[0].data, wsAction)
        elif len(textNodes) > 1:
            textNodes[0].data = processWhitespaceAction (textNodes[0].data, wsAction, rstrip=0)
            lstrip = 0
            if len(textNodes[0].data) > 0 and textNodes[0].data[-1] == " ":
                lstrip = 1
            for textNode in textNodes[1:-1]:
                textNode.data = processWhitespaceAction (textNode.data, wsAction, lstrip, rstrip=0)
                if len(textNode.data) > 0 and textNode.data[-1] == " ":
                    lstrip = 1
                else:
                    lstrip = 0
            textNodes[-1].data = processWhitespaceAction (textNodes[-1].data, wsAction, lstrip)


    ###############################################################
    # PRIVATE methods
    ###############################################################

    def __xmlIfExtGetChildTextNodes ( self ):
        """Return list of TEXT nodes."""
        return filter (lambda e: ( e.nodeType in (Node.TEXT_NODE, Node.CDATA_SECTION_NODE) ), # - only TEXT-NODES
                       self.element.childNodes)                         # from element's child nodes
        


class XmlIfBuilderExtensionDom (XmlIfBuilderExtensionBase):
    """XmlIf builder extension class for DOM parsers."""
    
    pass
