# -*- coding: UTF-8 -*-


# Expression
# By: Frederic Peters <fpeters@entrouvert.com>
#     Emmanuel Raviart <eraviart@entrouvert.com>
#
# Copyright (C) 2004 Entr'ouvert, Frederic Peters & Emmanuel Raviart
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.


"""XML Documents Module"""


import libxml2

import elements
import environs
import logs
import namespaces
import nodes
import stations


class AbstractDocument(nodes.NodeWrapper):
    _node = None
    _rootElement = None
    defaultNamespaceUri = None
    mimeType = "text/xml"

    def __del__(self):
        if self._node is not None:
            #import inspect
            logs.warning("Freeing AbstractDocument: %r" % self)
            #for x in inspect.stack():
            #    print x[1][-30:], '%4s' % x[2], x[3]
            self._node.freeDoc()
            del self._node

    def __init__(self, node = None, defaultNamespaceUri = None):
        super(AbstractDocument, self).__init__(node)
        if defaultNamespaceUri is not None:
            self.defaultNamespaceUri = defaultNamespaceUri

    def absorbNode(self, element, isDirectElement = True):
        """Extract node from element and prepare it to be inserted in self.

        Element will still contain the node but will not manage its memory
        allocation anymore.
        """
        assert isinstance(element, elements.Element)
        assert element.owner is None, \
               "Trying to absorb an already absorbed node = %s" % element
        element.owner = self
        if self.defaultNamespaceUri is not None and isDirectElement:
            # We are absorbing the root node.
            element.node.newNs(self.defaultNamespaceUri, None)
        # Because of reconciliateNs (which will be executed ASAP, typically during append), the
        # namespaces defined in element dummyNodes are not used => They can be freed when
        # element will be freed, so we don't need to move dummyNodes of element to self.
        return element.node

    def append(self, item):
        if isinstance(item, elements.Element):
            assert self._rootElement is None
            self._rootElement = item
            item = self.absorbNode(item) # Prepare to execute the next if.
        if isinstance(item, libxml2.xmlCore):
            self.node.addChild(item)
            item.reconciliateNs(self.node)
        elif item is not None:
            raise Exception("Wrong item = %s (of type %s)" % (item, type(item)))

    def deleteNode(self):
        if self._node is not None:
            self._node.freeDoc()
        del self._node

    def getDocument(self):
        return self

    def getNode(self):
        return self._node

    def getRootElement(self):
        if self._rootElement is None:
            rootNode = self.getRootNode()
            if rootNode is None:
                return None
            # There doesn't exist any URI path to access a rootElement
            # => no uriPathFragment.
            self._rootElement = elements.newElement(
                rootNode, previous = self, owner = self)
        rootElement = self._rootElement
        return rootElement

    def getRootNode(self):
        try:
            return self.node.getRootElement()
        except libxml2.treeError, e:
            # The libxml2 Python binding raises this exception when
            # xmlDocGetRootElement returns None (or when there is a
            # parsing error)
            if e.msg == "[EX] Parsing failed":
                raise
            return None

    def newNamespace(self, namespaceUri):
        namespacePrefix = namespaces.getName(namespaceUri)
        return self.getRootNode().newNs(namespaceUri, namespacePrefix)

    def setNode(self, node):
        if self._node is not None:
            self._node.freeDoc()
        self._node = node

    def outputHttpSource(self):
        data = self.serialize()
        environs.getVar("httpRequestHandler").outputData(data, mimeType = "text/xml")

    node = property(getNode, setNode, deleteNode)


class TemporaryDocument(AbstractDocument, stations.AbstractStation):
    def __init__(self, node, *items, **attributes):
        defaultNamespaceUri = attributes.pop("defaultNamespaceUri", None)
        AbstractDocument.__init__(self, node, defaultNamespaceUri = defaultNamespaceUri)
        stations.AbstractStation.__init__(self,  *items, **attributes)

    def generatePlainText(self):
        rootElement = self.getRootElement()
        if rootElement is not None:
            return rootElement.generateXml(layout)
        return super(TemporaryDocument, self).generatePlainText()

    def generateXml(self, layout):
        rootElement = self.getRootElement()
        if rootElement is not None:
            return rootElement.generateXml(layout)
        return super(TemporaryDocument, self).generateXml(layout)

    def generateXmlDocument(self):
        rootElement = self.getRootElement()
        if rootElement is not None:
            return rootElement.generateXmlDocument()
        return super(TemporaryDocument, self).generateXmlDocument()


def newTemporaryDocument(*items, **attributes):
    return TemporaryDocument(libxml2.newDoc("1.0"), *items, **attributes)


def newTemporaryHtmlDocument(*items, **attributes):
    if "defaultNamespaceUri" not in attributes:
        attributes["defaultNamespaceUri"] = namespaces.html.uri
    htmlDocument = newTemporaryDocument(*items, **attributes)
    htmlDocument.mimeType = "text/html"
    htmlDocument.node.createIntSubset(
        "html", "-//W3C//DTD XHTML 1.0 Transitional//EN",
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd")
    return htmlDocument
