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"
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
)
16 text
= elem
.ownerDocument
.createTextNode(value
)
17 elem
.appendChild(text
)
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() == '':
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
:
36 new_parent
.insertBefore(new
, next
)
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
46 new_parent
.insertBefore(new
, prev
.nextSibling
)
48 new_parent
.appendChild(new
, new_parent
)
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
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
)
66 parent
.insertBefore(new
, last_element
.nextSibling
)
68 had_children
= bool(list(child_elements(parent
)))
69 parent
.appendChild(new
)
71 final_indent
= '\n' + (' ' * indent_of(parent
))
72 parent
.appendChild(parent
.ownerDocument
.createTextNode(final_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
)
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
)
95 for x
in parent
.childNodes
:
96 if x
.nodeType
!= Node
.TEXT_NODE
: return
97 if x
.nodeValue
.strip(): return
100 # Nothing but white-space left
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
):
111 if not elem
.hasAttribute(x
): return False
112 if elem
.getAttribute(x
) != attrs
[x
]: return False
115 def child_elements(parent
):
116 """Yield all direct child elements."""
117 for x
in parent
.childNodes
:
118 if x
.nodeType
== Node
.ELEMENT_NODE
:
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
):
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
))
133 return data(elements
[0])
135 def set_or_remove(element
, attr_name
, 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 existing_prefix
= namespace_prefixes
.get(namespace
, None)
152 return existing_prefix
157 # Find a variation on 'prefix' that isn't used yet, if necessary
160 while prefix
in namespace_prefixes
.values():
161 print "Prefix %s already in %s, not %s" % (prefix
, namespace_prefixes
, namespace
)
163 prefix
= orig_prefix
+ str(n
)
164 namespace_prefixes
[namespace
] = prefix
168 def add_attribute_ns(element
, uri
, name
, value
):
169 """Set an attribute, giving it the correct prefix or namespace declarations needed."""
171 element
.setAttributeNS(None, name
, value
)
173 prefix
= register_namespace(uri
)
174 element
.setAttributeNS(uri
, '%s:%s' % (prefix
, name
), value
)
175 element
.ownerDocument
.documentElement
.setAttributeNS(XMLNS_NAMESPACE
, 'xmlns:' + prefix
, uri
)