From 3458e3f24d1ebebcef2dbf54df23ebc01f6ed266 Mon Sep 17 00:00:00 2001 From: Thomas Leonard Date: Sat, 17 Nov 2007 11:54:04 +0000 Subject: [PATCH] Better formatting when merging. git-svn-id: file:///home/talex/Backups/sf.net/Subversion/zero-install/trunk/0publish@2081 9f8c893c-44ee-0310-b757-c8ca8341c71e --- merge.py | 10 ++--- xmltools.py | 139 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+), 6 deletions(-) create mode 100644 xmltools.py diff --git a/merge.py b/merge.py index a56e82b..7ec132f 100644 --- a/merge.py +++ b/merge.py @@ -3,6 +3,7 @@ from xml.dom import minidom, XMLNS_NAMESPACE, Node from zeroinstall.injector.namespaces import XMLNS_IFACE from zeroinstall.injector import model, reader from logging import info +import xmltools def childNodes(parent, namespaceURI, localName = None): for x in parent.childNodes: @@ -87,8 +88,7 @@ def merge(data, local): # If we have additional requirements, we'll need to create a subgroup and add them if len(new_impl_context.requires) > len(group_context.requires): - subgroup = master_doc.createElementNS(XMLNS_IFACE, 'group') - group.appendChild(subgroup) + subgroup = xmltools.create_element(group, 'group') group = subgroup group_context = Context(group) for x in new_impl_context.requires: @@ -97,7 +97,7 @@ def merge(data, local): else: req = master_doc.importNode(x, True) #print "Add", req - group.appendChild(req) + xmltools.insert_element(req, group) new_impl = master_doc.importNode(impl, True) for name, value in new_impl.attributes.itemsNS(): @@ -109,8 +109,6 @@ def merge(data, local): #print "Set", name, value new_impl.setAttributeNS(name[0], name[1], value) - group.appendChild(master_doc.createTextNode(' ')) - group.appendChild(new_impl) - group.appendChild(master_doc.createTextNode('\n')) + xmltools.insert_element(new_impl, group) return master_doc.toxml() diff --git a/xmltools.py b/xmltools.py new file mode 100644 index 0000000..50d123b --- /dev/null +++ b/xmltools.py @@ -0,0 +1,139 @@ +from xml.dom import Node, minidom + +XMLNS_INTERFACE = "http://zero-install.sourceforge.net/2004/injector/interface" +XMLNS_COMPILE = "http://zero-install.sourceforge.net/2006/namespaces/0compile" + +def data(node): + """Return all the text directly inside this DOM Node.""" + return ''.join([text.nodeValue for text in node.childNodes + if text.nodeType == Node.TEXT_NODE]) + +def set_data(elem, value): + """Replace all children of 'elem' with a single text node containing 'value'""" + for node in elem.childNodes: + elem.removeChild(node) + if value: + text = elem.ownerDocument.createTextNode(value) + elem.appendChild(text) + +def indent_of(x): + """If x's previous sibling is whitespace, return its length. Otherwise, return 0.""" + indent = x.previousSibling + if indent and indent.nodeType == Node.TEXT_NODE: + spaces = indent.nodeValue.split('\n')[-1] + if spaces.strip() == '': + return len(spaces) + return 0 + +def insert_before(new, next): + indent_depth = indent_of(next) + new_parent = next.parentNode + + prev = next.previousSibling + if prev and prev.nodeType == Node.TEXT_NODE: + next = prev + + new_parent.insertBefore(new, next) + if indent_depth: + text = new_parent.ownerDocument.createTextNode('\n' + (' ' * indent_depth)) + new_parent.insertBefore(text, new) + +def insert_after(new, prev): + indent_depth = indent_of(prev) + new_parent = prev.parentNode + + if prev.nextSibling: + new_parent.insertBefore(new, prev.nextSibling) + else: + new_parent.appendChild(new, new_parent) + + if indent_depth: + text = new_parent.ownerDocument.createTextNode('\n' + (' ' * indent_depth)) + new_parent.insertBefore(text, new) + +def insert_element(new, parent, before = []): + indent = indent_of(parent) + 2 # Default indent + last_element = None + for x in parent.childNodes: + if x.nodeType == Node.ELEMENT_NODE: + indent = indent or indent_of(x) + if x.localName in before: + parent.insertBefore(new, last_element.nextSibling) + break + last_element = x + else: + if last_element: + parent.insertBefore(new, last_element.nextSibling) + else: + had_children = bool(list(child_elements(parent))) + parent.appendChild(new) + if not had_children: + final_indent = '\n' + (' ' * indent_of(parent)) + parent.appendChild(parent.ownerDocument.createTextNode(final_indent)) + if indent: + indent_text = parent.ownerDocument.createTextNode('\n' + (' ' * indent)) + parent.insertBefore(indent_text, new) + +def create_element(parent, name, uri = XMLNS_INTERFACE, before = []): + """Create a new child element with the given name. + Add it as far down the list of children as possible, but before + any element in the 'before' set. Indent it sensibly.""" + new = parent.ownerDocument.createElementNS(uri, name) + insert_element(new, parent, before) + return new + +def remove_element(elem): + """Remove 'elem' and any whitespace before it.""" + parent = elem.parentNode + prev = elem.previousSibling + if prev and prev.nodeType == Node.TEXT_NODE: + if prev.nodeValue.strip() == '': + parent.removeChild(prev) + parent.removeChild(elem) + + whitespace = [] + for x in parent.childNodes: + if x.nodeType != Node.TEXT_NODE: return + if x.nodeValue.strip(): return + whitespace.append(x) + + # Nothing but white-space left + for w in whitespace: + parent.removeChild(w) + +def format_para(para): + """Turn new-lines into spaces, removing any blank lines.""" + lines = [l.strip() for l in para.split('\n')] + return ' '.join(filter(None, lines)) + +def attrs_match(elem, attrs): + for x in attrs: + if not elem.hasAttribute(x): return False + if elem.getAttribute(x) != attrs[x]: return False + return True + +def child_elements(parent): + """Yield all direct child elements.""" + for x in parent.childNodes: + if x.nodeType == Node.ELEMENT_NODE: + yield x + +def children(parent, localName, uri = XMLNS_INTERFACE, attrs = {}): + """Yield all direct child elements with this name and attributes.""" + for x in parent.childNodes: + if x.nodeType == Node.ELEMENT_NODE: + if x.nodeName == localName and x.namespaceURI == uri and attrs_match(x, attrs): + yield x + +def singleton_text(parent, localName, uri = XMLNS_INTERFACE): + """Return the text of the first child element with this name, or None + if there aren't any.""" + elements = list(children(parent, localName, uri)) + if elements: + return data(elements[0]) + +def set_or_remove(element, attr_name, value): + if value: + element.setAttribute(attr_name, value) + elif element.hasAttribute(attr_name): + element.removeAttribute(attr_name) -- 2.11.4.GIT