"""
Parse an XML file into a DOM made up of objects from 'Node.py'.

Although this is not strictly compatible with DOM (Or SAX) it
is designed to by lightweight and fast.
"""
# $Id: XMLParser.py,v 1.1.1.1 2002/02/03 17:07:16 root Exp $
#
# $Log: XMLParser.py,v $
# Revision 1.1.1.1  2002/02/03 17:07:16  root
#
#
# Revision 1.4  2001/10/23 09:35:00  adrian
# no message
#
# Revision 1.3  2001/10/22 15:40:00  adrian
# Started moving common functions to base classes
#
# Revision 1.2  2001/10/21 15:10:16  adrian
# no message
#
#
__version__ = '$Revision: 1.1.1.1 $'[11:-2]

import string
import xmllib
import urlparse
import httplib
import Node

def _resetFunc(self):
    """
        Additional Reset Function
        This is the default function if the client does not specify one.
    """
    pass

class XMLParser(xmllib.XMLParser):
    """ """
    def __init__(self, _f_reset=_resetFunc):
        """ Call BaseParser.__init__ and XMLParser.__init__ """
        xmllib.XMLParser.__init__(self)
        self._f_reset = _f_reset
        self.reset()

    def reset(self):
        """ """
        xmllib.XMLParser.reset(self)
        self.DOM = Node.Document()
        self.currentNode = self.DOM
        if not hasattr(self, '_f_reset'):
            self._f_reset = _resetFunc
        self._f_reset(self)

    def runString(self, string):
        """ """
        self.reset()
        self.feed(string)
        self.close()

    def runURL(self, source):
        """ Read an XML file from the passed URL """
        d1, server, file, d2, d3, d4 = urlparse.urlparse(source)
        try:
            h = httplib.HTTP(server)
        except:
            raise "Failed to connect to server %s" % server, ""
        if d3:
            file = "%s?%s" % (file, d3)
        h.putrequest('GET', file)
        h.putheader('Accept', 'text/xml')
        h.putheader('Accept', 'text/plain')
        h.putheader('Accept', 'text/html')
        h.putheader('Accept', 'text/*')
        h.putheader('HOST', server)
        h.endheaders()
        errcode, errmsg, headers = h.getreply()
        if errcode == 200:
            self.runString("%s" % h.getfile().read())
        elif errcode == 301:
            raise "HTTP error:%d, message:%s, url:[http://%s%s]<hr><pre>%s</pre>" % (errcode, errmsg, server, file, headers), ""
        else:
            raise "HTTP error:%d, message:%s, url:[http://%s%s]" % (errcode, errmsg, server, file), ""
        self.close()

    def feed(self, data):
        """ Feed the data to the XML parser instead of the BaseParser """
        data = string.strip(data)
        xmllib.XMLParser.feed(self, data)

    def handle_xml(self, encoding, standalone):
        """ """
        self.currentNode.addChild(
            Node.XMLNode(encoding, standalone))

    def handle_proc(self, name, data):
        """ """
        self.currentNode.addChild(
            Node.PINode(name, data))

    def handle_doctype(self, tag, pubid, syslit, data):
        """ """
        self.currentNode.addChild(
            Node.DocType(tag, pubid, syslit, data))

    def handle_comment(self, data):
        """ """
        self.currentNode.addChild(
            Node.CommentNode(string.strip(data)))

    def unknown_starttag(self, tag, attrs):
        """ """
        t = string.split(tag)
        if len(t)>1:
            tag = t[1]
            ns = t[0]
        else:
            ns = ''
        self.currentNode = self.currentNode.addChild(
            Node.Node(tag, ns, attrs))

    def unknown_endtag(self, tag):
        """ """
        t = string.split(tag)
        if len(t)>1:
            tag = t[1]
            ns = t[0]
        self.currentNode = self.currentNode._parent

    def handle_data(self, data):
        """ Add data to the current node """
        self.handle_cdata(string.strip(data))

    def handle_cdata(self, data):
        """ Add cdata to the current node """
        self.currentNode.addCDATA(data)

_entity_map = (
    ('"', 'quot'),
    ("'", 'apos'),
    ("&", 'amp'),
    ("<", 'lt'),
    (">", 'gt'),
)

_encode_map = {}
_decode_map = {}
for item in _entity_map:
    _encode_map[item[0]] = item[1]
    _decode_map[item[1]] = item[0]
_encode_keys = _encode_map.keys()
_decode_keys = _decode_map.keys()

def encode(xml):
    """ Encode the passed string, changing all troublsome characters to
        character entities """
    retval = ""
    for c in xml:
        i = ord(c)
        if (i<32 or i>126) and (i not in [10, 13]):
            retval = retval + "&#x%x;" % i
        elif c in _encode_keys:
            retval = retval + "&%s;" % _encode_map[c]
        else:
            retval = retval + c
    return retval

def decode(xml):
    """ Convert all the character entities in the passed XML string into
        the characters they represent """
# Ok, so we need to scan the string, and avoid re-processing
# ('&amp;gt;' is NOT '>')
    retval = xml
    i=0
    while i<len(retval):
        if retval[i] == '&':
            j = string.find(retval, ';')
            try:
                if retval[i+1] == '#':
                    if retval[i+2] == 'x':
                         c = chr(string.atoi(retval[i+3:j], 16))
                    else:
                         c = chr(string.atoi(retval[i+2:j]))
                    retval = retval[:i] + c + retval[j+1:]
                elif retval[i+1:j] in _decode_keys:
                    retval = retval[:i] + _decode_map[retval[i+1:j]] + retval[j+1:]
                else:
                    i = j
            except:
                pass
        i = i + 1
    return retval
