2 from xml
.dom
import minidom
, XMLNS_NAMESPACE
, Node
3 from zeroinstall
.injector
.namespaces
import XMLNS_IFACE
4 from zeroinstall
.injector
import model
, reader
5 from logging
import info
7 def childNodes(parent
, namespaceURI
, localName
= None):
8 for x
in parent
.childNodes
:
9 if x
.nodeType
!= Node
.ELEMENT_NODE
: continue
10 if x
.namespaceURI
!= namespaceURI
: continue
12 if localName
is None or x
.localName
== localName
:
16 def __init__(self
, impl
):
17 doc
= impl
.ownerDocument
23 for name
, value
in node
.attributes
.itemsNS():
24 if name
not in self
.attribs
:
25 self
.attribs
[name
] = value
26 if node
.nodeName
== 'group':
27 # We don't care about <requires> inside <implementation>; they'll get copied over anyway
28 for x
in childNodes(node
, XMLNS_IFACE
, 'requires'):
29 self
.requires
.append(x
)
30 node
= node
.parentNode
31 if node
.nodeName
!= 'group':
34 def find_impls(parent
):
35 """Return all <implementation> children, including those inside groups."""
36 for x
in childNodes(parent
, XMLNS_IFACE
):
37 if x
.localName
== 'implementation':
39 elif x
.localName
== 'group':
40 for y
in find_impls(x
):
43 def find_groups(parent
):
44 """Return all <group> children, including those inside other groups."""
45 for x
in childNodes(parent
, XMLNS_IFACE
, 'group'):
47 for y
in find_groups(x
):
50 def is_subset(group
, impl
):
51 for key
in group
.attribs
:
52 if key
not in impl
.attribs
.keys():
54 return False # Group sets an attribute the impl doesn't want
55 for g_req
in group
.requires
:
56 for i_req
in impl
.requires
:
57 if nodesEqual(g_req
, i_req
): break
59 return False # Group adds a requires that the impl doesn't want
62 def merge(data
, local
):
63 local_doc
= minidom
.parse(local
)
64 master_doc
= minidom
.parseString(data
)
66 # Merge each implementation in the local feed in turn (normally there will only be one)
67 for impl
in find_impls(local_doc
.documentElement
):
68 # 1. Get the context of the implementation to add. This is:
69 # - The set of its requirements
71 new_impl_context
= Context(impl
)
73 # 2. For each <group> in the master feed, see if it provides a compatible context:
74 # - A subset of the new implementation's requirements
75 # - A subset of the new implementation's attributes (names, not values)
76 # Choose the most compatible <group> (the root counts as a minimally compatible group)
78 best_group
= (0, master_doc
.documentElement
)
80 for group
in find_groups(master_doc
.documentElement
):
81 group_context
= Context(group
)
82 if is_subset(group_context
, new_impl_context
):
83 best_group
= (0, group
)
86 group_context
= Context(group
)
88 # If we have additional requirements, we'll need to create a subgroup and add them
89 if len(new_impl_context
.requires
) > len(group_context
.requires
):
90 subgroup
= master_doc
.createElementNS(XMLNS_IFACE
, 'group')
91 group
.appendChild(subgroup
)
93 group_context
= Context(group
)
94 for x
in new_impl_context
.requires
:
95 for y
in group_context
.requires
:
96 if nodesEqual(x
, y
): break
98 req
= master_doc
.importNode(x
, True)
100 group
.appendChild(req
)
102 new_impl
= master_doc
.importNode(impl
, True)
103 for name
, value
in new_impl
.attributes
.itemsNS():
104 if name
in group_context
.attribs
and group_context
.attribs
[name
] == value
:
105 #print "Deleting duplicate attribute", name, value
106 new_impl
.removeAttributeNS(name
[0], name
[1])
108 # Might have been on a parent group; move to the impl
109 #print "Set", name, value
110 new_impl
.setAttributeNS(name
[0], name
[1], value
)
112 group
.appendChild(master_doc
.createTextNode(' '))
113 group
.appendChild(new_impl
)
114 group
.appendChild(master_doc
.createTextNode('\n'))
116 return master_doc
.toxml()