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