# This program is free software; you can redistribute it and/or modify it under
# the terms of the (LGPL) GNU Lesser General Public License as published by the
# Free Software Foundation; either version 3 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 Library Lesser General Public License
# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
#
# You should have received a copy of the GNU Lesser 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.
# written by: Jeff Ortel ( jortel@redhat.com )

"""
Classes providing a (D)ocument (O)bject (M)odel representation of an XML
document.

The goal is to provide an easy, intuitive interface for managing XML documents.
Although the term DOM is used above, this model is B{far} better.

XML namespaces in suds are represented using a (2) element tuple containing the
prefix and the URI, e.g. I{('tns', 'http://myns')}.

"""

import suds
from suds import *
from suds.sax import *
from suds.sax.attribute import Attribute
from suds.sax.document import Document
from suds.sax.element import Element
from suds.sax.text import Text

import sys
from xml.sax import make_parser, InputSource, ContentHandler
from xml.sax.handler import feature_external_ges


class Handler(ContentHandler):
    """SAX handler."""

    def __init__(self):
        self.nodes = [Document()]

    def startElement(self, name, attrs):
        top = self.top()
        node = Element(str(name))
        for a in attrs.getNames():
            n = str(a)
            v = str(attrs.getValue(a))
            attribute = Attribute(n, v)
            if self.mapPrefix(node, attribute):
                continue
            node.append(attribute)
        node.charbuffer = []
        top.append(node)
        self.push(node)

    def mapPrefix(self, node, attribute):
        if attribute.name == "xmlns":
            if len(attribute.value):
                node.expns = str(attribute.value)
            return True
        if attribute.prefix == "xmlns":
            prefix = attribute.name
            node.nsprefixes[prefix] = str(attribute.value)
            return True
        return False

    def endElement(self, name):
        name = str(name)
        current = self.pop()
        if name != current.qname():
            raise Exception("malformed document")
        if current.charbuffer:
            current.text = Text("".join(current.charbuffer))
        del current.charbuffer
        if current:
            current.trim()

    def characters(self, content):
        text = str(content)
        node = self.top()
        node.charbuffer.append(text)

    def push(self, node):
        self.nodes.append(node)
        return node

    def pop(self):
        return self.nodes.pop()

    def top(self):
        return self.nodes[-1]


class Parser:
    """SAX parser."""

    @classmethod
    def saxparser(cls):
        p = make_parser()
        p.setFeature(feature_external_ges, 0)
        h = Handler()
        p.setContentHandler(h)
        return p, h

    def parse(self, file=None, string=None):
        """
        SAX parse XML text.

        @param file: Parse a python I{file-like} object.
        @type file: I{file-like} object
        @param string: Parse string XML.
        @type string: str
        @return: Parsed XML document.
        @rtype: L{Document}

        """
        if file is None and string is None:
            return
        timer = suds.metrics.Timer()
        timer.start()
        source = file
        if file is None:
            source = InputSource(None)
            source.setByteStream(suds.BytesIO(string))
        sax, handler = self.saxparser()
        sax.parse(source)
        timer.stop()
        if file is None:
            suds.metrics.log.debug("%s\nsax duration: %s", string, timer)
        else:
            suds.metrics.log.debug("sax (%s) duration: %s", file, timer)
        return handler.nodes[0]
