gtk-builder-convert 27.2 KB
Newer Older
1 2
#!/usr/bin/env python
#
3
# Copyright (C) 2006-2008 Async Open Source
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
#                         Henrique Romano <henrique@async.com.br>
#                         Johan Dahlin <jdahlin@async.com.br>
#
# 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.
#
# TODO:
#  Toolbars

24 25 26 27
"""Usage: gtk-builder-convert [OPTION] [INPUT] [OUTPUT]
Converts Glade files into XML files which can be loaded with GtkBuilder.
The [INPUT] file is

Matthias Clasen's avatar
Matthias Clasen committed
28
  -w, --skip-windows     Convert everything but GtkWindow subclasses.
29
  -r, --root             Convert only widget named root and its children
30 31
  -h, --help             display this help and exit

32
When OUTPUT is -, write to standard output.
33 34 35 36 37 38 39

Examples:
  gtk-builder-convert preference.glade preferences.ui

Report bugs to http://bugzilla.gnome.org/."""

import getopt
40
import os
41 42 43 44
import sys

from xml.dom import minidom, Node

45
DIALOGS = ['GtkDialog',
46 47
           'GtkFileChooserDialog',
           'GtkMessageDialog']
48
WINDOWS = ['GtkWindow'] + DIALOGS
49

50 51 52 53 54 55 56
# The subprocess is only available in Python 2.4+
try:
    import subprocess
    subprocess # pyflakes
except ImportError:
    subprocess = None

57
def get_child_nodes(node):
58
    assert node.tagName == 'object'
59 60
    nodes = []
    for child in node.childNodes:
61
        if child.nodeType != Node.ELEMENT_NODE:
62 63 64 65 66 67
            continue
        if child.tagName != 'child':
            continue
        nodes.append(child)
    return nodes

68
def get_properties(node):
69
    assert node.tagName == 'object'
70 71
    properties = {}
    for child in node.childNodes:
72
        if child.nodeType != Node.ELEMENT_NODE:
73 74 75 76 77 78 79
            continue
        if child.tagName != 'property':
            continue
        value = child.childNodes[0].data
        properties[child.getAttribute('name')] = value
    return properties

80 81
def get_property(node, property_name):
    assert node.tagName == 'object'
82
    properties = get_properties(node)
83 84
    return properties.get(property_name)

85 86 87 88
def get_property_node(node, property_name):
    assert node.tagName == 'object'
    properties = {}
    for child in node.childNodes:
89
        if child.nodeType != Node.ELEMENT_NODE:
90 91 92 93 94 95
            continue
        if child.tagName != 'property':
            continue
        if child.getAttribute('name') == property_name:
            return child

96 97 98 99
def get_signal_nodes(node):
    assert node.tagName == 'object'
    signals = []
    for child in node.childNodes:
100
        if child.nodeType != Node.ELEMENT_NODE:
101 102 103 104 105
            continue
        if child.tagName == 'signal':
            signals.append(child)
    return signals

106 107 108 109
def get_property_nodes(node):
    assert node.tagName == 'object'
    properties = []
    for child in node.childNodes:
110
        if child.nodeType != Node.ELEMENT_NODE:
111
            continue
112
        # FIXME: handle comments
113 114 115 116
        if child.tagName == 'property':
            properties.append(child)
    return properties

117 118 119 120
def get_accelerator_nodes(node):
    assert node.tagName == 'object'
    accelerators = []
    for child in node.childNodes:
121
        if child.nodeType != Node.ELEMENT_NODE:
122 123 124 125 126
            continue
        if child.tagName == 'accelerator':
            accelerators.append(child)
    return accelerators

127
def get_object_node(child_node):
128
    assert child_node.tagName == 'child', child_node
129 130
    nodes = []
    for node in child_node.childNodes:
131
        if node.nodeType != Node.ELEMENT_NODE:
132 133 134 135 136 137
            continue
        if node.tagName == 'object':
            nodes.append(node)
    assert len(nodes) == 1, nodes
    return nodes[0]

138 139 140
def copy_properties(node, props, prop_dict):
    assert node.tagName == 'object'
    for prop_name in props:
141 142 143 144 145
        child = get_property_node(node, prop_name)
        if child is not None:
            prop_dict[prop_name] = child

    return node
146

147 148
class GtkBuilderConverter(object):

149
    def __init__(self, skip_windows, root):
150
        self.skip_windows = skip_windows
151
        self.root = root
152 153
        self.root_objects = []
        self.objects = {}
154

155 156 157 158 159 160 161 162 163 164 165 166 167
    #
    # 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):
168 169
        xml = self._dom.toprettyxml("", "")
        return xml.encode('utf-8')
170 171 172 173 174

    #
    # Private
    #

175 176 177 178
    def _get_object(self, name):
        return self.objects.get(name)

    def _get_objects_by_attr(self, attribute, value):
179 180 181
        return [w for w in self._dom.getElementsByTagName("object")
                      if w.getAttribute(attribute) == value]

182
    def _create_object(self, obj_class, obj_id, template=None, properties=None):
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
        """
        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
        """
198 199 200 201
        if template is not None:
            count = 1
            while True:
                obj_id = template + str(count)
202
                widget = self._get_object(obj_id)
203 204 205 206 207
                if widget is None:
                    break

                count += 1

208 209 210
        obj = self._dom.createElement('object')
        obj.setAttribute('class', obj_class)
        obj.setAttribute('id', obj_id)
211 212 213 214 215 216 217 218 219 220 221 222
        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)
223 224 225
        self.objects[obj_id] = obj
        return obj

226 227
    def _create_root_object(self, obj_class, template, properties=None):
        obj = self._create_object(obj_class, None, template, properties)
228
        self.root_objects.append(obj)
229 230 231 232 233 234
        return obj

    def _parse(self):
        glade_iface = self._dom.getElementsByTagName("glade-interface")
        assert glade_iface, ("Badly formed XML, there is "
                             "no <glade-interface> tag.")
235
        # Rename glade-interface to interface
236 237 238
        glade_iface[0].tagName = 'interface'
        self._interface = glade_iface[0]

239 240 241 242 243 244
        # Remove glade-interface doc type
        for node in self._dom.childNodes:
            if node.nodeType == Node.DOCUMENT_TYPE_NODE:
                if node.name == 'glade-interface':
                    self._dom.removeChild(node)

245
        # Strip unsupported tags
246
        for tag in ['requires', 'requires-version']:
247 248
            for child in self._dom.getElementsByTagName(tag):
                child.parentNode.removeChild(child)
249

250 251 252
        if self.root:
            self._strip_root(self.root)

253
        # Rename widget to object
254 255 256 257 258
        objects = self._dom.getElementsByTagName("widget")
        for node in objects:
            node.tagName = "object"

        for node in objects:
259
            self._convert(node.getAttribute("class"), node)
260 261
            if self._get_object(node.getAttribute('id')) is not None:
		print "WARNING: duplicate id \"" + node.getAttribute('id') + "\""
262
            self.objects[node.getAttribute('id')] = node
263 264 265 266 267

        # Convert Gazpachos UI tag
        for node in self._dom.getElementsByTagName("ui"):
            self._convert_ui(node)

268 269 270 271
        # Convert accessibility tag
        for node in self._dom.getElementsByTagName("accessibility"):
            self._convert_accessibility(node)

272 273
        # Output the newly created root objects and sort them
        # by attribute id
274 275 276 277 278 279 280 281
        # 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:
282 283
            self._interface.childNodes.insert(0, obj)

284 285 286 287 288 289 290
    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":
291
            self._convert_menu(node)
292 293 294 295
        elif klass == "GtkMenu":
            # Only convert toplevel popups
            if node.parentNode == self._interface:
                self._convert_menu(node, popup=True)
296 297
        elif klass in WINDOWS and self.skip_windows:
            self._remove_window(node)
298 299 300 301
        self._default_widget_converter(node)

    def _default_widget_converter(self, node):
        klass = node.getAttribute("class")
302
        for prop in get_property_nodes(node):
303 304 305 306 307 308
            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']:
309 310 311 312
                # It does not make sense to convert responses when
                # we're not going to output dialogs
                if self.skip_windows:
                    continue
313 314 315 316
                object_id = node.getAttribute('id')
                response = prop.childNodes[0].data
                self._convert_dialog_response(node, object_id, response)
                prop.parentNode.removeChild(prop)
317 318
            elif prop_name == "adjustment":
                self._convert_adjustment(prop)
319 320 321
            elif prop_name == "items" and klass in ['GtkComboBox',
                                                    'GtkComboBoxEntry']:
                self._convert_combobox_items(node, prop)
322 323
            elif prop_name == "text" and klass == 'GtkTextView':
                self._convert_textview_text(prop)
324

325 326 327 328 329 330
    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)

331
    def _convert_menu(self, node, popup=False):
332 333 334 335 336 337
        if node.hasAttribute('constructor'):
            return

        uimgr = self._create_root_object('GtkUIManager',
                                         template='uimanager')

338 339 340 341
        if popup:
            name = 'popup'
        else:
            name = 'menubar'
342

343
        menu = self._dom.createElement(name)
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359
        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)
            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(menu)

    def _convert_menuitem(self, uimgr, obj_node):
360 361 362 363 364 365 366 367 368 369
        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')
370 371
        if object_class in ['GtkMenuItem',
                            'GtkImageMenuItem',
372 373
                            'GtkCheckMenuItem',
                            'GtkRadioMenuItem']:
374 375
            menu = self._dom.createElement(name)
        elif object_class == 'GtkSeparatorMenuItem':
376
            return self._dom.createElement('separator')
377 378
        else:
            raise NotImplementedError(object_class)
379

380 381 382 383 384
        menu.setAttribute('action', obj_node.getAttribute('id'))
        self._add_action_from_menuitem(uimgr, obj_node)
        if children:
            for child in get_child_nodes(menu_node):
                obj_node = get_object_node(child)
385 386
                item = self._convert_menuitem(uimgr, obj_node)
                menu.appendChild(item)
387 388
                child.removeChild(obj_node)
                child.parentNode.removeChild(child)
389
        return menu
390

391
    def _menuitem_to_action(self, node, properties):
392
        copy_properties(node, ['label', 'tooltip'], properties)
393 394 395 396 397 398 399 400 401

    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)

402 403 404 405
    def _add_action_from_menuitem(self, uimgr, node):
        properties = {}
        object_class = node.getAttribute('class')
        object_id = node.getAttribute('id')
406 407 408 409 410 411 412 413 414 415
        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':
416 417 418 419 420
            name = 'GtkAction'
            children = get_child_nodes(node)
            if (children and
                children[0].getAttribute('internal-child') == 'image'):
                image = get_object_node(children[0])
421 422 423
                child = get_property_node(image, 'stock')
                if child is not None:
                    properties['stock_id'] = child
424
            self._menuitem_to_action(node, properties)
425 426 427 428 429
        elif object_class == 'GtkSeparatorMenuItem':
            return
        else:
            raise NotImplementedError(object_class)

430
        if get_property(node, 'use_stock') == 'True':
431
            if 'label' in properties:
432
                properties['stock_id'] = properties['label']
433
                del properties['label']
434

435 436 437
        properties['name'] = object_id
        action = self._create_object(name,
                                     object_id,
438
                                     properties=properties)
439 440
        for signal in get_signal_nodes(node):
            signal_name = signal.getAttribute('name')
441
            if signal_name in ['activate', 'toggled']:
442 443 444 445 446
                action.appendChild(signal)
            else:
                print 'Unhandled signal %s::%s' % (node.getAttribute('class'),
                                                   signal_name)

447 448 449 450
        if not uimgr.childNodes:
            child = self._dom.createElement('child')
            uimgr.appendChild(child)

451 452
            group = self._create_object('GtkActionGroup', None,
                                        template='actiongroup')
453 454 455 456 457 458 459 460
            child.appendChild(group)
        else:
            group = uimgr.childNodes[0].childNodes[0]

        child = self._dom.createElement('child')
        group.appendChild(child)
        child.appendChild(action)

461
        for accelerator in get_accelerator_nodes(node):
462
            signal_name = accelerator.getAttribute('signal')
463 464 465 466 467 468 469
            if signal_name != 'activate':
                print 'Unhandled accelerator signal for %s::%s' % (
                    node.getAttribute('class'), signal_name)
                continue
            accelerator.removeAttribute('signal')
            child.appendChild(accelerator)

470 471 472
    def _convert_sizegroup(self, node, prop):
        # This is Gazpacho only
        node.removeChild(prop)
473
        obj = self._get_object(prop.childNodes[0].data)
474
        if obj is None:
475
            widgets = self._get_objects_by_attr("class", "GtkSizeGroup")
476 477 478
            if widgets:
                obj = widgets[-1]
            else:
479 480
                obj = self._create_root_object('GtkSizeGroup',
                                               template='sizegroup')
481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496

        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:
497 498 499 500
            # If we can't find the parent dialog, give up
            if node == self._dom:
                return

501
            if (node.tagName == 'object' and
502
                node.getAttribute('class') in DIALOGS):
503 504 505 506 507 508 509
                dialog = node
                break
            node = node.parentNode
            assert node

        # 2) Get dialogs action-widgets tag, create if not found
        for child in dialog.childNodes:
510
            if child.nodeType != Node.ELEMENT_NODE:
511 512 513 514 515 516 517 518 519 520 521 522 523 524
                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)

525
    def _convert_adjustment(self, prop):
526 527 528 529 530 531 532 533 534 535 536 537 538
        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(""))

539
        adj = self._create_root_object("GtkAdjustment",
540
                                       template='adjustment',
541
                                       properties=properties)
542
        prop.childNodes[0].data = adj.getAttribute('id')
543

544
    def _convert_combobox_items(self, node, prop):
545
        parent = prop.parentNode
546
        if not prop.childNodes:
547
            parent.removeChild(prop)
548
            return
549 550 551 552 553 554 555 556

        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

557
        value = prop.childNodes[0].data
558 559
        model = self._create_root_object("GtkListStore",
                                         template="model")
560 561 562 563 564 565 566 567 568 569 570

        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)

571 572
        if value.endswith('\n'):
            value = value[:-1]
573 574 575 576 577 578
        for item in value.split('\n'):
            row = self._dom.createElement('row')
            data.appendChild(row)

            col = self._dom.createElement('col')
            col.setAttribute('id', '0')
579 580 581 582 583 584 585 586 587
            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)
588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612
            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'))

613
    def _convert_textview_text(self, prop):
614 615 616 617
        if not prop.childNodes:
            prop.parentNode.removeChild(prop)
            return

618 619 620
        data = prop.childNodes[0].data
        if prop.hasAttribute('translatable'):
            prop.removeAttribute('translatable')
621 622
        tbuffer = self._create_root_object("GtkTextBuffer",
                                           template='textbuffer',
623
                                           properties=dict(text=data))
624
        prop.childNodes[0].data = tbuffer.getAttribute('id')
625
        prop.setAttribute('name', 'buffer')
626

627 628
    def _packing_prop_to_child_attr(self, node, prop_name, prop_val,
                                   attr_val=None):
629
        for child in get_child_nodes(node):
630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660
            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("<![CDATA[") or not data.endswith("]]>"):
            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")
661
        for widget in self._get_objects_by_attr("constructor",
662 663 664 665
                                                node.getAttribute("id")):
            widget.getAttributeNode("constructor").value = parent_id
        node.removeAttribute("id")

666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694
    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)

695
    def _strip_root(self, root_name):
696 697 698 699
        for widget in self._dom.getElementsByTagName("widget"):
            if widget.getAttribute('id') == root_name:
                break
        else:
700 701 702 703 704 705 706 707 708 709 710
            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)

        self._interface.appendChild(widget)


711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728
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()

729 730 731 732 733
def usage():
    print __doc__

def main(args):
    try:
734 735
        opts, args = getopt.getopt(args[1:], "hwr:",
                                   ["help", "skip-windows", "root="])
736 737 738 739 740 741 742 743 744 745 746 747
    except getopt.GetoptError:
        usage()
        return 2

    if len(args) != 2:
        usage()
        return 2

    input_filename, output_filename = args

    skip_windows = False
    split = False
748
    root = None
749 750 751 752
    for o, a in opts:
        if o in ("-h", "--help"):
            usage()
            sys.exit()
753 754
        elif o in ("-r", "--root"):
            root = a
755 756 757
        elif o in ("-w", "--skip-windows"):
            skip_windows = True

758 759
    conv = GtkBuilderConverter(skip_windows=skip_windows,
                               root=root)
760 761 762 763 764 765 766 767
    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
768

769
    return 0
770 771

if __name__ == "__main__":
772
    sys.exit(main(sys.argv))