##############################################################################
# 
# 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.
# 
##############################################################################


"""Constructor for ParsedXML.DOM, based on a SAX parser."""

import os
import urllib
import xml.sax


class SAXBuilder(xml.sax.ContentHandler):
    _locator = None
    document = None
    documentElement = None

    def __init__(self, documentFactory=None):
        self.documentFactory = documentFactory
        self._ns_contexts = [{}] # contains uri -> prefix dicts
        self._current_context = self._ns_contexts[-1]

    def install(self, parser):
        parser.setContentHandler(self)

    def setDocumentLocator(self, locator):
        self._locator = locator

    def startPrefixMapping(self, prefix, uri):
        self._ns_contexts.append(self._current_context.copy())
        self._current_context[uri] = prefix or None

    def endPrefixMapping(self, prefix):
        self._current_context = self._ns_contexts.pop()

    def _make_qname(self, uri, localname, tagname):
        # When using namespaces, the reader may or may not
        # provide us with the original name. If not, create
        # *a* valid tagName from the current context.
        if uri:
            if tagname is None:
                prefix = self._current_context.get(uri)
                if prefix:
                    tagname = "%s:%s" % (prefix, localname)
                else:
                    tagname = localname
        else:
            tagname = localname
        return tagname

    def startElementNS(self, name, tagName, attrs):
        uri, localname = name
        tagName = self._make_qname(uri, localname, tagName)
        if not self.document:
            factory = self.documentFactory
            self.document = factory.createDocument(uri or None, tagName, None)
            node = self.document.documentElement
        else:
            if uri:
                node = self.document.createElementNS(uri, tagName)
            else:
                node = self.document.createElement(localname)
            self.curNode.appendChild(node)
        self.curNode = node

        for aname, value in attrs.items():
            a_uri, a_localname = aname
            if a_uri:
                qname = "%s:%s" % (self._current_context[a_uri], a_localname)
                node.setAttributeNS(a_uri, qname, value)
            else:
                attr = self.document.createAttribute(a_localname)
                node.setAttribute(a_localname, value)

    def endElementNS(self, name, tagName):
        self.curNode = self.curNode.parentNode

    def startElement(self, name, attrs):
        if self.documentElement is None:
            factory = self.documentFactory
            self.document = factory.createDocument(None, name, None)
            node = self.document.documentElement
            self.documentElement = 1
        else:
            node = self.document.createElement(name)
            self.curNode.appendChild(node)
        self.curNode = node

        for aname, value in attrs.items():
            node.setAttribute(aname, value)

    def endElement(self, name):
        self.curNode = self.curNode.parentNode

    def comment(self, s):
        node = self.document.createComment(s)
        self.curNode.appendChild(node)

    def processingInstruction(self, target, data):
        node = self.document.createProcessingInstruction(target, data)
        self.curNode.appendChild(node)

    def ignorableWhitespace(self, chars):
        node = self.document.createTextNode(chars)
        self.curNode.appendChild(node)

    def characters(self, chars):
        node = self.document.createTextNode(chars)
        self.curNode.appendChild(node)


def parse(file, namespaces=1, dom=None, parser=None):
    if not parser:
        parser = xml.sax.make_parser()
    parser.setFeature(xml.sax.handler.feature_namespaces, namespaces)
    if not dom:
        import Core
        dom = Core.theDOMImplementation
    if isinstance(file, type('')):
        try:
            fp = open(file)
        except IOError, e:
            if e.errno != errno.ENOENT:
                raise
            fp = urllib.urlopen(file)
            systemId = file
        else:
            # Ugh!  Why doesn't urllib.pathname2url() do something useful?
            systemId = "file://" + os.path.abspath(file)
    else:
        source = xml.sax.InputSource()
        fp = file
        try:
            systemId = file.name
        except AttributeError:
            systemId = None
    source = xml.sax.InputSource(file)
    source.setByteStream(fp)
    source.setSystemId(systemId)
    builder = SAXBuilder(documentFactory=dom)
    builder.install(parser)
    parser.parse(source)
    if fp is not file:
        fp.close()
    return builder.document
