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