3 # Copyright (C) 2006-2007 Async Open Source
4 # Henrique Romano <henrique@async.com.br>
5 # Johan Dahlin <jdahlin@async.com.br>
7 # This program is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU Lesser General Public License
9 # as published by the Free Software Foundation; either version 2
10 # of the License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU Lesser General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22 # GtkComboBox.items -> GtkListStore
23 # GtkTextView.text -> GtkTextBuffer
26 """Usage: gtk-builder-convert [OPTION] [INPUT] [OUTPUT]
27 Converts Glade files into XML files which can be loaded with GtkBuilder.
30 -w, --skip-windows Convert everything bug GtkWindow subclasses.
31 -h, --help display this help and exit
33 When OUTPUT is -, write to standard input.
36 gtk-builder-convert preference.glade preferences.ui
38 Report bugs to http://bugzilla.gnome.org/."""
44 from xml.dom import minidom, Node
46 WINDOWS = ['GtkWindow',
48 'GtkFileChooserDialog',
51 # The subprocess is only available in Python 2.4+
58 def get_child_nodes(node):
60 for child in node.childNodes:
61 if child.nodeType == Node.TEXT_NODE:
63 if child.tagName != 'child':
68 def get_object_properties(node):
70 for child in node.childNodes:
71 if child.nodeType == Node.TEXT_NODE:
73 if child.tagName != 'property':
75 value = child.childNodes[0].data
76 properties[child.getAttribute('name')] = value
79 def get_object_node(child_node):
80 assert child_node.tagName == 'child'
82 for node in child_node.childNodes:
83 if node.nodeType == Node.TEXT_NODE:
85 if node.tagName == 'object':
87 assert len(nodes) == 1, nodes
90 class GtkBuilderConverter(object):
92 def __init__(self, skip_windows):
93 self.skip_windows = skip_windows
99 def parse_file(self, file):
100 self._dom = minidom.parse(file)
103 def parse_buffer(self, buffer):
104 self._dom = minidom.parseString(buffer)
108 xml = self._dom.toprettyxml("", "")
109 return xml.encode('utf-8')
115 def _get_widget(self, name):
116 result = self._get_widgets_by_attr("id", name)
119 "It is not possible to have more than one "
120 "widget with the same id (`%s')" % name)
121 elif len(result) == 1:
125 def _get_widgets_by_attr(self, attribute, value):
126 return [w for w in self._dom.getElementsByTagName("object")
127 if w.getAttribute(attribute) == value]
129 def _create_object(self, obj_class, obj_id, **properties):
130 obj = self._dom.createElement('object')
131 obj.setAttribute('class', obj_class)
132 obj.setAttribute('id', obj_id)
133 for name, value in properties.items():
134 prop = self._dom.createElement('property')
135 prop.setAttribute('name', name)
136 prop.appendChild(self._dom.createTextNode(value))
137 obj.appendChild(prop)
140 def _get_property(self, node, property_name):
141 properties = get_object_properties(node)
142 return properties.get(property_name)
145 glade_iface = self._dom.getElementsByTagName("glade-interface")
146 assert glade_iface, ("Badly formed XML, there is "
147 "no <glade-interface> tag.")
148 glade_iface[0].tagName = 'interface'
149 self._interface = glade_iface[0]
151 # Remove glade-interface doc type
152 for node in self._dom.childNodes:
153 if node.nodeType == Node.DOCUMENT_TYPE_NODE:
154 if node.name == 'glade-interface':
155 self._dom.removeChild(node)
158 requires = self._dom.getElementsByTagName("requires")
159 for require in requires:
160 require.parentNode.childNodes.remove(require)
162 for child in self._dom.getElementsByTagName("accessibility"):
163 child.parentNode.removeChild(child)
165 for node in self._dom.getElementsByTagName("widget"):
166 node.tagName = "object"
168 for node in self._dom.getElementsByTagName("object"):
169 self._convert(node.getAttribute("class"), node)
171 # Convert Gazpachos UI tag
172 for node in self._dom.getElementsByTagName("ui"):
173 self._convert_ui(node)
175 def _convert(self, klass, node):
176 if klass == 'GtkNotebook':
177 self._packing_prop_to_child_attr(node, "type", "tab")
178 elif klass in ['GtkExpander', 'GtkFrame']:
179 self._packing_prop_to_child_attr(
180 node, "type", "label_item", "label")
181 elif klass == "GtkMenuBar":
182 if node.hasAttribute('constructor'):
183 uimgr = self._get_widget('uimanager')
185 uimgr = node.ownerDocument.createElement('object')
186 uimgr.setAttribute('class', 'GtkUIManager')
187 uimgr.setAttribute('id', 'uimanager1')
188 self._interface.childNodes.insert(0, uimgr)
189 self._convert_menubar(uimgr, node)
190 elif klass in WINDOWS and self.skip_windows:
191 self._remove_window(node)
193 self._default_widget_converter(node)
195 def _default_widget_converter(self, node):
196 klass = node.getAttribute("class")
197 for prop in node.getElementsByTagName("property"):
198 if prop.parentNode is not node:
200 prop_name = prop.getAttribute("name")
201 if prop_name == "sizegroup":
202 self._convert_sizegroup(node, prop)
203 elif prop_name == "tooltip" and klass != "GtkAction":
204 prop.setAttribute("name", "tooltip-text")
205 elif prop_name in ["response_id", 'response-id']:
206 # It does not make sense to convert responses when
207 # we're not going to output dialogs
208 if self.skip_windows:
210 object_id = node.getAttribute('id')
211 response = prop.childNodes[0].data
212 self._convert_dialog_response(node, object_id, response)
213 prop.parentNode.removeChild(prop)
215 def _remove_window(self, node):
216 object_node = get_object_node(get_child_nodes(node)[0])
217 parent = node.parentNode
218 parent.removeChild(node)
219 parent.appendChild(object_node)
221 def _convert_menubar(self, uimgr, node):
222 menubar = self._dom.createElement('menubar')
223 menubar.setAttribute('name', node.getAttribute('id'))
224 node.setAttribute('constructor', uimgr.getAttribute('id'))
226 for child in get_child_nodes(node):
227 obj_node = get_object_node(child)
228 self._convert_menuitem(uimgr, menubar, obj_node)
229 child.removeChild(obj_node)
230 child.parentNode.removeChild(child)
232 ui = self._dom.createElement('ui')
233 uimgr.appendChild(ui)
235 ui.appendChild(menubar)
237 def _convert_menuitem(self, uimgr, menubar, obj_node):
238 children = get_child_nodes(obj_node)
241 child_node = children[0]
242 menu_node = get_object_node(child_node)
243 # Can be GtkImage, which will take care of later.
244 if menu_node.getAttribute('class') == 'GtkMenu':
247 object_class = obj_node.getAttribute('class')
248 if object_class in ['GtkMenuItem', 'GtkImageMenuItem']:
249 menu = self._dom.createElement(name)
250 elif object_class == 'GtkSeparatorMenuItem':
251 menu = self._dom.createElement('sep')
253 raise NotImplementedError(object_class)
254 menu.setAttribute('name', obj_node.getAttribute('id'))
255 menu.setAttribute('action', obj_node.getAttribute('id'))
256 menubar.appendChild(menu)
257 self._add_action_from_menuitem(uimgr, obj_node)
259 for child in get_child_nodes(menu_node):
260 obj_node = get_object_node(child)
261 self._convert_menuitem(uimgr, menu, obj_node)
262 child.removeChild(obj_node)
263 child.parentNode.removeChild(child)
265 def _add_action_from_menuitem(self, uimgr, node):
267 object_class = node.getAttribute('class')
268 object_id = node.getAttribute('id')
269 if object_class == 'GtkImageMenuItem':
271 children = get_child_nodes(node)
273 children[0].getAttribute('internal-child') == 'image'):
274 image = get_object_node(children[0])
275 properties['stock_id'] = self._get_property(image, 'stock')
276 elif object_class == 'GtkMenuItem':
278 label = self._get_property(node, 'label')
279 if label is not None:
280 properties['label'] = label
281 elif object_class == 'GtkSeparatorMenuItem':
284 raise NotImplementedError(object_class)
286 if self._get_property(node, 'use_stock') == 'True':
287 properties['stock_id'] = self._get_property(node, 'label')
288 properties['name'] = object_id
289 action = self._create_object(name,
292 if not uimgr.childNodes:
293 child = self._dom.createElement('child')
294 uimgr.appendChild(child)
296 group = self._create_object('GtkActionGroup', 'actiongroup1')
297 child.appendChild(group)
299 group = uimgr.childNodes[0].childNodes[0]
301 child = self._dom.createElement('child')
302 group.appendChild(child)
303 child.appendChild(action)
305 def _convert_sizegroup(self, node, prop):
306 # This is Gazpacho only
307 node.removeChild(prop)
308 obj = self._get_widget(prop.childNodes[0].data)
310 widgets = self._get_widgets_by_attr("class", "GtkSizeGroup")
314 obj = self._dom.createElement("object")
315 obj.setAttribute("class", "GtkSizeGroup")
316 obj.setAttribute("id", "sizegroup1")
317 self._interface.insertBefore(
318 obj, self._interface.childNodes[0])
320 widgets = obj.getElementsByTagName("widgets")
322 assert len(widgets) == 1
325 widgets = self._dom.createElement("widgets")
326 obj.appendChild(widgets)
328 member = self._dom.createElement("widget")
329 member.setAttribute("name", node.getAttribute("id"))
330 widgets.appendChild(member)
332 def _convert_dialog_response(self, node, object_name, response):
333 # 1) Get parent dialog node
335 if (node.tagName == 'object' and
336 node.getAttribute('class') == 'GtkDialog'):
339 node = node.parentNode
342 # 2) Get dialogs action-widgets tag, create if not found
343 for child in dialog.childNodes:
344 if child.nodeType == Node.TEXT_NODE:
346 if child.tagName == 'action-widgets':
350 actions = self._dom.createElement("action-widgets")
351 dialog.appendChild(actions)
353 # 3) Add action-widget tag for the response
354 action = self._dom.createElement("action-widget")
355 action.setAttribute("response", response)
356 action.appendChild(self._dom.createTextNode(object_name))
357 actions.appendChild(action)
359 def _packing_prop_to_child_attr(self, node, prop_name, prop_val,
361 for child in node.getElementsByTagName("child"):
362 packing_props = [p for p in child.childNodes if p.nodeName == "packing"]
363 if not packing_props:
365 assert len(packing_props) == 1
366 packing_prop = packing_props[0]
367 properties = packing_prop.getElementsByTagName("property")
368 for prop in properties:
369 if (prop.getAttribute("name") != prop_name or
370 prop.childNodes[0].data != prop_val):
372 packing_prop.removeChild(prop)
373 child.setAttribute(prop_name, attr_val or prop_val)
374 if len(properties) == 1:
375 child.removeChild(packing_prop)
377 def _convert_ui(self, node):
378 cdata = node.childNodes[0]
379 data = cdata.toxml().strip()
380 if not data.startswith("<![CDATA[") or not data.endswith("]]>"):
383 child = minidom.parseString(data).childNodes[0]
384 nodes = child.childNodes[:]
385 for child_node in nodes:
386 node.appendChild(child_node)
387 node.removeChild(cdata)
388 if not node.hasAttribute("id"):
391 # Updating references made by widgets
392 parent_id = node.parentNode.getAttribute("id")
393 for widget in self._get_widgets_by_attr("constructor",
394 node.getAttribute("id")):
395 widget.getAttributeNode("constructor").value = parent_id
396 node.removeAttribute("id")
402 for directory in os.environ['PATH'].split(os.pathsep):
403 filename = os.path.join(directory, 'xmllint')
404 if os.path.exists(filename):
409 s = subprocess.Popen([filename, '--format', '-'],
410 stdin=subprocess.PIPE,
411 stdout=subprocess.PIPE)
412 s.stdin.write(output)
414 return s.stdout.read()
421 opts, args = getopt.getopt(args[1:], "hw",
422 ["help", "skip-windows"])
423 except getopt.GetoptError:
431 input_filename, output_filename = args
436 if o in ("-h", "--help"):
439 elif o in ("-w", "--skip-windows"):
442 conv = GtkBuilderConverter(skip_windows=skip_windows)
443 conv.parse_file(input_filename)
445 xml = _indent(conv.to_xml())
446 if output_filename == "-":
449 open(output_filename, 'w').write(xml)
450 print "Wrote", output_filename
454 if __name__ == "__main__":
455 sys.exit(main(sys.argv))