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