|
- #!/usr/bin/env python
- #
- # Copyright (C) 2006-2008 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:
- # 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.ELEMENT_NODE:
- continue
- if child.tagName != 'child':
- continue
- nodes.append(child)
- return nodes
-
- def get_properties(node):
- assert node.tagName == 'object'
- properties = {}
- for child in node.childNodes:
- if child.nodeType != Node.ELEMENT_NODE:
- continue
- if child.tagName != 'property':
- continue
- value = child.childNodes[0].data
- 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', child_node
- nodes = []
- for node in child_node.childNodes:
- 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
- #
-
- 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_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, 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)
- 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 _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]
-
- # 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 unsupported tags
- for tag in ['requires', 'requires-version']:
- for child in self._dom.getElementsByTagName(tag):
- child.parentNode.removeChild(child)
-
- if self.root:
- self._strip_root(self.root)
-
- # Rename widget to object
- objects = self._dom.getElementsByTagName("widget")
- for node in objects:
- node.tagName = "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")
- elif klass in ['GtkExpander', 'GtkFrame']:
- self._packing_prop_to_child_attr(
- node, "type", "label_item", "label")
- elif klass == "GtkMenuBar":
- 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 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'
-
- 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)
- 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(menu)
-
- def _convert_menuitem(self, uimgr, 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',
- 'GtkCheckMenuItem',
- 'GtkRadioMenuItem']:
- menu = self._dom.createElement(name)
- elif object_class == 'GtkSeparatorMenuItem':
- return self._dom.createElement('separator')
- else:
- raise NotImplementedError(object_class)
-
- menu.setAttribute('action', obj_node.getAttribute('id'))
- self._add_action_from_menuitem(uimgr, obj_node)
- if children:
- for child in get_child_nodes(menu_node):
- obj_node = get_object_node(child)
- 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 == '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])
- 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 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)
- 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', None,
- template='actiongroup')
- child.appendChild(group)
- else:
- group = uimgr.childNodes[0].childNodes[0]
-
- child = self._dom.createElement('child')
- 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_object(prop.childNodes[0].data)
- if obj is None:
- widgets = self._get_objects_by_attr("class", "GtkSizeGroup")
- if widgets:
- obj = widgets[-1]
- else:
- obj = self._create_root_object('GtkSizeGroup',
- template='sizegroup')
-
- 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 we can't find the parent dialog, give up
- if node == self._dom:
- return
-
- if (node.tagName == 'object' and
- node.getAttribute('class') in DIALOGS):
- 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.ELEMENT_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):
- 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 get_child_nodes(node):
- 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_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)
-
- 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))
|