# XML DOM tree manipulation

import cgi
from HTMLParser import HTMLParser
from xml.dom import Node
import xml.dom.minidom

import strings

def escape(str_):
    return strings.to_unicode(cgi.escape(str_, True)).encode('ascii',
        'xmlcharrefreplace')

def unescape(str_): return HTMLParser().unescape(str_)

def get_id(node): return node.getAttribute('id')

def set_id(node, id_): node.setAttribute('id', id_)

def is_empty(node): return node.firstChild == None

class NodeElemIter:
    def __init__(self, node): self.child = node.firstChild
    
    def __iter__(self): return self
    
    def curr(self):
        while self.child != None:
            if self.child.nodeType == Node.ELEMENT_NODE: return self.child
            self.child = self.child.nextSibling
        raise StopIteration
    
    def next(self):
        child = self.curr()
        self.child = self.child.nextSibling
        return child

def first_elem(node): return NodeElemIter(node).next()

class NodeElemReverseIter:
    def __init__(self, node): self.child = node.lastChild
    
    def __iter__(self): return self
    
    def curr(self):
        while self.child != None:
            if self.child.nodeType == Node.ELEMENT_NODE: return self.child
            self.child = self.child.previousSibling
        raise StopIteration
    
    def next(self):
        child = self.curr()
        self.child = self.child.previousSibling
        return child

def last_elem(node): return NodeElemReverseIter(node).next()

class NodeParentIter:
    def __init__(self, node): self.node = node
    
    def __iter__(self): return self
    
    def curr(self):
        if self.node != None and self.node.nodeType == Node.ELEMENT_NODE:
            return self.node
        raise StopIteration
    
    def next(self):
        node = self.curr()
        self.node = self.node.parentNode
        return node

def is_text(node):
    for child in NodeElemIter(node): return False # has an element node
    return True

def value(node):
    if node.firstChild != None: return node.firstChild.nodeValue
    else: return node.nodeValue

def set_value(node, value):
    if node.nodeType == Node.ELEMENT_NODE:
        node.appendChild(node.ownerDocument.createTextNode(value))
    else: node.nodeValue = value

class NodeTextEntryIter:
    def __init__(self, node): self.iter_ = NodeElemIter(node)
    
    def __iter__(self): return self
    
    def curr(self):
        while True:
            child = self.iter_.curr()
            if is_text(child): return (child.tagName, value(child))
            self.iter_.next()
    
    def next(self):
        entry = self.curr()
        self.iter_.next()
        return entry

def set_child(node, name, value):
    '''Note: does not remove any existing child of the same name'''
    child = node.ownerDocument.createElement(name)
    set_value(child, value)
    node.appendChild(child)

def replace(old_node, new_node):
    old_node.parentNode.replaceChild(new_node, old_node) # note order reversed

def replace_with_text(node, str_):
    replace(node, node.ownerDocument.createTextNode(str_))

def by_tag_name(node, name, last_only=False):
    '''last_only optimization returns last matching node'''
    children = []
    for child in NodeElemReverseIter(node):
        if child.tagName == name:
            children.append(child)
            if last_only: break
    return children

def create_doc(root='_'):
    return xml.dom.minidom.getDOMImplementation().createDocument(None, root,
        None)

def writexml(writer, node): node.writexml(writer, addindent='    ', newl='\n')

# xml.dom.minidom modifications

def _write_data(writer, data): writer.write(escape(data))

xml.dom.minidom._write_data = _write_data

_writexml_orig = xml.dom.minidom.Element.writexml

def _writexml(self, writer, indent="", addindent="", newl=""):
    if self.firstChild != None and self.firstChild.nextSibling == None\
    and self.firstChild.nodeType == Node.TEXT_NODE: # a single text node
        writer.write(indent+'<'+self.tagName)
        for attr_idx in xrange(self.attributes.length):
            attr = self.attributes.item(attr_idx)
            writer.write(' '+attr.name+'='+escape(attr.value))
        writer.write('>'+escape(value(self))+'</'+self.tagName+'>'+newl)
    else: _writexml_orig(self, writer, indent, addindent, newl)

xml.dom.minidom.Element.writexml = _writexml

xml.dom.minidom.Node.__str__ = lambda self: self.toxml()
