#!/usr/bin/env python
#
-# Copyright (C) 2006-2007 Async Open Source
+# Copyright (C) 2006-2008 Async Open Source
# Henrique Romano <henrique@async.com.br>
# Johan Dahlin <jdahlin@async.com.br>
#
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
-# This is a script that can convert libglade files to the new gtkbuilder format
-#
# TODO:
-# GtkComboBox.items -> GtkListStore
-# GtkTextView.text -> GtkTextBuffer
# Toolbars
+"""Usage: gtk-builder-convert [OPTION] [INPUT] [OUTPUT]
+Converts Glade files into XML files which can be loaded with GtkBuilder.
+The [INPUT] file is
+
+ -w, --skip-windows Convert everything but GtkWindow subclasses.
+ -r, --root Convert only widget named root and its children
+ -h, --help display this help and exit
+
+When OUTPUT is -, write to standard output.
+
+Examples:
+ gtk-builder-convert preference.glade preferences.ui
+
+Report bugs to http://bugzilla.gnome.org/."""
+
+import getopt
+import os
import sys
from xml.dom import minidom, Node
+DIALOGS = ['GtkDialog',
+ 'GtkFileChooserDialog',
+ 'GtkMessageDialog']
+WINDOWS = ['GtkWindow'] + DIALOGS
+
+# The subprocess is only available in Python 2.4+
+try:
+ import subprocess
+ subprocess # pyflakes
+except ImportError:
+ subprocess = None
+
def get_child_nodes(node):
+ assert node.tagName == 'object'
nodes = []
for child in node.childNodes:
- if child.nodeType == Node.TEXT_NODE:
+ if child.nodeType != Node.ELEMENT_NODE:
continue
if child.tagName != 'child':
continue
nodes.append(child)
return nodes
-def get_object_properties(node):
+def get_properties(node):
+ assert node.tagName == 'object'
properties = {}
for child in node.childNodes:
- if child.nodeType == Node.TEXT_NODE:
+ if child.nodeType != Node.ELEMENT_NODE:
continue
if child.tagName != 'property':
continue
properties[child.getAttribute('name')] = value
return properties
+def get_property(node, property_name):
+ assert node.tagName == 'object'
+ properties = get_properties(node)
+ return properties.get(property_name)
+
+def get_property_node(node, property_name):
+ assert node.tagName == 'object'
+ properties = {}
+ for child in node.childNodes:
+ if child.nodeType != Node.ELEMENT_NODE:
+ continue
+ if child.tagName != 'property':
+ continue
+ if child.getAttribute('name') == property_name:
+ return child
+
+def get_signal_nodes(node):
+ assert node.tagName == 'object'
+ signals = []
+ for child in node.childNodes:
+ if child.nodeType != Node.ELEMENT_NODE:
+ continue
+ if child.tagName == 'signal':
+ signals.append(child)
+ return signals
+
+def get_property_nodes(node):
+ assert node.tagName == 'object'
+ properties = []
+ for child in node.childNodes:
+ if child.nodeType != Node.ELEMENT_NODE:
+ continue
+ # FIXME: handle comments
+ if child.tagName == 'property':
+ properties.append(child)
+ return properties
+
+def get_accelerator_nodes(node):
+ assert node.tagName == 'object'
+ accelerators = []
+ for child in node.childNodes:
+ if child.nodeType != Node.ELEMENT_NODE:
+ continue
+ if child.tagName == 'accelerator':
+ accelerators.append(child)
+ return accelerators
+
def get_object_node(child_node):
- assert child_node.tagName == 'child'
+ assert child_node.tagName == 'child', child_node
nodes = []
for node in child_node.childNodes:
- if node.nodeType == Node.TEXT_NODE:
+ if node.nodeType != Node.ELEMENT_NODE:
continue
if node.tagName == 'object':
nodes.append(node)
assert len(nodes) == 1, nodes
return nodes[0]
+def copy_properties(node, props, prop_dict):
+ assert node.tagName == 'object'
+ for prop_name in props:
+ child = get_property_node(node, prop_name)
+ if child is not None:
+ prop_dict[prop_name] = child
+
+ return node
+
class GtkBuilderConverter(object):
+ def __init__(self, skip_windows, root):
+ self.skip_windows = skip_windows
+ self.root = root
+ self.root_objects = []
+ self.objects = {}
+
#
# Public API
#
# Private
#
- def _get_widget(self, name):
- result = self._get_widgets_by_attr("id", name)
- if len(result) > 1:
- raise ValueError(
- "It is not possible to have more than one "
- "widget with the same id (`%s')" % name)
- elif len(result) == 1:
- return result[0]
- return None
-
- def _get_widgets_by_attr(self, attribute, value):
+ def _get_object(self, name):
+ return self.objects.get(name)
+
+ def _get_objects_by_attr(self, attribute, value):
return [w for w in self._dom.getElementsByTagName("object")
if w.getAttribute(attribute) == value]
- def _create_object(self, obj_class, obj_id, **properties):
+ def _create_object(self, obj_class, obj_id, template=None, properties=None):
+ """
+ Creates a new <object> tag.
+ Optionally a name template can be provided which will be used
+ to avoid naming collisions.
+ The properties dictionary can either contain string values or Node
+ values. If a node is provided the name of the node will be overridden
+ by the dictionary key.
+
+ @param obj_class: class of the object (class tag)
+ @param obj_id: identifier of the object (id tag)
+ @param template: name template to use, for example 'button'
+ @param properties: dictionary of properties
+ @type properties: string or Node.
+ @returns: Newly created node of the object
+ """
+ if template is not None:
+ count = 1
+ while True:
+ obj_id = template + str(count)
+ widget = self._get_object(obj_id)
+ if widget is None:
+ break
+
+ count += 1
+
obj = self._dom.createElement('object')
obj.setAttribute('class', obj_class)
obj.setAttribute('id', obj_id)
- for name, value in properties.items():
- prop = self._dom.createElement('property')
- prop.setAttribute('name', name)
- prop.appendChild(self._dom.createTextNode(value))
- obj.appendChild(prop)
+ if properties:
+ for name, value in properties.items():
+ if isinstance(value, Node):
+ # Reuse the node, so translatable and context still will be
+ # set when converting nodes. See also #509153
+ prop = value
+ else:
+ prop = self._dom.createElement('property')
+ prop.appendChild(self._dom.createTextNode(value))
+
+ prop.setAttribute('name', str(name))
+ obj.appendChild(prop)
+ self.objects[obj_id] = obj
return obj
- def _get_property(self, node, property_name):
- properties = get_object_properties(node)
- return properties.get(property_name)
+ def _create_root_object(self, obj_class, template, properties=None):
+ obj = self._create_object(obj_class, None, template, properties)
+ self.root_objects.append(obj)
+ return obj
def _parse(self):
glade_iface = self._dom.getElementsByTagName("glade-interface")
assert glade_iface, ("Badly formed XML, there is "
"no <glade-interface> tag.")
+ # Rename glade-interface to interface
glade_iface[0].tagName = 'interface'
self._interface = glade_iface[0]
if node.name == 'glade-interface':
self._dom.removeChild(node)
- # Strip requires
- requires = self._dom.getElementsByTagName("requires")
- for require in requires:
- require.parentNode.childNodes.remove(require)
+ # Strip unsupported tags
+ for tag in ['requires', 'requires-version']:
+ for child in self._dom.getElementsByTagName(tag):
+ child.parentNode.removeChild(child)
- for child in self._dom.getElementsByTagName("accessibility"):
- child.parentNode.removeChild(child)
+ if self.root:
+ self._strip_root(self.root)
- for node in self._dom.getElementsByTagName("widget"):
+ # Rename widget to object
+ objects = self._dom.getElementsByTagName("widget")
+ for node in objects:
node.tagName = "object"
- for node in self._dom.getElementsByTagName("object"):
+ for node in objects:
self._convert(node.getAttribute("class"), node)
+ if self._get_object(node.getAttribute('id')) is not None:
+ print "WARNING: duplicate id \"" + node.getAttribute('id') + "\""
+ self.objects[node.getAttribute('id')] = node
# Convert Gazpachos UI tag
for node in self._dom.getElementsByTagName("ui"):
self._convert_ui(node)
+ # Convert accessibility tag
+ for node in self._dom.getElementsByTagName("accessibility"):
+ self._convert_accessibility(node)
+
+ # Output the newly created root objects and sort them
+ # by attribute id
+ # FIXME: Use sorted(self.root_objects,
+ # key=lambda n: n.getAttribute('id'),
+ # reverse=True):
+ # when we can depend on python 2.4 or higher
+ root_objects = self.root_objects[:]
+ root_objects.sort(lambda a, b: cmp(b.getAttribute('id'),
+ a.getAttribute('id')))
+ for obj in root_objects:
+ self._interface.childNodes.insert(0, obj)
+
def _convert(self, klass, node):
if klass == 'GtkNotebook':
self._packing_prop_to_child_attr(node, "type", "tab")
self._packing_prop_to_child_attr(
node, "type", "label_item", "label")
elif klass == "GtkMenuBar":
- if node.hasAttribute('constructor'):
- uimgr = self._get_widget('uimanager')
- else:
- uimgr = node.ownerDocument.createElement('object')
- uimgr.setAttribute('class', 'GtkUIManager')
- uimgr.setAttribute('id', 'uimanager1')
- self._interface.childNodes.insert(0, uimgr)
- self._convert_menubar(uimgr, node)
-
+ self._convert_menu(node)
+ elif klass == "GtkMenu":
+ # Only convert toplevel popups
+ if node.parentNode == self._interface:
+ self._convert_menu(node, popup=True)
+ elif klass in WINDOWS and self.skip_windows:
+ self._remove_window(node)
self._default_widget_converter(node)
def _default_widget_converter(self, node):
klass = node.getAttribute("class")
- for prop in node.getElementsByTagName("property"):
- if prop.parentNode is not node:
- continue
+ for prop in get_property_nodes(node):
prop_name = prop.getAttribute("name")
if prop_name == "sizegroup":
self._convert_sizegroup(node, prop)
elif prop_name == "tooltip" and klass != "GtkAction":
prop.setAttribute("name", "tooltip-text")
elif prop_name in ["response_id", 'response-id']:
+ # It does not make sense to convert responses when
+ # we're not going to output dialogs
+ if self.skip_windows:
+ continue
object_id = node.getAttribute('id')
response = prop.childNodes[0].data
self._convert_dialog_response(node, object_id, response)
prop.parentNode.removeChild(prop)
+ elif prop_name == "adjustment":
+ self._convert_adjustment(prop)
+ elif prop_name == "items" and klass in ['GtkComboBox',
+ 'GtkComboBoxEntry']:
+ self._convert_combobox_items(node, prop)
+ elif prop_name == "text" and klass == 'GtkTextView':
+ self._convert_textview_text(prop)
+
+ def _remove_window(self, node):
+ object_node = get_object_node(get_child_nodes(node)[0])
+ parent = node.parentNode
+ parent.removeChild(node)
+ parent.appendChild(object_node)
+
+ def _convert_menu(self, node, popup=False):
+ if node.hasAttribute('constructor'):
+ return
+
+ uimgr = self._create_root_object('GtkUIManager',
+ template='uimanager')
+
+ if popup:
+ name = 'popup'
+ else:
+ name = 'menubar'
- def _convert_menubar(self, uimgr, node):
- menubar = self._dom.createElement('menubar')
- menubar.setAttribute('name', node.getAttribute('id'))
+ menu = self._dom.createElement(name)
+ menu.setAttribute('name', node.getAttribute('id'))
node.setAttribute('constructor', uimgr.getAttribute('id'))
for child in get_child_nodes(node):
obj_node = get_object_node(child)
- self._convert_menuitem(uimgr, menubar, obj_node)
+ item = self._convert_menuitem(uimgr, obj_node)
+ menu.appendChild(item)
child.removeChild(obj_node)
child.parentNode.removeChild(child)
ui = self._dom.createElement('ui')
uimgr.appendChild(ui)
- ui.appendChild(menubar)
+ ui.appendChild(menu)
- def _convert_menuitem(self, uimgr, menubar, obj_node):
+ def _convert_menuitem(self, uimgr, obj_node):
children = get_child_nodes(obj_node)
name = 'menuitem'
if children:
name = 'menu'
object_class = obj_node.getAttribute('class')
- if object_class in ['GtkMenuItem', 'GtkImageMenuItem']:
+ if object_class in ['GtkMenuItem',
+ 'GtkImageMenuItem',
+ 'GtkCheckMenuItem',
+ 'GtkRadioMenuItem']:
menu = self._dom.createElement(name)
elif object_class == 'GtkSeparatorMenuItem':
- menu = self._dom.createElement('sep')
+ return self._dom.createElement('separator')
else:
raise NotImplementedError(object_class)
- menu.setAttribute('name', obj_node.getAttribute('id'))
+
menu.setAttribute('action', obj_node.getAttribute('id'))
- menubar.appendChild(menu)
self._add_action_from_menuitem(uimgr, obj_node)
if children:
for child in get_child_nodes(menu_node):
obj_node = get_object_node(child)
- self._convert_menuitem(uimgr, menu, obj_node)
+ item = self._convert_menuitem(uimgr, obj_node)
+ menu.appendChild(item)
child.removeChild(obj_node)
child.parentNode.removeChild(child)
+ return menu
+
+ def _menuitem_to_action(self, node, properties):
+ copy_properties(node, ['label', 'tooltip'], properties)
+
+ def _togglemenuitem_to_action(self, node, properties):
+ self._menuitem_to_action(node, properties)
+ copy_properties(node, ['active'], properties)
+
+ def _radiomenuitem_to_action(self, node, properties):
+ self._togglemenuitem_to_action(node, properties)
+ copy_properties(node, ['group'], properties)
def _add_action_from_menuitem(self, uimgr, node):
properties = {}
object_class = node.getAttribute('class')
object_id = node.getAttribute('id')
- if object_class == 'GtkImageMenuItem':
+ if object_class == 'GtkMenuItem':
+ name = 'GtkAction'
+ self._menuitem_to_action(node, properties)
+ elif object_class == 'GtkCheckMenuItem':
+ name = 'GtkToggleAction'
+ self._togglemenuitem_to_action(node, properties)
+ elif object_class == 'GtkRadioMenuItem':
+ name = 'GtkRadioAction'
+ self._radiomenuitem_to_action(node, properties)
+ elif object_class == 'GtkImageMenuItem':
name = 'GtkAction'
children = get_child_nodes(node)
if (children and
children[0].getAttribute('internal-child') == 'image'):
image = get_object_node(children[0])
- properties['stock_id'] = self._get_property(image, 'stock')
- elif object_class == 'GtkMenuItem':
- name = 'GtkAction'
- label = self._get_property(node, 'label')
- if label is not None:
- properties['label'] = label
+ child = get_property_node(image, 'stock')
+ if child is not None:
+ properties['stock_id'] = child
+ self._menuitem_to_action(node, properties)
elif object_class == 'GtkSeparatorMenuItem':
return
else:
raise NotImplementedError(object_class)
- if self._get_property(node, 'use_stock') == 'True':
- properties['stock_id'] = self._get_property(node, 'label')
+ if get_property(node, 'use_stock') == 'True':
+ if 'label' in properties:
+ properties['stock_id'] = properties['label']
+ del properties['label']
+
properties['name'] = object_id
action = self._create_object(name,
object_id,
- **properties)
+ properties=properties)
+ for signal in get_signal_nodes(node):
+ signal_name = signal.getAttribute('name')
+ if signal_name in ['activate', 'toggled']:
+ action.appendChild(signal)
+ else:
+ print 'Unhandled signal %s::%s' % (node.getAttribute('class'),
+ signal_name)
+
if not uimgr.childNodes:
child = self._dom.createElement('child')
uimgr.appendChild(child)
- group = self._create_object('GtkActionGroup', 'actiongroup1')
+ group = self._create_object('GtkActionGroup', None,
+ template='actiongroup')
child.appendChild(group)
else:
group = uimgr.childNodes[0].childNodes[0]
group.appendChild(child)
child.appendChild(action)
+ for accelerator in get_accelerator_nodes(node):
+ signal_name = accelerator.getAttribute('signal')
+ if signal_name != 'activate':
+ print 'Unhandled accelerator signal for %s::%s' % (
+ node.getAttribute('class'), signal_name)
+ continue
+ accelerator.removeAttribute('signal')
+ child.appendChild(accelerator)
+
def _convert_sizegroup(self, node, prop):
# This is Gazpacho only
node.removeChild(prop)
- obj = self._get_widget(prop.childNodes[0].data)
+ obj = self._get_object(prop.childNodes[0].data)
if obj is None:
- widgets = self._get_widgets_by_attr("class", "GtkSizeGroup")
+ widgets = self._get_objects_by_attr("class", "GtkSizeGroup")
if widgets:
obj = widgets[-1]
else:
- obj = self._dom.createElement("object")
- obj.setAttribute("class", "GtkSizeGroup")
- obj.setAttribute("id", "sizegroup1")
- self._interface.insertBefore(
- obj, self._interface.childNodes[0])
+ obj = self._create_root_object('GtkSizeGroup',
+ template='sizegroup')
widgets = obj.getElementsByTagName("widgets")
if widgets:
def _convert_dialog_response(self, node, object_name, response):
# 1) Get parent dialog node
while True:
+ # If we can't find the parent dialog, give up
+ if node == self._dom:
+ return
+
if (node.tagName == 'object' and
- node.getAttribute('class') == 'GtkDialog'):
+ node.getAttribute('class') in DIALOGS):
dialog = node
break
node = node.parentNode
# 2) Get dialogs action-widgets tag, create if not found
for child in dialog.childNodes:
- if child.nodeType == Node.TEXT_NODE:
+ if child.nodeType != Node.ELEMENT_NODE:
continue
if child.tagName == 'action-widgets':
actions = child
action.appendChild(self._dom.createTextNode(object_name))
actions.appendChild(action)
+ def _convert_adjustment(self, prop):
+ properties = {}
+ if prop.childNodes:
+ data = prop.childNodes[0].data
+ value, lower, upper, step, page, page_size = data.split(' ')
+ properties.update(value=value,
+ lower=lower,
+ upper=upper,
+ step_increment=step,
+ page_increment=page,
+ page_size=page_size)
+ else:
+ prop.appendChild(self._dom.createTextNode(""))
+
+ adj = self._create_root_object("GtkAdjustment",
+ template='adjustment',
+ properties=properties)
+ prop.childNodes[0].data = adj.getAttribute('id')
+
+ def _convert_combobox_items(self, node, prop):
+ parent = prop.parentNode
+ if not prop.childNodes:
+ parent.removeChild(prop)
+ return
+
+ translatable_attr = prop.attributes.get('translatable')
+ translatable = translatable_attr is not None and translatable_attr.value == 'yes'
+ has_context_attr = prop.attributes.get('context')
+ has_context = has_context_attr is not None and has_context_attr.value == 'yes'
+ comments_attr = prop.attributes.get('comments')
+ comments = comments_attr is not None and comments_attr.value or None
+
+ value = prop.childNodes[0].data
+ model = self._create_root_object("GtkListStore",
+ template="model")
+
+ columns = self._dom.createElement('columns')
+ model.appendChild(columns)
+
+ column = self._dom.createElement('column')
+ column.setAttribute('type', 'gchararray')
+ columns.appendChild(column)
+
+ data = self._dom.createElement('data')
+ model.appendChild(data)
+
+ if value.endswith('\n'):
+ value = value[:-1]
+ for item in value.split('\n'):
+ row = self._dom.createElement('row')
+ data.appendChild(row)
+
+ col = self._dom.createElement('col')
+ col.setAttribute('id', '0')
+ if translatable:
+ col.setAttribute('translatable', 'yes')
+ if has_context:
+ splitting = item.split('|', 1)
+ if len(splitting) == 2:
+ context, item = splitting
+ col.setAttribute('context', context)
+ if comments is not None:
+ col.setAttribute('comments', comments)
+ col.appendChild(self._dom.createTextNode(item))
+ row.appendChild(col)
+
+ model_prop = self._dom.createElement('property')
+ model_prop.setAttribute('name', 'model')
+ model_prop.appendChild(
+ self._dom.createTextNode(model.getAttribute('id')))
+ parent.appendChild(model_prop)
+
+ parent.removeChild(prop)
+
+ child = self._dom.createElement('child')
+ node.appendChild(child)
+ cell_renderer = self._create_object('GtkCellRendererText', None,
+ template='renderer')
+ child.appendChild(cell_renderer)
+
+ attributes = self._dom.createElement('attributes')
+ child.appendChild(attributes)
+
+ attribute = self._dom.createElement('attribute')
+ attributes.appendChild(attribute)
+ attribute.setAttribute('name', 'text')
+ attribute.appendChild(self._dom.createTextNode('0'))
+
+ def _convert_textview_text(self, prop):
+ if not prop.childNodes:
+ prop.parentNode.removeChild(prop)
+ return
+
+ data = prop.childNodes[0].data
+ if prop.hasAttribute('translatable'):
+ prop.removeAttribute('translatable')
+ tbuffer = self._create_root_object("GtkTextBuffer",
+ template='textbuffer',
+ properties=dict(text=data))
+ prop.childNodes[0].data = tbuffer.getAttribute('id')
+ prop.setAttribute('name', 'buffer')
+
def _packing_prop_to_child_attr(self, node, prop_name, prop_val,
attr_val=None):
- for child in node.getElementsByTagName("child"):
+ for child in get_child_nodes(node):
packing_props = [p for p in child.childNodes if p.nodeName == "packing"]
if not packing_props:
continue
# Updating references made by widgets
parent_id = node.parentNode.getAttribute("id")
- for widget in self._get_widgets_by_attr("constructor",
+ for widget in self._get_objects_by_attr("constructor",
node.getAttribute("id")):
widget.getAttributeNode("constructor").value = parent_id
node.removeAttribute("id")
+ def _convert_accessibility(self, node):
+ objectNode = node.parentNode
+ parent_id = objectNode.getAttribute("id")
+
+ properties = {}
+ for node in node.childNodes:
+ if node.nodeName == 'atkproperty':
+ node.tagName = 'property'
+ properties[node.getAttribute('name')] = node
+ node.parentNode.removeChild(node)
+ elif node.nodeName == 'atkrelation':
+ node.tagName = 'relation'
+ relation_type = node.getAttribute('type')
+ relation_type = relation_type.replace('_', '-')
+ node.setAttribute('type', relation_type)
+ elif node.nodeName == 'atkaction':
+ node.tagName = 'action'
+
+ if properties:
+ child = self._dom.createElement('child')
+ child.setAttribute("internal-child", "accessible")
+
+ atkobject = self._create_object(
+ "AtkObject", None,
+ template='a11y-%s' % (parent_id,),
+ properties=properties)
+ child.appendChild(atkobject)
+ objectNode.appendChild(child)
+
+ def _strip_root(self, root_name):
+ for widget in self._dom.getElementsByTagName("widget"):
+ if widget.getAttribute('id') == root_name:
+ break
+ else:
+ raise SystemExit("Could not find an object called `%s'" % (
+ root_name))
+
+ for child in self._interface.childNodes[:]:
+ if child.nodeType != Node.ELEMENT_NODE:
+ continue
+ child.parentNode.removeChild(child)
-def main():
- if len(sys.argv) < 2:
- print "Usage: %s filename.glade" % (sys.argv[0],)
- return
- conv = GtkBuilderConverter()
- conv.parse_file(sys.argv[1])
- print conv.to_xml()
+ self._interface.appendChild(widget)
+
+
+def _indent(output):
+ if not subprocess:
+ return output
+
+ for directory in os.environ['PATH'].split(os.pathsep):
+ filename = os.path.join(directory, 'xmllint')
+ if os.path.exists(filename):
+ break
+ else:
+ return output
+
+ s = subprocess.Popen([filename, '--format', '-'],
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE)
+ s.stdin.write(output)
+ s.stdin.close()
+ return s.stdout.read()
+
+def usage():
+ print __doc__
+
+def main(args):
+ try:
+ opts, args = getopt.getopt(args[1:], "hwr:",
+ ["help", "skip-windows", "root="])
+ except getopt.GetoptError:
+ usage()
+ return 2
+
+ if len(args) != 2:
+ usage()
+ return 2
+
+ input_filename, output_filename = args
+
+ skip_windows = False
+ split = False
+ root = None
+ for o, a in opts:
+ if o in ("-h", "--help"):
+ usage()
+ sys.exit()
+ elif o in ("-r", "--root"):
+ root = a
+ elif o in ("-w", "--skip-windows"):
+ skip_windows = True
+
+ conv = GtkBuilderConverter(skip_windows=skip_windows,
+ root=root)
+ conv.parse_file(input_filename)
+
+ xml = _indent(conv.to_xml())
+ if output_filename == "-":
+ print xml
+ else:
+ open(output_filename, 'w').write(xml)
+ print "Wrote", output_filename
+
+ return 0
if __name__ == "__main__":
- main()
+ sys.exit(main(sys.argv))