1#!/usr/local/bin/python3.8
2#
3# Copyright (C) 2006-2008 Async Open Source
4#                         Henrique Romano <henrique@async.com.br>
5#                         Johan Dahlin <jdahlin@async.com.br>
6#
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.
11#
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.
16#
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.
20#
21# TODO:
22#  Toolbars
23
24"""Usage: gtk-builder-convert [OPTION] [INPUT] [OUTPUT]
25Converts Glade files into XML files which can be loaded with GtkBuilder.
26The [INPUT] file is
27
28  -w, --skip-windows     Convert everything but GtkWindow subclasses.
29  -r, --root             Convert only widget named root and its children
30  -h, --help             display this help and exit
31
32When OUTPUT is -, write to standard output.
33
34Examples:
35  gtk-builder-convert preference.glade preferences.ui
36
37Report bugs to https://gitlab.gnome.org/GNOME/gtk/issues/new
38"""
39
40from __future__ import print_function
41import getopt
42import os
43import sys
44
45from xml.dom import minidom, Node
46
47DIALOGS = ['GtkDialog',
48           'GtkFileChooserDialog',
49           'GtkMessageDialog']
50WINDOWS = ['GtkWindow'] + DIALOGS
51
52import subprocess
53
54def get_child_nodes(node):
55    assert node.tagName == 'object'
56    nodes = []
57    for child in node.childNodes:
58        if child.nodeType != Node.ELEMENT_NODE:
59            continue
60        if child.tagName != 'child':
61            continue
62        nodes.append(child)
63    return nodes
64
65def get_properties(node):
66    assert node.tagName == 'object'
67    properties = {}
68    for child in node.childNodes:
69        if child.nodeType != Node.ELEMENT_NODE:
70            continue
71        if child.tagName != 'property':
72            continue
73        value = child.childNodes[0].data
74        properties[child.getAttribute('name')] = value
75    return properties
76
77def get_property(node, property_name):
78    assert node.tagName == 'object'
79    properties = get_properties(node)
80    return properties.get(property_name)
81
82def get_property_node(node, property_name):
83    assert node.tagName == 'object'
84    properties = {}
85    for child in node.childNodes:
86        if child.nodeType != Node.ELEMENT_NODE:
87            continue
88        if child.tagName != 'property':
89            continue
90        if child.getAttribute('name') == property_name:
91            return child
92
93def get_signal_nodes(node):
94    assert node.tagName == 'object'
95    signals = []
96    for child in node.childNodes:
97        if child.nodeType != Node.ELEMENT_NODE:
98            continue
99        if child.tagName == 'signal':
100            signals.append(child)
101    return signals
102
103def get_property_nodes(node):
104    assert node.tagName == 'object'
105    properties = []
106    for child in node.childNodes:
107        if child.nodeType != Node.ELEMENT_NODE:
108            continue
109        # FIXME: handle comments
110        if child.tagName == 'property':
111            properties.append(child)
112    return properties
113
114def get_accelerator_nodes(node):
115    assert node.tagName == 'object'
116    accelerators = []
117    for child in node.childNodes:
118        if child.nodeType != Node.ELEMENT_NODE:
119            continue
120        if child.tagName == 'accelerator':
121            accelerators.append(child)
122    return accelerators
123
124def get_object_node(child_node):
125    assert child_node.tagName == 'child', child_node
126    nodes = []
127    for node in child_node.childNodes:
128        if node.nodeType != Node.ELEMENT_NODE:
129            continue
130        if node.tagName == 'object':
131            nodes.append(node)
132    assert len(nodes) == 1, nodes
133    return nodes[0]
134
135def copy_properties(node, props, prop_dict):
136    assert node.tagName == 'object'
137    for prop_name in props:
138        child = get_property_node(node, prop_name)
139        if child is not None:
140            prop_dict[prop_name] = child
141
142    return node
143
144class GtkBuilderConverter(object):
145
146    def __init__(self, skip_windows, target_version, root):
147        self.skip_windows = skip_windows
148        self.target_version = target_version
149        self.root = root
150        self.root_objects = []
151        self.objects = {}
152
153    #
154    # Public API
155    #
156
157    def parse_file(self, file):
158        self._dom = minidom.parse(file)
159        self._parse()
160
161    def parse_buffer(self, buffer):
162        self._dom = minidom.parseString(buffer)
163        self._parse()
164
165    def to_xml(self):
166        xml = self._dom.toprettyxml("", "")
167        return xml.encode('utf-8')
168
169    #
170    # Private
171    #
172
173    def _get_object(self, name):
174        return self.objects.get(name)
175
176    def _get_objects_by_attr(self, attribute, value):
177        return [w for w in self._dom.getElementsByTagName("object")
178                      if w.getAttribute(attribute) == value]
179
180    def _create_object(self, obj_class, obj_id, template=None, properties=None):
181        """
182        Creates a new <object> tag.
183        Optionally a name template can be provided which will be used
184        to avoid naming collisions.
185        The properties dictionary can either contain string values or Node
186        values. If a node is provided the name of the node will be overridden
187        by the dictionary key.
188
189        @param obj_class: class of the object (class tag)
190        @param obj_id: identifier of the object (id tag)
191        @param template: name template to use, for example 'button'
192        @param properties: dictionary of properties
193        @type properties: string or Node.
194        @returns: Newly created node of the object
195        """
196        if template is not None:
197            count = 1
198            while True:
199                obj_id = template + str(count)
200                widget = self._get_object(obj_id)
201                if widget is None:
202                    break
203
204                count += 1
205
206        obj = self._dom.createElement('object')
207        obj.setAttribute('class', obj_class)
208        obj.setAttribute('id', obj_id)
209        if properties:
210            for name, value in properties.items():
211                if isinstance(value, Node):
212                    # Reuse the node, so translatable and context still will be
213                    # set when converting nodes. See also #509153
214                    prop = value
215                else:
216                    prop = self._dom.createElement('property')
217                    prop.appendChild(self._dom.createTextNode(value))
218
219                prop.setAttribute('name', str(name))
220                obj.appendChild(prop)
221        self.objects[obj_id] = obj
222        return obj
223
224    def _create_root_object(self, obj_class, template, properties=None):
225        obj = self._create_object(obj_class, None, template, properties)
226        self.root_objects.append(obj)
227        return obj
228
229    def _parse(self):
230        glade_iface = self._dom.getElementsByTagName("glade-interface")
231        assert glade_iface, ("Badly formed XML, there is "
232                             "no <glade-interface> tag.")
233        # Rename glade-interface to interface
234        glade_iface[0].tagName = 'interface'
235        self._interface = glade_iface[0]
236
237        # Remove glade-interface doc type
238        for node in self._dom.childNodes:
239            if node.nodeType == Node.DOCUMENT_TYPE_NODE:
240                if node.name == 'glade-interface':
241                    self._dom.removeChild(node)
242
243        # Strip unsupported tags
244        for tag in ['requires', 'requires-version']:
245            for child in self._dom.getElementsByTagName(tag):
246                child.parentNode.removeChild(child)
247
248        if self.root:
249            self._strip_root(self.root)
250
251        # Rename widget to object
252        objects = self._dom.getElementsByTagName("widget")
253        for node in objects:
254            node.tagName = "object"
255
256        for node in objects:
257            self._convert(node.getAttribute("class"), node)
258            if self._get_object(node.getAttribute('id')) is not None:
259                print("WARNING: duplicate id \"" + node.getAttribute('id') + "\"")
260            self.objects[node.getAttribute('id')] = node
261
262        # Convert Gazpachos UI tag
263        for node in self._dom.getElementsByTagName("ui"):
264            self._convert_ui(node)
265
266        # Convert accessibility tag
267        for node in self._dom.getElementsByTagName("accessibility"):
268            self._convert_accessibility(node)
269
270        root_objects = self.root_objects[:]
271        root_objects.sort(key=lambda a: a.getAttribute('id'), reverse=True)
272        for obj in root_objects:
273            self._interface.childNodes.insert(0, obj)
274
275    def _convert(self, klass, node):
276        if klass == 'GtkNotebook':
277            self._packing_prop_to_child_attr(node, "type", "tab")
278        elif klass in ['GtkExpander', 'GtkFrame']:
279            self._packing_prop_to_child_attr(
280                node, "type", "label_item", "label")
281        elif klass == "GtkMenuBar":
282            self._convert_menu(node)
283        elif klass == "GtkMenu":
284            # Only convert toplevel popups
285            if node.parentNode == self._interface:
286                self._convert_menu(node, popup=True)
287        elif klass in WINDOWS and self.skip_windows:
288            self._remove_window(node)
289
290        if self.target_version == "3.0":
291            if klass == "GtkComboBoxEntry":
292                node.setAttribute("class","GtkComboBox")
293                prop = self._dom.createElement("property")
294                prop.setAttribute("name", "has-entry")
295                prop.appendChild(self._dom.createTextNode("True"))
296                node.appendChild(prop)
297            elif klass == "GtkDialog":
298                for child in node.childNodes:
299                    if child.nodeType != Node.ELEMENT_NODE:
300                        continue
301                    if child.tagName != 'property':
302                        continue
303                    if (child.getAttribute("name") not in ("has-separator", "has_separator")):
304                        continue;
305                    node.removeChild(child)
306                    break
307
308        self._default_widget_converter(node)
309
310    def _default_widget_converter(self, node):
311        klass = node.getAttribute("class")
312        for prop in get_property_nodes(node):
313            prop_name = prop.getAttribute("name")
314            if prop_name == "sizegroup":
315                self._convert_sizegroup(node, prop)
316            elif prop_name == "tooltip" and klass != "GtkAction":
317                prop.setAttribute("name", "tooltip-text")
318            elif prop_name in ["response_id", 'response-id']:
319                # It does not make sense to convert responses when
320                # we're not going to output dialogs
321                if self.skip_windows:
322                    continue
323                object_id = node.getAttribute('id')
324                response = prop.childNodes[0].data
325                self._convert_dialog_response(node, object_id, response)
326                prop.parentNode.removeChild(prop)
327            elif prop_name == "adjustment":
328                self._convert_adjustment(prop)
329            elif prop_name == "items" and klass in ['GtkComboBox',
330                                                    'GtkComboBoxEntry']:
331                self._convert_combobox_items(node, prop)
332            elif prop_name == "text" and klass == 'GtkTextView':
333                self._convert_textview_text(prop)
334
335    def _remove_window(self, node):
336        object_node = get_object_node(get_child_nodes(node)[0])
337        parent = node.parentNode
338        parent.removeChild(node)
339        parent.appendChild(object_node)
340
341    def _convert_menu(self, node, popup=False):
342        if node.hasAttribute('constructor'):
343            return
344
345        uimgr = self._create_root_object('GtkUIManager',
346                                         template='uimanager')
347
348        if popup:
349            name = 'popup'
350        else:
351            name = 'menubar'
352
353        menu = self._dom.createElement(name)
354        menu.setAttribute('name', node.getAttribute('id'))
355        node.setAttribute('constructor', uimgr.getAttribute('id'))
356
357        for child in get_child_nodes(node):
358            obj_node = get_object_node(child)
359            item = self._convert_menuitem(uimgr, obj_node)
360            menu.appendChild(item)
361            child.removeChild(obj_node)
362            child.parentNode.removeChild(child)
363
364        ui = self._dom.createElement('ui')
365        uimgr.appendChild(ui)
366
367        ui.appendChild(menu)
368
369    def _convert_menuitem(self, uimgr, obj_node):
370        children = get_child_nodes(obj_node)
371        name = 'menuitem'
372        if children:
373            child_node = children[0]
374            menu_node = get_object_node(child_node)
375            # Can be GtkImage, which will take care of later.
376            if menu_node.getAttribute('class') == 'GtkMenu':
377                name = 'menu'
378
379        object_class = obj_node.getAttribute('class')
380        if object_class in ['GtkMenuItem',
381                            'GtkImageMenuItem',
382                            'GtkCheckMenuItem',
383                            'GtkRadioMenuItem']:
384            menu = self._dom.createElement(name)
385        elif object_class == 'GtkSeparatorMenuItem':
386            return self._dom.createElement('separator')
387        else:
388            raise NotImplementedError(object_class)
389
390        menu.setAttribute('action', obj_node.getAttribute('id'))
391        self._add_action_from_menuitem(uimgr, obj_node)
392        if children:
393            for child in get_child_nodes(menu_node):
394                obj_node = get_object_node(child)
395                item = self._convert_menuitem(uimgr, obj_node)
396                menu.appendChild(item)
397                child.removeChild(obj_node)
398                child.parentNode.removeChild(child)
399        return menu
400
401    def _menuitem_to_action(self, node, properties):
402        copy_properties(node, ['label', 'tooltip'], properties)
403
404    def _togglemenuitem_to_action(self, node, properties):
405        self._menuitem_to_action(node, properties)
406        copy_properties(node, ['active'], properties)
407
408    def _radiomenuitem_to_action(self, node, properties):
409        self._togglemenuitem_to_action(node, properties)
410        copy_properties(node, ['group'], properties)
411
412    def _add_action_from_menuitem(self, uimgr, node):
413        properties = {}
414        object_class = node.getAttribute('class')
415        object_id = node.getAttribute('id')
416        if object_class == 'GtkMenuItem':
417            name = 'GtkAction'
418            self._menuitem_to_action(node, properties)
419        elif object_class == 'GtkCheckMenuItem':
420            name = 'GtkToggleAction'
421            self._togglemenuitem_to_action(node, properties)
422        elif object_class == 'GtkRadioMenuItem':
423            name = 'GtkRadioAction'
424            self._radiomenuitem_to_action(node, properties)
425        elif object_class == 'GtkImageMenuItem':
426            name = 'GtkAction'
427            children = get_child_nodes(node)
428            if (children and
429                children[0].getAttribute('internal-child') == 'image'):
430                image = get_object_node(children[0])
431                child = get_property_node(image, 'stock')
432                if child is not None:
433                    properties['stock_id'] = child
434            self._menuitem_to_action(node, properties)
435        elif object_class == 'GtkSeparatorMenuItem':
436            return
437        else:
438            raise NotImplementedError(object_class)
439
440        if get_property(node, 'use_stock') == 'True':
441            if 'label' in properties:
442                properties['stock_id'] = properties['label']
443                del properties['label']
444
445        properties['name'] = object_id
446        action = self._create_object(name,
447                                     object_id,
448                                     properties=properties)
449        for signal in get_signal_nodes(node):
450            signal_name = signal.getAttribute('name')
451            if signal_name in ['activate', 'toggled']:
452                action.appendChild(signal)
453            else:
454                print('Unhandled signal %s::%s' % (node.getAttribute('class'),
455                                                   signal_name))
456
457        if not uimgr.childNodes:
458            child = self._dom.createElement('child')
459            uimgr.appendChild(child)
460
461            group = self._create_object('GtkActionGroup', None,
462                                        template='actiongroup')
463            child.appendChild(group)
464        else:
465            group = uimgr.childNodes[0].childNodes[0]
466
467        child = self._dom.createElement('child')
468        group.appendChild(child)
469        child.appendChild(action)
470
471        for accelerator in get_accelerator_nodes(node):
472            signal_name = accelerator.getAttribute('signal')
473            if signal_name != 'activate':
474                print('Unhandled accelerator signal for %s::%s' % (
475                    node.getAttribute('class'), signal_name))
476                continue
477            accelerator.removeAttribute('signal')
478            child.appendChild(accelerator)
479
480    def _convert_sizegroup(self, node, prop):
481        # This is Gazpacho only
482        node.removeChild(prop)
483        obj = self._get_object(prop.childNodes[0].data)
484        if obj is None:
485            widgets = self._get_objects_by_attr("class", "GtkSizeGroup")
486            if widgets:
487                obj = widgets[-1]
488            else:
489                obj = self._create_root_object('GtkSizeGroup',
490                                               template='sizegroup')
491
492        widgets = obj.getElementsByTagName("widgets")
493        if widgets:
494            assert len(widgets) == 1
495            widgets = widgets[0]
496        else:
497            widgets = self._dom.createElement("widgets")
498            obj.appendChild(widgets)
499
500        member = self._dom.createElement("widget")
501        member.setAttribute("name", node.getAttribute("id"))
502        widgets.appendChild(member)
503
504    def _convert_dialog_response(self, node, object_name, response):
505        # 1) Get parent dialog node
506        while True:
507            # If we can't find the parent dialog, give up
508            if node == self._dom:
509                return
510
511            if (node.tagName == 'object' and
512                node.getAttribute('class') in DIALOGS):
513                dialog = node
514                break
515            node = node.parentNode
516            assert node
517
518        # 2) Get dialogs action-widgets tag, create if not found
519        for child in dialog.childNodes:
520            if child.nodeType != Node.ELEMENT_NODE:
521                continue
522            if child.tagName == 'action-widgets':
523                actions = child
524                break
525        else:
526            actions = self._dom.createElement("action-widgets")
527            dialog.appendChild(actions)
528
529        # 3) Add action-widget tag for the response
530        action = self._dom.createElement("action-widget")
531        action.setAttribute("response", response)
532        action.appendChild(self._dom.createTextNode(object_name))
533        actions.appendChild(action)
534
535    def _convert_adjustment(self, prop):
536        properties = {}
537        if prop.childNodes:
538            data = prop.childNodes[0].data
539            value, lower, upper, step, page, page_size = data.split(' ')
540            properties.update(value=value,
541                              lower=lower,
542                              upper=upper,
543                              step_increment=step,
544                              page_increment=page,
545                              page_size=page_size)
546        else:
547            prop.appendChild(self._dom.createTextNode(""))
548
549        adj = self._create_root_object("GtkAdjustment",
550                                       template='adjustment',
551                                       properties=properties)
552        prop.childNodes[0].data = adj.getAttribute('id')
553
554    def _convert_combobox_items(self, node, prop):
555        parent = prop.parentNode
556        if not prop.childNodes:
557            parent.removeChild(prop)
558            return
559
560        translatable_attr = prop.attributes.get('translatable')
561        translatable = translatable_attr is not None and translatable_attr.value == 'yes'
562        has_context_attr = prop.attributes.get('context')
563        has_context = has_context_attr is not None and has_context_attr.value == 'yes'
564        comments_attr = prop.attributes.get('comments')
565        comments = comments_attr is not None and comments_attr.value or None
566
567        value = prop.childNodes[0].data
568        model = self._create_root_object("GtkListStore",
569                                         template="model")
570
571        columns = self._dom.createElement('columns')
572        model.appendChild(columns)
573
574        column = self._dom.createElement('column')
575        column.setAttribute('type', 'gchararray')
576        columns.appendChild(column)
577
578        data = self._dom.createElement('data')
579        model.appendChild(data)
580
581        if value.endswith('\n'):
582            value = value[:-1]
583        for item in value.split('\n'):
584            row = self._dom.createElement('row')
585            data.appendChild(row)
586
587            col = self._dom.createElement('col')
588            col.setAttribute('id', '0')
589            if translatable:
590                col.setAttribute('translatable', 'yes')
591            if has_context:
592                splitting = item.split('|', 1)
593                if len(splitting) == 2:
594                    context, item = splitting
595                    col.setAttribute('context', context)
596            if comments is not None:
597                col.setAttribute('comments', comments)
598            col.appendChild(self._dom.createTextNode(item))
599            row.appendChild(col)
600
601        model_prop = self._dom.createElement('property')
602        model_prop.setAttribute('name', 'model')
603        model_prop.appendChild(
604            self._dom.createTextNode(model.getAttribute('id')))
605        parent.appendChild(model_prop)
606
607        parent.removeChild(prop)
608
609        child = self._dom.createElement('child')
610        node.appendChild(child)
611        cell_renderer = self._create_object('GtkCellRendererText', None,
612                                            template='renderer')
613        child.appendChild(cell_renderer)
614
615        attributes = self._dom.createElement('attributes')
616        child.appendChild(attributes)
617
618        attribute = self._dom.createElement('attribute')
619        attributes.appendChild(attribute)
620        attribute.setAttribute('name', 'text')
621        attribute.appendChild(self._dom.createTextNode('0'))
622
623    def _convert_textview_text(self, prop):
624        if not prop.childNodes:
625            prop.parentNode.removeChild(prop)
626            return
627
628        data = prop.childNodes[0].data
629        if prop.hasAttribute('translatable'):
630            prop.removeAttribute('translatable')
631        tbuffer = self._create_root_object("GtkTextBuffer",
632                                           template='textbuffer',
633                                           properties=dict(text=data))
634        prop.childNodes[0].data = tbuffer.getAttribute('id')
635        prop.setAttribute('name', 'buffer')
636
637    def _packing_prop_to_child_attr(self, node, prop_name, prop_val,
638                                   attr_val=None):
639        for child in get_child_nodes(node):
640            packing_props = [p for p in child.childNodes if p.nodeName == "packing"]
641            if not packing_props:
642                continue
643            assert len(packing_props) == 1
644            packing_prop = packing_props[0]
645            properties = packing_prop.getElementsByTagName("property")
646            for prop in properties:
647                if (prop.getAttribute("name") != prop_name or
648                    prop.childNodes[0].data != prop_val):
649                    continue
650                packing_prop.removeChild(prop)
651                child.setAttribute(prop_name, attr_val or prop_val)
652            if len(properties) == 1:
653                child.removeChild(packing_prop)
654
655    def _convert_ui(self, node):
656        cdata = node.childNodes[0]
657        data = cdata.toxml().strip()
658        if not data.startswith("<![CDATA[") or not data.endswith("]]>"):
659            return
660        data = data[9:-3]
661        child = minidom.parseString(data).childNodes[0]
662        nodes = child.childNodes[:]
663        for child_node in nodes:
664            node.appendChild(child_node)
665        node.removeChild(cdata)
666        if not node.hasAttribute("id"):
667            return
668
669        # Updating references made by widgets
670        parent_id = node.parentNode.getAttribute("id")
671        for widget in self._get_objects_by_attr("constructor",
672                                                node.getAttribute("id")):
673            widget.getAttributeNode("constructor").value = parent_id
674        node.removeAttribute("id")
675
676    def _convert_accessibility(self, node):
677        objectNode = node.parentNode
678        parent_id = objectNode.getAttribute("id")
679
680        properties = {}
681        for node in node.childNodes:
682            if node.nodeName == 'atkproperty':
683                node.tagName = 'property'
684                properties[node.getAttribute('name')] = node
685                node.parentNode.removeChild(node)
686            elif node.nodeName == 'atkrelation':
687                node.tagName = 'relation'
688                relation_type = node.getAttribute('type')
689                relation_type = relation_type.replace('_', '-')
690                node.setAttribute('type', relation_type)
691            elif node.nodeName == 'atkaction':
692                node.tagName = 'action'
693
694        if properties:
695            child = self._dom.createElement('child')
696            child.setAttribute("internal-child", "accessible")
697
698            atkobject = self._create_object(
699                "AtkObject", None,
700                template='a11y-%s' % (parent_id,),
701                properties=properties)
702            child.appendChild(atkobject)
703            objectNode.appendChild(child)
704
705    def _strip_root(self, root_name):
706        for widget in self._dom.getElementsByTagName("widget"):
707            if widget.getAttribute('id') == root_name:
708                break
709        else:
710            raise SystemExit("Could not find an object called `%s'" % (
711                root_name))
712
713        for child in self._interface.childNodes[:]:
714            if child.nodeType != Node.ELEMENT_NODE:
715                continue
716            child.parentNode.removeChild(child)
717
718        self._interface.appendChild(widget)
719
720
721def _indent(output):
722    if not subprocess:
723        return output
724
725    for directory in os.environ['PATH'].split(os.pathsep):
726        filename = os.path.join(directory, 'xmllint')
727        if os.path.exists(filename):
728            break
729    else:
730        return output
731
732    s = subprocess.Popen([filename, '--format', '-'],
733                         stdin=subprocess.PIPE,
734                         stdout=subprocess.PIPE)
735    s.stdin.write(output)
736    s.stdin.close()
737    return s.stdout.read()
738
739def usage():
740    print(__doc__)
741
742def main(args):
743    try:
744        opts, args = getopt.getopt(args[1:], "hwr:",
745                                   ["help",
746                                    "skip-windows",
747                                    "target-version=",
748                                    "root="])
749    except getopt.GetoptError:
750        usage()
751        return 2
752
753    if len(args) != 2:
754        usage()
755        return 2
756
757    input_filename, output_filename = args
758
759    skip_windows = False
760    split = False
761    root = None
762    target_version = "2.0"
763    for o, a in opts:
764        if o in ("-h", "--help"):
765            usage()
766            sys.exit()
767        elif o in ("-r", "--root"):
768            root = a
769        elif o in ("-w", "--skip-windows"):
770            skip_windows = True
771        elif o in ("-t", "--target-version"):
772            target_version = a
773
774    conv = GtkBuilderConverter(skip_windows=skip_windows,
775                               target_version=target_version,
776                               root=root)
777    conv.parse_file(input_filename)
778
779    xml = _indent(conv.to_xml())
780    if output_filename == "-":
781        if isinstance(xml, str):
782            print(xml)
783        else:
784            print(xml.decode(sys.stdout.encoding))
785    else:
786        open(output_filename, 'wb').write(xml)
787        print("Wrote", output_filename)
788
789    return 0
790
791if __name__ == "__main__":
792    sys.exit(main(sys.argv))
793