gtk2/gtk/gtk-builder-convert
Johan Dahlin 0b3728b3cd Add a --root option. Filter empty properties. Convert GtkAdjustments.
2007-07-03  Johan Dahlin  <jdahlin@async.com.br>

    * gtk/gtk-builder-convert (GtkBuilderConverter._strip_root): Add a
    --root option. Filter empty properties. Convert GtkAdjustments.


svn path=/trunk/; revision=18350
2007-07-03 03:28:39 +00:00

504 lines
17 KiB
Python
Executable File

#!/usr/bin/env python
#
# Copyright (C) 2006-2007 Async Open Source
# Henrique Romano <henrique@async.com.br>
# Johan Dahlin <jdahlin@async.com.br>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# 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 bug 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 input.
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
WINDOWS = ['GtkWindow',
'GtkDialog',
'GtkFileChooserDialog',
'GtkMessageDialog']
# The subprocess is only available in Python 2.4+
try:
import subprocess
subprocess # pyflakes
except ImportError:
subprocess = None
def get_child_nodes(node):
nodes = []
for child in node.childNodes:
if child.nodeType == Node.TEXT_NODE:
continue
if child.tagName != 'child':
continue
nodes.append(child)
return nodes
def get_object_properties(node):
properties = {}
for child in node.childNodes:
if child.nodeType == Node.TEXT_NODE:
continue
if child.tagName != 'property':
continue
value = child.childNodes[0].data
properties[child.getAttribute('name')] = value
return properties
def get_object_node(child_node):
assert child_node.tagName == 'child'
nodes = []
for node in child_node.childNodes:
if node.nodeType == Node.TEXT_NODE:
continue
if node.tagName == 'object':
nodes.append(node)
assert len(nodes) == 1, nodes
return nodes[0]
class GtkBuilderConverter(object):
def __init__(self, skip_windows, root):
self.skip_windows = skip_windows
self.root = root
#
# Public API
#
def parse_file(self, file):
self._dom = minidom.parse(file)
self._parse()
def parse_buffer(self, buffer):
self._dom = minidom.parseString(buffer)
self._parse()
def to_xml(self):
xml = self._dom.toprettyxml("", "")
return xml.encode('utf-8')
#
# 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):
return [w for w in self._dom.getElementsByTagName("object")
if w.getAttribute(attribute) == value]
def _create_object(self, obj_class, obj_id, **properties):
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)
return obj
def _get_property(self, node, property_name):
properties = get_object_properties(node)
return properties.get(property_name)
def _parse(self):
glade_iface = self._dom.getElementsByTagName("glade-interface")
assert glade_iface, ("Badly formed XML, there is "
"no <glade-interface> tag.")
glade_iface[0].tagName = 'interface'
self._interface = glade_iface[0]
# Remove glade-interface doc type
for node in self._dom.childNodes:
if node.nodeType == Node.DOCUMENT_TYPE_NODE:
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)
for child in self._dom.getElementsByTagName("accessibility"):
child.parentNode.removeChild(child)
for node in self._dom.getElementsByTagName("widget"):
node.tagName = "object"
if self.root:
self._strip_root(self.root)
for node in self._dom.getElementsByTagName("object"):
self._convert(node.getAttribute("class"), node)
for node in self._dom.getElementsByTagName('property'):
if not node.childNodes:
node.parentNode.removeChild(node)
# Convert Gazpachos UI tag
for node in self._dom.getElementsByTagName("ui"):
self._convert_ui(node)
def _convert(self, klass, node):
if klass == 'GtkNotebook':
self._packing_prop_to_child_attr(node, "type", "tab")
elif klass in ['GtkExpander', 'GtkFrame']:
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)
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
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)
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_menubar(self, uimgr, node):
menubar = self._dom.createElement('menubar')
menubar.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)
child.removeChild(obj_node)
child.parentNode.removeChild(child)
ui = self._dom.createElement('ui')
uimgr.appendChild(ui)
ui.appendChild(menubar)
def _convert_menuitem(self, uimgr, menubar, obj_node):
children = get_child_nodes(obj_node)
name = 'menuitem'
if children:
child_node = children[0]
menu_node = get_object_node(child_node)
# Can be GtkImage, which will take care of later.
if menu_node.getAttribute('class') == 'GtkMenu':
name = 'menu'
object_class = obj_node.getAttribute('class')
if object_class in ['GtkMenuItem', 'GtkImageMenuItem']:
menu = self._dom.createElement(name)
elif object_class == 'GtkSeparatorMenuItem':
menu = self._dom.createElement('sep')
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)
child.removeChild(obj_node)
child.parentNode.removeChild(child)
def _add_action_from_menuitem(self, uimgr, node):
properties = {}
object_class = node.getAttribute('class')
object_id = node.getAttribute('id')
if 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
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')
properties['name'] = object_id
action = self._create_object(name,
object_id,
**properties)
if not uimgr.childNodes:
child = self._dom.createElement('child')
uimgr.appendChild(child)
group = self._create_object('GtkActionGroup', 'actiongroup1')
child.appendChild(group)
else:
group = uimgr.childNodes[0].childNodes[0]
child = self._dom.createElement('child')
group.appendChild(child)
child.appendChild(action)
def _convert_sizegroup(self, node, prop):
# This is Gazpacho only
node.removeChild(prop)
obj = self._get_widget(prop.childNodes[0].data)
if obj is None:
widgets = self._get_widgets_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])
widgets = obj.getElementsByTagName("widgets")
if widgets:
assert len(widgets) == 1
widgets = widgets[0]
else:
widgets = self._dom.createElement("widgets")
obj.appendChild(widgets)
member = self._dom.createElement("widget")
member.setAttribute("name", node.getAttribute("id"))
widgets.appendChild(member)
def _convert_dialog_response(self, node, object_name, response):
# 1) Get parent dialog node
while True:
if (node.tagName == 'object' and
node.getAttribute('class') == 'GtkDialog'):
dialog = node
break
node = node.parentNode
assert node
# 2) Get dialogs action-widgets tag, create if not found
for child in dialog.childNodes:
if child.nodeType == Node.TEXT_NODE:
continue
if child.tagName == 'action-widgets':
actions = child
break
else:
actions = self._dom.createElement("action-widgets")
dialog.appendChild(actions)
# 3) Add action-widget tag for the response
action = self._dom.createElement("action-widget")
action.setAttribute("response", response)
action.appendChild(self._dom.createTextNode(object_name))
actions.appendChild(action)
def _convert_adjustment(self, prop):
name = "adjustment1"
data = prop.childNodes[0].data
value, lower, upper, step, page, page_size = data.split(' ')
prop.childNodes[0].data = name
adj = self._create_object("GtkAdjustment", name,
value=value,
lower=lower,
upper=upper,
step_increment=step,
page_increment=page,
page_size=page_size)
self._interface.childNodes.insert(0, adj)
def _packing_prop_to_child_attr(self, node, prop_name, prop_val,
attr_val=None):
for child in node.getElementsByTagName("child"):
packing_props = [p for p in child.childNodes if p.nodeName == "packing"]
if not packing_props:
continue
assert len(packing_props) == 1
packing_prop = packing_props[0]
properties = packing_prop.getElementsByTagName("property")
for prop in properties:
if (prop.getAttribute("name") != prop_name or
prop.childNodes[0].data != prop_val):
continue
packing_prop.removeChild(prop)
child.setAttribute(prop_name, attr_val or prop_val)
if len(properties) == 1:
child.removeChild(packing_prop)
def _convert_ui(self, node):
cdata = node.childNodes[0]
data = cdata.toxml().strip()
if not data.startswith("<![CDATA[") or not data.endswith("]]>"):
return
data = data[9:-3]
child = minidom.parseString(data).childNodes[0]
nodes = child.childNodes[:]
for child_node in nodes:
node.appendChild(child_node)
node.removeChild(cdata)
if not node.hasAttribute("id"):
return
# Updating references made by widgets
parent_id = node.parentNode.getAttribute("id")
for widget in self._get_widgets_by_attr("constructor",
node.getAttribute("id")):
widget.getAttributeNode("constructor").value = parent_id
node.removeAttribute("id")
def _strip_root(self, root_name):
widget = self._get_widget(root_name)
if widget is None:
raise SystemExit("Could not find an object called `%s'" % (
root_name))
# If it's already a root object, don't do anything
if widget.parentNode is self._interface:
return
for child in self._interface.childNodes[:]:
if child.nodeType != Node.ELEMENT_NODE:
continue
child.parentNode.removeChild(child)
widget.parentNode.removeChild(widget)
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__":
sys.exit(main(sys.argv))