From 7b582ade927d250ad135e9779bbe3c867d0f7159 Mon Sep 17 00:00:00 2001 From: Thomas Leonard Date: Sun, 18 Nov 2007 11:51:17 +0000 Subject: [PATCH] Try to cope better with namespaces when merging XML documents. Still not quite right, but better. --- merge.py | 11 ++++++++--- tests/local-req.xml | 5 +++-- tests/testlocal.py | 22 ++++++++++++---------- xmltools.py | 38 +++++++++++++++++++++++++++++++++++++- 4 files changed, 60 insertions(+), 16 deletions(-) diff --git a/merge.py b/merge.py index 00d8632..72344bf 100644 --- a/merge.py +++ b/merge.py @@ -22,7 +22,9 @@ class Context: node = impl while True: for name, value in node.attributes.itemsNS(): - if name not in self.attribs: + if name[0] == XMLNS_NAMESPACE: + xmltools.register_namespace(value, name[1]) + elif name not in self.attribs: self.attribs[name] = value if node.nodeName == 'group': # We don't care about inside ; they'll get copied over anyway @@ -91,6 +93,8 @@ def score_subset(group, impl): # Score result so we get groups that have all the same requires first, then ones with all the same attribs return (1, len(group.requires), len(group.attribs)) +# Note: the namespace stuff isn't quite right yet. +# Might get conflicts if both documents use the same prefix for different things. def merge(data, local): local_doc = minidom.parse(local) master_doc = minidom.parseString(data) @@ -136,10 +140,11 @@ def merge(data, local): # Attributes might have been set on a parent group; move to the impl for name in new_impl_context.attribs: #print "Set", name, value - new_impl.setAttributeNS(name[0], name[1], new_impl_context.attribs[name]) + xmltools.add_attribute_ns(new_impl, name[0], name[1], new_impl_context.attribs[name]) for name, value in new_impl.attributes.itemsNS(): - if name in group_context.attribs and group_context.attribs[name] == value: + if name[0] == XMLNS_NAMESPACE or \ + (name in group_context.attribs and group_context.attribs[name] == value): #print "Deleting duplicate attribute", name, value new_impl.removeAttributeNS(name[0], name[1]) diff --git a/tests/local-req.xml b/tests/local-req.xml index e0e774c..f9c0232 100644 --- a/tests/local-req.xml +++ b/tests/local-req.xml @@ -1,5 +1,6 @@ - + hello prints hello @@ -7,7 +8,7 @@ http://test/hello.html - + diff --git a/tests/testlocal.py b/tests/testlocal.py index 56fc1c3..0cde7ac 100755 --- a/tests/testlocal.py +++ b/tests/testlocal.py @@ -60,7 +60,7 @@ class TestLocal(unittest.TestCase): assert len(master.implementations) == 2 def testMergeGroup(self): - master = parse(tap(merge.merge(header + "\n \n " + footer, local_file))) + master = parse(merge.merge(header + "\n \n " + footer, local_file)) assert master.uri == 'http://test/hello.xml', master assert len(master.implementations) == 2 assert master.implementations['sha1=002'].requires == [] @@ -73,14 +73,16 @@ class TestLocal(unittest.TestCase): assert len(deps) == 1 assert deps[0].interface == 'http://foo', deps[0] + assert master.implementations['sha1=003'].metadata['http://mynamespace/foo bob'] == 'bob' + def testNotSubset(self): - master = parse(tap(merge.merge(header + "\n \n " + footer, local_file))) + master = parse(merge.merge(header + "\n \n " + footer, local_file)) assert master.uri == 'http://test/hello.xml', master assert len(master.implementations) == 2 assert master.implementations['sha1=123'].metadata.get('a', None) == 'a' assert master.implementations['sha1=002'].metadata.get('a', None) == None - master = parse(tap(merge.merge(header + """\n + master = parse(merge.merge(header + """\n @@ -90,7 +92,7 @@ class TestLocal(unittest.TestCase): - """ + footer, local_file_req))) + """ + footer, local_file_req)) assert len(master.implementations['sha1=001'].requires[0].restrictions) == 1 assert len(master.implementations['sha1=003'].requires[0].restrictions) == 0 @@ -100,14 +102,14 @@ class TestLocal(unittest.TestCase): assert master.implementations['sha1=003'].main == 'hello' def testMergeBest(self): - master_xml = tap(merge.merge(header + """\n + master_xml = merge.merge(header + """\n - """ + footer, local_file_req)) + """ + footer, local_file_req) master = parse(master_xml) assert master.uri == 'http://test/hello.xml', master assert len(master.implementations) == 3 @@ -118,14 +120,14 @@ class TestLocal(unittest.TestCase): assert len(minidom.parseString(master_xml).documentElement.getElementsByTagNameNS(XMLNS_IFACE, 'group')) == 2 # Again, but with the groups the other way around - master_xml = tap(merge.merge(header + """\n + master_xml = merge.merge(header + """\n - """ + footer, local_file_req)) + """ + footer, local_file_req) master = parse(master_xml) assert master.uri == 'http://test/hello.xml', master assert len(master.implementations) == 3 @@ -146,12 +148,12 @@ class TestLocal(unittest.TestCase): assert ctx.attribs[(None, 'id')] == 'sha1=001' assert ctx.attribs[(None, 'version')] == '1' - ctx = get_context("") + ctx = get_context("") assert ctx.attribs[(None, 'id')] == 'sha1=001' assert ctx.attribs[(None, 'version')] == '1' assert ctx.attribs[(None, 't')] == 'r' assert ctx.attribs[(None, 'x')] == '1' - assert ctx.attribs[('foo', 'z')] == '2' + assert ctx.attribs[('yns', 'z')] == '2' suite = unittest.makeSuite(TestLocal) if __name__ == '__main__': diff --git a/xmltools.py b/xmltools.py index 50d123b..556a385 100644 --- a/xmltools.py +++ b/xmltools.py @@ -1,4 +1,4 @@ -from xml.dom import Node, minidom +from xml.dom import Node, minidom, XMLNS_NAMESPACE XMLNS_INTERFACE = "http://zero-install.sourceforge.net/2004/injector/interface" XMLNS_COMPILE = "http://zero-install.sourceforge.net/2006/namespaces/0compile" @@ -137,3 +137,39 @@ def set_or_remove(element, attr_name, value): element.setAttribute(attr_name, value) elif element.hasAttribute(attr_name): element.removeAttribute(attr_name) + +namespace_prefixes = {} # Namespace -> prefix + +def register_namespace(namespace, prefix = None): + """Return the prefix to use for a namespace. + If none is registered, create a new one based on the suggested prefix. + @param namespace: namespace to register / query + @param prefix: suggested prefix + @return: the actual prefix + """ + existing_prefix = namespace_prefixes.get(namespace, None) + if existing_prefix: + return existing_prefix + + if not prefix: + prefix = 'ns' + + # Find a variation on 'prefix' that isn't used yet, if necessary + orig_prefix = prefix + n = 0 + while prefix in namespace_prefixes.values(): + print "Prefix %s already in %s, not %s" % (prefix, namespace_prefixes, namespace) + n += 1 + prefix = orig_prefix + str(n) + namespace_prefixes[namespace] = prefix + + return prefix + +def add_attribute_ns(element, uri, name, value): + """Set an attribute, giving it the correct prefix or namespace declarations needed.""" + if not uri: + element.setAttributeNS(None, name, value) + else: + prefix = register_namespace(uri) + element.setAttributeNS(uri, '%s:%s' % (prefix, name), value) + element.ownerDocument.documentElement.setAttributeNS(XMLNS_NAMESPACE, 'xmlns:' + prefix, uri) -- 2.11.4.GIT