"Some XML helper classes."
import string
from types import StringType, ListType, TupleType
import re
import pprint

nakedAmp = re.compile('&\s')

IGNOREWHITESPACE = 1
_XMLESCAPE=1

if _XMLESCAPE:
    import xml.sax.saxutils
    xmlEscape = xml.sax.saxutils.escape
else:
    xmlEscape = lambda x: x

def escapeNakedAmpersands(xmlText):
    # avoid a problem caused by the Fidelity transport
    # protocol - embedding xml in an attribute of a form
    return nakedAmp.sub('&amp; ', xmlText)

def translateStandardEntities(s,mode=0):
    ''' &amp; &lt; &gt; &quot; &apos; --> & < > " ' mode = 0 or
        & < > " ' --> &amp; &lt; &gt; &quot; &apos; mode = 1'''  #"
    if mode:
        s = string.replace(s,"&",'&amp;')
        s = string.replace(s,"<",'&lt;')
        s = string.replace(s,">",'&gt;')
        s = string.replace(s,'"','&quot;')
        s = string.replace(s,"'",'&apos;')
    else:
        s = string.replace(s,'&amp;',"&")
        s = string.replace(s,'&lt;',"<")
        s = string.replace(s,'&gt;',">")
        s = string.replace(s,'&quot;','"')
        s = string.replace(s,'&apos;',"'")
    return s

from rlextra.utils.cgisupport import quoteValue
def _makeXMLTag(name,attrs,contents,inner=1):
    T = []
    a = T.append
    if inner:
        a('\n<%s' % name)
        for k,v in attrs.items():
            a('%s="%s"' % (k,quoteValue(v)))
        T[:] = [' '.join(T)]
    if contents in ([],None):
        if inner: a('/>')
    else:
        if inner: a('>')
        for c in contents:
            a(reconstructXML(c,inner=1))
        if inner: a('</%s>' % name)
    return ''.join(T)

def reconstructXML(tag,inner=0):
    '''Takes a TagWrapper and converts to XML,
    if inner=1 then the outer tag is made explicit'''
    R = []
    r = R.append
    if type(tag) in (ListType, TupleType):
        r(_makeXMLTag(tag[0],tag[1] or {},tag[2]))
    elif isinstance(tag,TagWrapper):
        r(_makeXMLTag(tag.tagName,tag._attrs,tag._children,inner=inner))
    else:
        r(quoteValue(tag))
    return ''.join(R)

def ignoreWhitespace(L):
    N = []
    a = N.append
    for e in L:
        if type(e) is StringType:
            e = string.strip(e)
            if not e: continue
        a(e)
    L[:] = N
    return L

    #####################################################
    #
    # miscellaneous tree manipulating utilities.  Work in
    # place where possible.
    #####################################################

def writeXML(tree):
    "Convert to a string.  No auto-indenting provided yet"
    if type(tree) is StringType:
        return tree
    else:
        (tagName, attrs, children, spare) = tree
        chunks = []
        chunks.append('<%s ' % tree)

def stripWhitespace(tree):
    "Remove any whitespace-only text nodes from the tree."
    tagName, attrs, children, spare = tree
    if children is None:
        return
    to_remove = []
    for child in children:
        if type(child) is StringType:
            if string.strip(child) == '':
                to_remove.append(child)
        elif type(child) is TupleType:
            stripWhitespace(child)
    for rem in to_remove:
        children.remove(rem)

def transformTagNames(tree, func):
    "Replace tagName with func(tagName)"
    tagName, attrs, children, spare = tree

    newTagName = func(tagName)
    if children is None:
        newChildren = None
    else:
        newChildren = []
        for child in children:
            if type(child) is StringType:
                newChildren.append(child)
            elif type(child) is TupleType:
                newChildren.append(transformTagNames(child, func))
    return (newTagName, attrs, newChildren, spare)


    
class TagWrapper:
    """Lazy utility for navigating XML.

    The following Python code works:

    tag.attribute      # returns given attribute
    tag.child          # returns first child with matching tag name
    for child in tag:  # iterates over them
    tag[3]             # returns fourth child
    len(tag)           # no of children
    """

    def __init__(self, node, parent=None):
        tagName, attrs, children, spare = node
        self.tagName = tagName

        if attrs is None:
            self._attrs = {}
        else:
            self._attrs = attrs  # share the dictionary

        if children is None:
            self._children = []
        elif IGNOREWHITESPACE:
            self._children = ignoreWhitespace(children)
        else:
            self._children = children
        self._parent = parent

    def __del__(self):
        del self._parent

    def __repr__(self):
        return '[%s TagWrapper@%8.8x]' % (self.tagName,id(self))

    def __str__(self):
        if len(self): return self[0] or ''
        else: return ""

    def __len__(self):
        return len(self._children)

    def _value(self,name,default):
        try:
            r = getattr(self,name)
            if type(r) is type(''): return r
            return r[0]
        except (AttributeError, IndexError):
            return default

    def __getattr__(self, attr):
        "Try various priorities"
        if self._attrs.has_key(attr):
            a = self._attrs[attr]
            if _XMLESCAPE: a = xmlEscape(a)
            return a
        else:
            #first child tag whose name matches?
            for child in self._children:
                if type(child) is StringType:
                    pass
                else:
                    tagName, attrs, children, spare = child
                    if tagName == attr:
                        return TagWrapper(child,(self._children,self._children.index(child)))
            # not found, barf
            msg = '"%s" not found in attributes of %s tag or its children' % (attr, self.tagName)
            raise AttributeError, msg

    def keys(self):
        "return list of valid keys"
        result = self._attrs.keys()
        for child in self._children:
            if type(child) is StringType: pass
            else: result.append(child[0])
        return result

    def has_key(self,k):
        return k in self.keys()

    def __getitem__(self, idx):
        try:
            child = self._children[idx]
        except IndexError:
            raise IndexError, '%s no index %s' % (self.__repr__(), `idx`)
        if type(child) is StringType:
            if _XMLESCAPE: child = xmlEscape(child)
            return child
        else: return TagWrapper(child,(self._children,idx))

    def _namedChildren(self,name):
        R = []
        for c in self:
            if type(c) is StringType:
                if name is None: R.append(c)
            elif name == c.tagName: R.append(c)
        return R

    def _namedNodes(self,name):
        R = []
        idx = 0
        while 1:
            try:
                c = self._children[idx]
            except IndexError:
                break
            if type(c) is StringType and name is None:
                if _XMLESCAPE: c = xmlEscape(c)
                R.append(c)
            elif c[0]==name: R.append(c)
            idx = idx+1
        return R

    def _replaceNamedChildren(self,name,nodes,all=0):
        '''given some tuples use to populate equivalent nodes under here'''
        if type(nodes) is not type([]): nodes = [nodes]
        C = self._children
        if not nodes:
            i = -1
        else:
            for i in xrange(len(C)):
                c = C[i]
                if c[0]==name:
                    C[i] = nodes[0]
                    del nodes[0]
                    if not nodes: break
        if all:
            i = i+1
            while i<len(C):
                if C[i][0]==name: del C[i]
                else: i = i+1
            map(C.append,nodes)

        p = self._parent
        if p:
            t = p[0][p[1]]
            n,a,c,o = t
            t = type(t) is TupleType and (n,self._attrs,self._children,o) or [n,self._attrs,self._children,o]
            p[0][p[1]] = t

    def _setAttr(self,**kw):
        self._attrs.update(kw)
        p = self._parent
        if p:
            t = p[0][p[1]]
            n,a,c,o = t
            t = type(t) is TupleType and (n,self._attrs,c,o) or [n,self._attrs,c,o]
            p[0][p[1]] = t

def compactDistro_eoCB(targets=(), dtdDirs=(), fs_eoCB=None):
    '''create an entity opening callback
    targets is a list of possible dtd basenames
    dtdDirs is a list of possible dtdDirs
    fs_eoCB is an alternate eoCB for when we're not compact
    '''
    from reportlab.lib.utils import isCompactDistro, open_and_read, rl_isfile
    if isCompactDistro():
        import sys, os
        def eoCB(s,targets=targets,dtdDirs=dtdDirs):
            bn = os.path.basename(s)
            if bn in targets:
                for d in dtdDirs:
                    fn = os.path.join(d,bn)
                    if rl_isfile(fn): return fn, open_and_read(fn,'t')
            return s
    else:
        eoCB = fs_eoCB
    return eoCB

def xml2rad(xml, validating=1, eoCB=None):
    '''convert xml to radxml form'''
    if validating:
        from reportlab.lib.utils import isCompactDistro, open_and_read
        if not eoCB and isCompactDistro():
            eoCB = lambda x, open_and_read=open_and_read: (x, open_and_read(x))
        import pyRXP
        if pyRXP.version>='0.5':
            rad = pyRXP.Parser().parse(xml,eoCB=eoCB)
        else:
            flags = 0x0157e1ff | pyRXP.PARSER_FLAGS['NoNoDTDWarning'] | pyRXP.PARSER_FLAGS['ErrorOnValidityErrors']
            rad = pyRXP.parse(xml,flags=flags,eoCB=eoCB)
    else:
        from reportlab.lib import rparsexml
        rad = rparsexml.parsexml0(xml)[0][2][0]
    return rad

def xml2doctree(xml, validating=1, eoCB=None):
    return TagWrapper(xml2rad(xml,validating, eoCB=None))

if __name__=='__main__':
    pass
