#!/usr/bin/env python # # Copyright (C) 2006-2007 Async Open Source # Henrique Romano # Johan Dahlin # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # This is a script that can convert libglade files to the new gtkbuilder format # # TODO: # GtkComboBox.items -> GtkListStore # GtkTextView.text -> GtkTextBuffer # Toolbars import sys from xml.dom import minidom, Node def get_child_nodes(node): nodes = [] for child in node.childNodes: if child.nodeType == Node.TEXT_NODE: continue if child.tagName != 'child': continue nodes.append(child) return nodes def get_object_properties(node): properties = {} for child in node.childNodes: if child.nodeType == Node.TEXT_NODE: continue if child.tagName != 'property': continue value = child.childNodes[0].data properties[child.getAttribute('name')] = value return properties def get_object_node(child_node): assert child_node.tagName == 'child' nodes = [] for node in child_node.childNodes: if node.nodeType == Node.TEXT_NODE: continue if node.tagName == 'object': nodes.append(node) assert len(nodes) == 1, nodes return nodes[0] class GtkBuilderConverter(object): # # Public API # def parse_file(self, file): self._dom = minidom.parse(file) self._parse() def parse_buffer(self, buffer): self._dom = minidom.parseString(buffer) self._parse() def to_xml(self): return self._dom.toprettyxml("", "") # # Private # def _get_widget(self, doc, name): result = self._get_widgets_by_attr(doc, "id", name) if len(result) > 1: raise ValueError( "It is not possible to have more than one " "widget with the same id (`%s')" % name) elif len(result) == 1: return result[0] return None def _get_widgets_by_attr(self, doc, attribute, value): return [w for w in self._dom.getElementsByTagName("object") if w.getAttribute(attribute) == value] def _create_object(self, obj_class, obj_id, **properties): obj = self._dom.createElement('object') obj.setAttribute('class', obj_class) obj.setAttribute('id', obj_id) for name, value in properties.items(): prop = self._dom.createElement('property') prop.setAttribute('name', name) prop.appendChild(self._dom.createTextNode(value)) obj.appendChild(prop) return obj def _get_property(self, node, property_name): properties = get_object_properties(node) return properties.get(property_name) def _parse(self): glade_iface = self._dom.getElementsByTagName("glade-interface") assert glade_iface, ("Badly formed XML, there is " "no tag.") glade_iface[0].tagName = 'interface' self._interface = glade_iface[0] # Strip requires requires = self._dom.getElementsByTagName("requires") for require in requires: require.parentNode.childNodes.remove(require) for child in self._dom.getElementsByTagName("accessibility"): child.parentNode.removeChild(child) for node in self._dom.getElementsByTagName("widget"): node.tagName = "object" for node in self._dom.getElementsByTagName("object"): self._convert(node.getAttribute("class"), node) # Convert Gazpachos UI tag for node in self._dom.getElementsByTagName("ui"): self._convert_ui(node) def _convert(self, klass, node): if klass == 'GtkNotebook': self._packing_prop_to_child_attr(node, "type", "tab") elif klass in ['GtkExpander', 'GtkFrame']: self._packing_prop_to_child_attr( node, "type", "label_item", "label") elif klass == "GtkMenuBar": uimgr = node.ownerDocument.createElement('object') uimgr.setAttribute('class', 'GtkUIManager') uimgr.setAttribute('id', 'uimanager1') self._interface.childNodes.insert(0, uimgr) self._convert_menubar(uimgr, node) self._default_widget_converter(node) def _default_widget_converter(self, node): klass = node.getAttribute("class") for prop in node.getElementsByTagName("property"): if prop.parentNode is not node: continue prop_name = prop.getAttribute("name") if prop_name == "sizegroup": self._convert_sizegroup(node, prop) elif prop_name == "tooltip" and klass != "GtkAction": prop.setAttribute("name", "tooltip-text") elif prop_name in ["response_id", 'response-id']: object_id = node.getAttribute('id') response = prop.childNodes[0].data self._convert_dialog_response(node, object_id, response) prop.parentNode.removeChild(prop) def _convert_menubar(self, uimgr, node): menubar = self._dom.createElement('menubar') menubar.setAttribute('name', node.getAttribute('id')) node.setAttribute('constructor', uimgr.getAttribute('id')) for child in get_child_nodes(node): obj_node = get_object_node(child) self._convert_menuitem(uimgr, menubar, obj_node) child.removeChild(obj_node) child.parentNode.removeChild(child) ui = self._dom.createElement('ui') uimgr.appendChild(ui) ui.appendChild(menubar) def _convert_menuitem(self, uimgr, menubar, obj_node): children = get_child_nodes(obj_node) name = 'menuitem' if children: child_node = children[0] menu_node = get_object_node(child_node) # Can be GtkImage, which will take care of later. if menu_node.getAttribute('class') == 'GtkMenu': name = 'menu' object_class = obj_node.getAttribute('class') if object_class in ['GtkMenuItem', 'GtkImageMenuItem']: menu = self._dom.createElement(name) elif object_class == 'GtkSeparatorMenuItem': menu = self._dom.createElement('sep') else: raise NotImplementedError(object_class) menu.setAttribute('name', obj_node.getAttribute('id')) menu.setAttribute('action', obj_node.getAttribute('id')) menubar.appendChild(menu) self._add_action_from_menuitem(uimgr, obj_node) if children: for child in get_child_nodes(menu_node): obj_node = get_object_node(child) self._convert_menuitem(uimgr, menu, obj_node) child.removeChild(obj_node) child.parentNode.removeChild(child) def _add_action_from_menuitem(self, uimgr, node): properties = {} object_class = node.getAttribute('class') object_id = node.getAttribute('id') if object_class == 'GtkImageMenuItem': name = 'GtkAction' children = get_child_nodes(node) if (children and children[0].getAttribute('internal-child') == 'image'): image = get_object_node(children[0]) properties['stock_id'] = self._get_property(image, 'stock') elif object_class == 'GtkMenuItem': name = 'GtkAction' label = self._get_property(node, 'label') if label is not None: properties['label'] = label elif object_class == 'GtkSeparatorMenuItem': return else: raise NotImplementedError(object_class) if self._get_property(node, 'use_stock') == 'True': properties['stock_id'] = self._get_property(node, 'label') properties['name'] = object_id action = self._create_object(name, object_id, **properties) if not uimgr.childNodes: child = self._dom.createElement('child') uimgr.appendChild(child) group = self._create_object('GtkActionGroup', 'actiongroup1') child.appendChild(group) else: group = uimgr.childNodes[0].childNodes[0] child = self._dom.createElement('child') group.appendChild(child) child.appendChild(action) def _convert_sizegroup(self, node, prop): # This is Gazpacho only node.removeChild(prop) obj = self._get_widget(doc, prop.childNodes[0].data) if obj is None: widgets = self._get_widgets_by_attr(doc, "class", "GtkSizeGroup") if widgets: obj = widgets[-1] else: obj = self._dom.createElement("object") obj.setAttribute("class", "GtkSizeGroup") obj.setAttribute("id", "sizegroup1") self._interface.insertBefore( obj, self._interface.childNodes[0]) widgets = obj.getElementsByTagName("widgets") if widgets: assert len(widgets) == 1 widgets = widgets[0] else: widgets = self._dom.createElement("widgets") obj.appendChild(widgets) member = self._dom.createElement("widget") member.setAttribute("name", node.getAttribute("id")) widgets.appendChild(member) def _convert_dialog_response(self, node, object_name, response): # 1) Get parent dialog node while True: if (node.tagName == 'object' and node.getAttribute('class') == 'GtkDialog'): dialog = node break node = node.parentNode assert node # 2) Get dialogs action-widgets tag, create if not found for child in dialog.childNodes: if child.nodeType == Node.TEXT_NODE: continue if child.tagName == 'action-widgets': actions = child break else: actions = self._dom.createElement("action-widgets") dialog.appendChild(actions) # 3) Add action-widget tag for the response action = self._dom.createElement("action-widget") action.setAttribute("response", response) action.appendChild(self._dom.createTextNode(object_name)) actions.appendChild(action) def _packing_prop_to_child_attr(self, node, prop_name, prop_val, attr_val=None): for child in node.getElementsByTagName("child"): packing_props = [p for p in child.childNodes if p.nodeName == "packing"] if not packing_props: continue assert len(packing_props) == 1 packing_prop = packing_props[0] properties = packing_prop.getElementsByTagName("property") for prop in properties: if (prop.getAttribute("name") != prop_name or prop.childNodes[0].data != prop_val): continue packing_prop.removeChild(prop) child.setAttribute(prop_name, attr_val or prop_val) if len(properties) == 1: child.removeChild(packing_prop) def _convert_ui(self, node): cdata = node.childNodes[0] data = cdata.toxml().strip() if not data.startswith(""): return data = data[9:-3] child = minidom.parseString(data).childNodes[0] nodes = child.childNodes[:] for child_node in nodes: node.appendChild(child_node) node.removeChild(cdata) if not node.hasAttribute("id"): return # Updating references made by widgets parent_id = node.parentNode.getAttribute("id") for widget in self._get_widgets_by_attr(node.ownerDocument, "constructor", node.getAttribute("id")): widget.getAttributeNode("constructor").value = parent_id node.removeAttribute("id") def main(): if len(sys.argv) < 2: print "Usage: %s filename.glade" % (sys.argv[0],) return conv = GtkBuilderConverter() conv.parse_file(sys.argv[1]) print conv.to_xml() if __name__ == "__main__": main()