]> Pileus Git - ~andy/gtk/blobdiff - gtk/gtk-builder-convert
Update Norwegian bokmål translation.
[~andy/gtk] / gtk / gtk-builder-convert
old mode 100644 (file)
new mode 100755 (executable)
index 7237c8f..a641b33
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright (C) 2006-2007 Async Open Source
+# Copyright (C) 2006-2008 Async Open Source
 #                         Henrique Romano <henrique@async.com.br>
 #                         Johan Dahlin <jdahlin@async.com.br>
 #
 # 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
 
+"""Usage: gtk-builder-convert [OPTION] [INPUT] [OUTPUT]
+Converts Glade files into XML files which can be loaded with GtkBuilder.
+The [INPUT] file is
+
+  -w, --skip-windows     Convert everything but GtkWindow subclasses.
+  -r, --root             Convert only widget named root and its children
+  -h, --help             display this help and exit
+
+When OUTPUT is -, write to standard output.
+
+Examples:
+  gtk-builder-convert preference.glade preferences.ui
+
+Report bugs to http://bugzilla.gnome.org/."""
+
+import getopt
+import os
 import sys
 
 from xml.dom import minidom, Node
 
+DIALOGS = ['GtkDialog',
+           'GtkFileChooserDialog',
+           'GtkMessageDialog']
+WINDOWS = ['GtkWindow'] + DIALOGS
+
+# The subprocess is only available in Python 2.4+
+try:
+    import subprocess
+    subprocess # pyflakes
+except ImportError:
+    subprocess = None
+
 def get_child_nodes(node):
+    assert node.tagName == 'object'
     nodes = []
     for child in node.childNodes:
-        if child.nodeType == Node.TEXT_NODE:
+        if child.nodeType != Node.ELEMENT_NODE:
             continue
         if child.tagName != 'child':
             continue
         nodes.append(child)
     return nodes
 
-def get_object_properties(node):
+def get_properties(node):
+    assert node.tagName == 'object'
     properties = {}
     for child in node.childNodes:
-        if child.nodeType == Node.TEXT_NODE:
+        if child.nodeType != Node.ELEMENT_NODE:
             continue
         if child.tagName != 'property':
             continue
@@ -50,19 +77,81 @@ def get_object_properties(node):
         properties[child.getAttribute('name')] = value
     return properties
 
+def get_property(node, property_name):
+    assert node.tagName == 'object'
+    properties = get_properties(node)
+    return properties.get(property_name)
+
+def get_property_node(node, property_name):
+    assert node.tagName == 'object'
+    properties = {}
+    for child in node.childNodes:
+        if child.nodeType != Node.ELEMENT_NODE:
+            continue
+        if child.tagName != 'property':
+            continue
+        if child.getAttribute('name') == property_name:
+            return child
+
+def get_signal_nodes(node):
+    assert node.tagName == 'object'
+    signals = []
+    for child in node.childNodes:
+        if child.nodeType != Node.ELEMENT_NODE:
+            continue
+        if child.tagName == 'signal':
+            signals.append(child)
+    return signals
+
+def get_property_nodes(node):
+    assert node.tagName == 'object'
+    properties = []
+    for child in node.childNodes:
+        if child.nodeType != Node.ELEMENT_NODE:
+            continue
+        # FIXME: handle comments
+        if child.tagName == 'property':
+            properties.append(child)
+    return properties
+
+def get_accelerator_nodes(node):
+    assert node.tagName == 'object'
+    accelerators = []
+    for child in node.childNodes:
+        if child.nodeType != Node.ELEMENT_NODE:
+            continue
+        if child.tagName == 'accelerator':
+            accelerators.append(child)
+    return accelerators
+
 def get_object_node(child_node):
-    assert child_node.tagName == 'child'
+    assert child_node.tagName == 'child', child_node
     nodes = []
     for node in child_node.childNodes:
-        if node.nodeType == Node.TEXT_NODE:
+        if node.nodeType != Node.ELEMENT_NODE:
             continue
         if node.tagName == 'object':
             nodes.append(node)
     assert len(nodes) == 1, nodes
     return nodes[0]
 
+def copy_properties(node, props, prop_dict):
+    assert node.tagName == 'object'
+    for prop_name in props:
+        child = get_property_node(node, prop_name)
+        if child is not None:
+            prop_dict[prop_name] = child
+
+    return node
+
 class GtkBuilderConverter(object):
 
+    def __init__(self, skip_windows, root):
+        self.skip_windows = skip_windows
+        self.root = root
+        self.root_objects = []
+        self.objects = {}
+
     #
     # Public API
     #
@@ -83,39 +172,67 @@ class GtkBuilderConverter(object):
     # Private
     #
 
-    def _get_widget(self, name):
-        result = self._get_widgets_by_attr("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, attribute, value):
+    def _get_object(self, name):
+        return self.objects.get(name)
+
+    def _get_objects_by_attr(self, 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):
+    def _create_object(self, obj_class, obj_id, template=None, properties=None):
+        """
+        Creates a new <object> tag.
+        Optionally a name template can be provided which will be used
+        to avoid naming collisions.
+        The properties dictionary can either contain string values or Node
+        values. If a node is provided the name of the node will be overridden
+        by the dictionary key.
+
+        @param obj_class: class of the object (class tag)
+        @param obj_id: identifier of the object (id tag)
+        @param template: name template to use, for example 'button'
+        @param properties: dictionary of properties
+        @type properties: string or Node.
+        @returns: Newly created node of the object
+        """
+        if template is not None:
+            count = 1
+            while True:
+                obj_id = template + str(count)
+                widget = self._get_object(obj_id)
+                if widget is None:
+                    break
+
+                count += 1
+
         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)
+        if properties:
+            for name, value in properties.items():
+                if isinstance(value, Node):
+                    # Reuse the node, so translatable and context still will be
+                    # set when converting nodes. See also #509153
+                    prop = value
+                else:
+                    prop = self._dom.createElement('property')
+                    prop.appendChild(self._dom.createTextNode(value))
+
+                prop.setAttribute('name', str(name))
+                obj.appendChild(prop)
+        self.objects[obj_id] = obj
         return obj
 
-    def _get_property(self, node, property_name):
-        properties = get_object_properties(node)
-        return properties.get(property_name)
+    def _create_root_object(self, obj_class, template, properties=None):
+        obj = self._create_object(obj_class, None, template, properties)
+        self.root_objects.append(obj)
+        return obj
 
     def _parse(self):
         glade_iface = self._dom.getElementsByTagName("glade-interface")
         assert glade_iface, ("Badly formed XML, there is "
                              "no <glade-interface> tag.")
+        # Rename glade-interface to interface
         glade_iface[0].tagName = 'interface'
         self._interface = glade_iface[0]
 
@@ -125,24 +242,45 @@ class GtkBuilderConverter(object):
                 if node.name == 'glade-interface':
                     self._dom.removeChild(node)
 
-        # Strip requires
-        requires = self._dom.getElementsByTagName("requires")
-        for require in requires:
-            require.parentNode.childNodes.remove(require)
+        # Strip unsupported tags
+        for tag in ['requires', 'requires-version']:
+            for child in self._dom.getElementsByTagName(tag):
+                child.parentNode.removeChild(child)
 
-        for child in self._dom.getElementsByTagName("accessibility"):
-            child.parentNode.removeChild(child)
+        if self.root:
+            self._strip_root(self.root)
 
-        for node in self._dom.getElementsByTagName("widget"):
+        # Rename widget to object
+        objects = self._dom.getElementsByTagName("widget")
+        for node in objects:
             node.tagName = "object"
 
-        for node in self._dom.getElementsByTagName("object"):
+        for node in objects:
             self._convert(node.getAttribute("class"), node)
+            if self._get_object(node.getAttribute('id')) is not None:
+               print "WARNING: duplicate id \"" + node.getAttribute('id') + "\""
+            self.objects[node.getAttribute('id')] = node
 
         # Convert Gazpachos UI tag
         for node in self._dom.getElementsByTagName("ui"):
             self._convert_ui(node)
 
+        # Convert accessibility tag
+        for node in self._dom.getElementsByTagName("accessibility"):
+            self._convert_accessibility(node)
+
+        # Output the newly created root objects and sort them
+        # by attribute id
+        # FIXME: Use sorted(self.root_objects,
+        #                   key=lambda n: n.getAttribute('id'),
+        #                   reverse=True):
+        # when we can depend on python 2.4 or higher
+        root_objects = self.root_objects[:]
+        root_objects.sort(lambda a, b: cmp(b.getAttribute('id'),
+                                           a.getAttribute('id')))
+        for obj in root_objects:
+            self._interface.childNodes.insert(0, obj)
+
     def _convert(self, klass, node):
         if klass == 'GtkNotebook':
             self._packing_prop_to_child_attr(node, "type", "tab")
@@ -150,50 +288,75 @@ class GtkBuilderConverter(object):
             self._packing_prop_to_child_attr(
                 node, "type", "label_item", "label")
         elif klass == "GtkMenuBar":
-            if node.hasAttribute('constructor'):
-                uimgr = self._get_widget('uimanager')
-            else:
-                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._convert_menu(node)
+        elif klass == "GtkMenu":
+            # Only convert toplevel popups
+            if node.parentNode == self._interface:
+                self._convert_menu(node, popup=True)
+        elif klass in WINDOWS and self.skip_windows:
+            self._remove_window(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
+        for prop in get_property_nodes(node):
             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']:
+                # It does not make sense to convert responses when
+                # we're not going to output dialogs
+                if self.skip_windows:
+                    continue
                 object_id = node.getAttribute('id')
                 response = prop.childNodes[0].data
                 self._convert_dialog_response(node, object_id, response)
                 prop.parentNode.removeChild(prop)
+            elif prop_name == "adjustment":
+                self._convert_adjustment(prop)
+            elif prop_name == "items" and klass in ['GtkComboBox',
+                                                    'GtkComboBoxEntry']:
+                self._convert_combobox_items(node, prop)
+            elif prop_name == "text" and klass == 'GtkTextView':
+                self._convert_textview_text(prop)
+
+    def _remove_window(self, node):
+        object_node = get_object_node(get_child_nodes(node)[0])
+        parent = node.parentNode
+        parent.removeChild(node)
+        parent.appendChild(object_node)
+
+    def _convert_menu(self, node, popup=False):
+        if node.hasAttribute('constructor'):
+            return
+
+        uimgr = self._create_root_object('GtkUIManager',
+                                         template='uimanager')
+
+        if popup:
+            name = 'popup'
+        else:
+            name = 'menubar'
 
-    def _convert_menubar(self, uimgr, node):
-        menubar = self._dom.createElement('menubar')
-        menubar.setAttribute('name', node.getAttribute('id'))
+        menu = self._dom.createElement(name)
+        menu.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)
+            item = self._convert_menuitem(uimgr, obj_node)
+            menu.appendChild(item)
             child.removeChild(obj_node)
             child.parentNode.removeChild(child)
 
         ui = self._dom.createElement('ui')
         uimgr.appendChild(ui)
 
-        ui.appendChild(menubar)
+        ui.appendChild(menu)
 
-    def _convert_menuitem(self, uimgr, menubar, obj_node):
+    def _convert_menuitem(self, uimgr, obj_node):
         children = get_child_nodes(obj_node)
         name = 'menuitem'
         if children:
@@ -204,55 +367,89 @@ class GtkBuilderConverter(object):
                 name = 'menu'
 
         object_class = obj_node.getAttribute('class')
-        if object_class in ['GtkMenuItem', 'GtkImageMenuItem']:
+        if object_class in ['GtkMenuItem',
+                            'GtkImageMenuItem',
+                            'GtkCheckMenuItem',
+                            'GtkRadioMenuItem']:
             menu = self._dom.createElement(name)
         elif object_class == 'GtkSeparatorMenuItem':
-            menu = self._dom.createElement('sep')
+            return self._dom.createElement('separator')
         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)
+                item = self._convert_menuitem(uimgr, obj_node)
+                menu.appendChild(item)
                 child.removeChild(obj_node)
                 child.parentNode.removeChild(child)
+        return menu
+
+    def _menuitem_to_action(self, node, properties):
+        copy_properties(node, ['label', 'tooltip'], properties)
+
+    def _togglemenuitem_to_action(self, node, properties):
+        self._menuitem_to_action(node, properties)
+        copy_properties(node, ['active'], properties)
+
+    def _radiomenuitem_to_action(self, node, properties):
+        self._togglemenuitem_to_action(node, properties)
+        copy_properties(node, ['group'], properties)
 
     def _add_action_from_menuitem(self, uimgr, node):
         properties = {}
         object_class = node.getAttribute('class')
         object_id = node.getAttribute('id')
-        if object_class == 'GtkImageMenuItem':
+        if object_class == 'GtkMenuItem':
+            name = 'GtkAction'
+            self._menuitem_to_action(node, properties)
+        elif object_class == 'GtkCheckMenuItem':
+            name = 'GtkToggleAction'
+            self._togglemenuitem_to_action(node, properties)
+        elif object_class == 'GtkRadioMenuItem':
+            name = 'GtkRadioAction'
+            self._radiomenuitem_to_action(node, properties)
+        elif 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
+                child = get_property_node(image, 'stock')
+                if child is not None:
+                    properties['stock_id'] = child
+            self._menuitem_to_action(node, properties)
         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')
+        if get_property(node, 'use_stock') == 'True':
+            if 'label' in properties:
+                properties['stock_id'] = properties['label']
+                del properties['label']
+
         properties['name'] = object_id
         action = self._create_object(name,
                                      object_id,
-                                     **properties)
+                                     properties=properties)
+        for signal in get_signal_nodes(node):
+            signal_name = signal.getAttribute('name')
+            if signal_name in ['activate', 'toggled']:
+                action.appendChild(signal)
+            else:
+                print 'Unhandled signal %s::%s' % (node.getAttribute('class'),
+                                                   signal_name)
+
         if not uimgr.childNodes:
             child = self._dom.createElement('child')
             uimgr.appendChild(child)
 
-            group = self._create_object('GtkActionGroup', 'actiongroup1')
+            group = self._create_object('GtkActionGroup', None,
+                                        template='actiongroup')
             child.appendChild(group)
         else:
             group = uimgr.childNodes[0].childNodes[0]
@@ -261,20 +458,26 @@ class GtkBuilderConverter(object):
         group.appendChild(child)
         child.appendChild(action)
 
+        for accelerator in get_accelerator_nodes(node):
+            signal_name = accelerator.getAttribute('signal')
+            if signal_name != 'activate':
+                print 'Unhandled accelerator signal for %s::%s' % (
+                    node.getAttribute('class'), signal_name)
+                continue
+            accelerator.removeAttribute('signal')
+            child.appendChild(accelerator)
+
     def _convert_sizegroup(self, node, prop):
         # This is Gazpacho only
         node.removeChild(prop)
-        obj = self._get_widget(prop.childNodes[0].data)
+        obj = self._get_object(prop.childNodes[0].data)
         if obj is None:
-            widgets = self._get_widgets_by_attr("class", "GtkSizeGroup")
+            widgets = self._get_objects_by_attr("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])
+                obj = self._create_root_object('GtkSizeGroup',
+                                               template='sizegroup')
 
         widgets = obj.getElementsByTagName("widgets")
         if widgets:
@@ -291,8 +494,12 @@ class GtkBuilderConverter(object):
     def _convert_dialog_response(self, node, object_name, response):
         # 1) Get parent dialog node
         while True:
+            # If we can't find the parent dialog, give up
+            if node == self._dom:
+                return
+
             if (node.tagName == 'object' and
-                node.getAttribute('class') == 'GtkDialog'):
+                node.getAttribute('class') in DIALOGS):
                 dialog = node
                 break
             node = node.parentNode
@@ -300,7 +507,7 @@ class GtkBuilderConverter(object):
 
         # 2) Get dialogs action-widgets tag, create if not found
         for child in dialog.childNodes:
-            if child.nodeType == Node.TEXT_NODE:
+            if child.nodeType != Node.ELEMENT_NODE:
                 continue
             if child.tagName == 'action-widgets':
                 actions = child
@@ -315,9 +522,111 @@ class GtkBuilderConverter(object):
         action.appendChild(self._dom.createTextNode(object_name))
         actions.appendChild(action)
 
+    def _convert_adjustment(self, prop):
+        properties = {}
+        if prop.childNodes:
+            data = prop.childNodes[0].data
+            value, lower, upper, step, page, page_size = data.split(' ')
+            properties.update(value=value,
+                              lower=lower,
+                              upper=upper,
+                              step_increment=step,
+                              page_increment=page,
+                              page_size=page_size)
+        else:
+            prop.appendChild(self._dom.createTextNode(""))
+
+        adj = self._create_root_object("GtkAdjustment",
+                                       template='adjustment',
+                                       properties=properties)
+        prop.childNodes[0].data = adj.getAttribute('id')
+
+    def _convert_combobox_items(self, node, prop):
+        parent = prop.parentNode
+        if not prop.childNodes:
+            parent.removeChild(prop)
+            return
+
+        translatable_attr = prop.attributes.get('translatable')
+        translatable = translatable_attr is not None and translatable_attr.value == 'yes'
+        has_context_attr = prop.attributes.get('context')
+        has_context = has_context_attr is not None and has_context_attr.value == 'yes'
+        comments_attr = prop.attributes.get('comments')
+        comments = comments_attr is not None and comments_attr.value or None
+
+        value = prop.childNodes[0].data
+        model = self._create_root_object("GtkListStore",
+                                         template="model")
+
+        columns = self._dom.createElement('columns')
+        model.appendChild(columns)
+
+        column = self._dom.createElement('column')
+        column.setAttribute('type', 'gchararray')
+        columns.appendChild(column)
+
+        data = self._dom.createElement('data')
+        model.appendChild(data)
+
+        if value.endswith('\n'):
+            value = value[:-1]
+        for item in value.split('\n'):
+            row = self._dom.createElement('row')
+            data.appendChild(row)
+
+            col = self._dom.createElement('col')
+            col.setAttribute('id', '0')
+            if translatable:
+                col.setAttribute('translatable', 'yes')
+            if has_context:
+                splitting = item.split('|', 1)
+                if len(splitting) == 2:
+                    context, item = splitting
+                    col.setAttribute('context', context)
+            if comments is not None:
+                col.setAttribute('comments', comments)
+            col.appendChild(self._dom.createTextNode(item))
+            row.appendChild(col)
+
+        model_prop = self._dom.createElement('property')
+        model_prop.setAttribute('name', 'model')
+        model_prop.appendChild(
+            self._dom.createTextNode(model.getAttribute('id')))
+        parent.appendChild(model_prop)
+
+        parent.removeChild(prop)
+
+        child = self._dom.createElement('child')
+        node.appendChild(child)
+        cell_renderer = self._create_object('GtkCellRendererText', None,
+                                            template='renderer')
+        child.appendChild(cell_renderer)
+
+        attributes = self._dom.createElement('attributes')
+        child.appendChild(attributes)
+
+        attribute = self._dom.createElement('attribute')
+        attributes.appendChild(attribute)
+        attribute.setAttribute('name', 'text')
+        attribute.appendChild(self._dom.createTextNode('0'))
+
+    def _convert_textview_text(self, prop):
+        if not prop.childNodes:
+            prop.parentNode.removeChild(prop)
+            return
+
+        data = prop.childNodes[0].data
+        if prop.hasAttribute('translatable'):
+            prop.removeAttribute('translatable')
+        tbuffer = self._create_root_object("GtkTextBuffer",
+                                           template='textbuffer',
+                                           properties=dict(text=data))
+        prop.childNodes[0].data = tbuffer.getAttribute('id')
+        prop.setAttribute('name', 'buffer')
+
     def _packing_prop_to_child_attr(self, node, prop_name, prop_val,
                                    attr_val=None):
-        for child in node.getElementsByTagName("child"):
+        for child in get_child_nodes(node):
             packing_props = [p for p in child.childNodes if p.nodeName == "packing"]
             if not packing_props:
                 continue
@@ -349,19 +658,115 @@ class GtkBuilderConverter(object):
 
         # Updating references made by widgets
         parent_id = node.parentNode.getAttribute("id")
-        for widget in self._get_widgets_by_attr("constructor",
+        for widget in self._get_objects_by_attr("constructor",
                                                 node.getAttribute("id")):
             widget.getAttributeNode("constructor").value = parent_id
         node.removeAttribute("id")
 
+    def _convert_accessibility(self, node):
+        objectNode = node.parentNode
+        parent_id = objectNode.getAttribute("id")
+
+        properties = {}
+        for node in node.childNodes:
+            if node.nodeName == 'atkproperty':
+                node.tagName = 'property'
+                properties[node.getAttribute('name')] = node
+                node.parentNode.removeChild(node)
+            elif node.nodeName == 'atkrelation':
+                node.tagName = 'relation'
+                relation_type = node.getAttribute('type')
+                relation_type = relation_type.replace('_', '-')
+                node.setAttribute('type', relation_type)
+            elif node.nodeName == 'atkaction':
+                node.tagName = 'action'
+
+        if properties:
+            child = self._dom.createElement('child')
+            child.setAttribute("internal-child", "accessible")
+
+            atkobject = self._create_object(
+                "AtkObject", None,
+                template='a11y-%s' % (parent_id,),
+                properties=properties)
+            child.appendChild(atkobject)
+            objectNode.appendChild(child)
+
+    def _strip_root(self, root_name):
+        for widget in self._dom.getElementsByTagName("widget"):
+            if widget.getAttribute('id') == root_name:
+                break
+        else:
+            raise SystemExit("Could not find an object called `%s'" % (
+                root_name))
+
+        for child in self._interface.childNodes[:]:
+            if child.nodeType != Node.ELEMENT_NODE:
+                continue
+            child.parentNode.removeChild(child)
 
-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()
+        self._interface.appendChild(widget)
+
+
+def _indent(output):
+    if not subprocess:
+        return output
+
+    for directory in os.environ['PATH'].split(os.pathsep):
+        filename = os.path.join(directory, 'xmllint')
+        if os.path.exists(filename):
+            break
+    else:
+        return output
+
+    s = subprocess.Popen([filename, '--format', '-'],
+                         stdin=subprocess.PIPE,
+                         stdout=subprocess.PIPE)
+    s.stdin.write(output)
+    s.stdin.close()
+    return s.stdout.read()
+
+def usage():
+    print __doc__
+
+def main(args):
+    try:
+        opts, args = getopt.getopt(args[1:], "hwr:",
+                                   ["help", "skip-windows", "root="])
+    except getopt.GetoptError:
+        usage()
+        return 2
+
+    if len(args) != 2:
+        usage()
+        return 2
+
+    input_filename, output_filename = args
+
+    skip_windows = False
+    split = False
+    root = None
+    for o, a in opts:
+        if o in ("-h", "--help"):
+            usage()
+            sys.exit()
+        elif o in ("-r", "--root"):
+            root = a
+        elif o in ("-w", "--skip-windows"):
+            skip_windows = True
+
+    conv = GtkBuilderConverter(skip_windows=skip_windows,
+                               root=root)
+    conv.parse_file(input_filename)
+
+    xml = _indent(conv.to_xml())
+    if output_filename == "-":
+        print xml
+    else:
+        open(output_filename, 'w').write(xml)
+        print "Wrote", output_filename
+
+    return 0
 
 if __name__ == "__main__":
-    main()
+    sys.exit(main(sys.argv))