Fixed missing namespace attribute problem
[0publish.git] / xmltools.py
blobd28f9e8365350efa2f807265282b10dc97545cc4
1 from xml.dom import Node, XMLNS_NAMESPACE
3 XMLNS_INTERFACE = "http://zero-install.sourceforge.net/2004/injector/interface"
4 XMLNS_COMPILE = "http://zero-install.sourceforge.net/2006/namespaces/0compile"
6 def data(node):
7 """Return all the text directly inside this DOM Node."""
8 return ''.join([text.nodeValue for text in node.childNodes
9 if text.nodeType == Node.TEXT_NODE])
11 def set_data(elem, value):
12 """Replace all children of 'elem' with a single text node containing 'value'"""
13 for node in elem.childNodes:
14 elem.removeChild(node)
15 if value:
16 text = elem.ownerDocument.createTextNode(value)
17 elem.appendChild(text)
19 def indent_of(x):
20 """If x's previous sibling is whitespace, return its length. Otherwise, return 0."""
21 indent = x.previousSibling
22 if indent and indent.nodeType == Node.TEXT_NODE:
23 spaces = indent.nodeValue.split('\n')[-1]
24 if spaces.strip() == '':
25 return len(spaces)
26 return 0
28 def insert_before(new, next):
29 indent_depth = indent_of(next)
30 new_parent = next.parentNode
32 prev = next.previousSibling
33 if prev and prev.nodeType == Node.TEXT_NODE:
34 next = prev
36 new_parent.insertBefore(new, next)
37 if indent_depth:
38 text = new_parent.ownerDocument.createTextNode('\n' + (' ' * indent_depth))
39 new_parent.insertBefore(text, new)
41 def insert_after(new, prev):
42 indent_depth = indent_of(prev)
43 new_parent = prev.parentNode
45 if prev.nextSibling:
46 new_parent.insertBefore(new, prev.nextSibling)
47 else:
48 new_parent.appendChild(new, new_parent)
50 if indent_depth:
51 text = new_parent.ownerDocument.createTextNode('\n' + (' ' * indent_depth))
52 new_parent.insertBefore(text, new)
54 def insert_element(new, parent, before = []):
55 indent = indent_of(parent) + 2 # Default indent
56 last_element = None
57 for x in parent.childNodes:
58 if x.nodeType == Node.ELEMENT_NODE:
59 indent = indent or indent_of(x)
60 if x.localName in before:
61 parent.insertBefore(new, last_element.nextSibling)
62 break
63 last_element = x
64 else:
65 if last_element:
66 parent.insertBefore(new, last_element.nextSibling)
67 else:
68 had_children = bool(list(child_elements(parent)))
69 parent.appendChild(new)
70 if not had_children:
71 final_indent = '\n' + (' ' * indent_of(parent))
72 parent.appendChild(parent.ownerDocument.createTextNode(final_indent))
73 if indent:
74 indent_text = parent.ownerDocument.createTextNode('\n' + (' ' * indent))
75 parent.insertBefore(indent_text, new)
77 def create_element(parent, name, uri = XMLNS_INTERFACE, before = []):
78 """Create a new child element with the given name.
79 Add it as far down the list of children as possible, but before
80 any element in the 'before' set. Indent it sensibly."""
81 new = parent.ownerDocument.createElementNS(uri, name)
82 insert_element(new, parent, before)
83 return new
85 def remove_element(elem):
86 """Remove 'elem' and any whitespace before it."""
87 parent = elem.parentNode
88 prev = elem.previousSibling
89 if prev and prev.nodeType == Node.TEXT_NODE:
90 if prev.nodeValue.strip() == '':
91 parent.removeChild(prev)
92 parent.removeChild(elem)
94 whitespace = []
95 for x in parent.childNodes:
96 if x.nodeType != Node.TEXT_NODE: return
97 if x.nodeValue.strip(): return
98 whitespace.append(x)
100 # Nothing but white-space left
101 for w in whitespace:
102 parent.removeChild(w)
104 def format_para(para):
105 """Turn new-lines into spaces, removing any blank lines."""
106 lines = [l.strip() for l in para.split('\n')]
107 return ' '.join(filter(None, lines))
109 def attrs_match(elem, attrs):
110 for x in attrs:
111 if not elem.hasAttribute(x): return False
112 if elem.getAttribute(x) != attrs[x]: return False
113 return True
115 def child_elements(parent):
116 """Yield all direct child elements."""
117 for x in parent.childNodes:
118 if x.nodeType == Node.ELEMENT_NODE:
119 yield x
121 def children(parent, localName, uri = XMLNS_INTERFACE, attrs = {}):
122 """Yield all direct child elements with this name and attributes."""
123 for x in parent.childNodes:
124 if x.nodeType == Node.ELEMENT_NODE:
125 if x.nodeName == localName and x.namespaceURI == uri and attrs_match(x, attrs):
126 yield x
128 def singleton_text(parent, localName, uri = XMLNS_INTERFACE):
129 """Return the text of the first child element with this name, or None
130 if there aren't any."""
131 elements = list(children(parent, localName, uri))
132 if elements:
133 return data(elements[0])
135 def set_or_remove(element, attr_name, value):
136 if value:
137 element.setAttribute(attr_name, value)
138 elif element.hasAttribute(attr_name):
139 element.removeAttribute(attr_name)
141 namespace_prefixes = {} # Namespace -> prefix
143 def register_namespace(namespace, prefix = None):
144 """Return the prefix to use for a namespace.
145 If none is registered, create a new one based on the suggested prefix.
146 @param namespace: namespace to register / query
147 @param prefix: suggested prefix
148 @return: the actual prefix
150 if namespace == XMLNS_INTERFACE:
151 return None
152 existing_prefix = namespace_prefixes.get(namespace, None)
153 if existing_prefix:
154 return existing_prefix
156 if not prefix:
157 prefix = 'ns'
159 # Find a variation on 'prefix' that isn't used yet, if necessary
160 orig_prefix = prefix
161 n = 0
162 while prefix in namespace_prefixes.values():
163 #print "Prefix %s already in %s, not %s" % (prefix, namespace_prefixes, namespace)
164 n += 1
165 prefix = orig_prefix + str(n)
166 namespace_prefixes[namespace] = prefix
168 return prefix
170 def add_attribute_ns(element, uri, name, value):
171 """Set an attribute, giving it the correct prefix or namespace declarations needed."""
172 if not uri:
173 element.setAttributeNS(None, name, value)
174 else:
175 prefix = register_namespace(uri)
176 element.setAttributeNS(uri, '%s:%s' % (prefix, name), value)
177 element.ownerDocument.documentElement.setAttributeNS(XMLNS_NAMESPACE, 'xmlns:' + prefix, uri)
179 def import_node(target_doc, source_node):
180 """Import a node for a new document, fixing up namespace prefixes as we go."""
181 target_root = target_doc.documentElement
183 new_node = target_doc.importNode(source_node, True)
184 def fixup(elem):
185 elem.prefix = register_namespace(elem.namespaceURI, elem.prefix)
186 if elem.prefix:
187 elem.tagName = elem.nodeName = '%s:%s' % (elem.prefix, elem.localName)
188 target_root.setAttributeNS(XMLNS_NAMESPACE, 'xmlns:' + elem.prefix, elem.namespaceURI)
190 for (uri, name), value in list(elem.attributes.itemsNS()):
191 if uri == XMLNS_NAMESPACE:
192 elem.removeAttributeNS(uri, name)
193 elif uri:
194 new_prefix = register_namespace(uri)
195 target_root.setAttributeNS(XMLNS_NAMESPACE, 'xmlns:' + new_prefix, uri)
196 elem.removeAttributeNS(uri, name)
197 elem.setAttributeNS(uri, '%s:%s' % (new_prefix, name), value)
199 for child in elem.childNodes:
200 if child.nodeType == Node.ELEMENT_NODE:
201 fixup(child)
203 fixup(new_node)
204 return new_node