12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148 |
- #!/usr/bin/env python
- # vim: set ts=4 sw=4 et: coding=UTF-8
- #
- # Copyright (c) 2010, Novell, Inc.
- #
- # 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.
- #
- # Authors: Vincent Untz <vuntz@gnome.org>
- # TODO: add alias support for choices
- # choices: 'this-is-an-alias' = 'real', 'other', 'real'
- # TODO: we don't support migrating a pair from a gconf schema. It has yet to be
- # seen in real-world usage, though.
- import os
- import sys
- import optparse
- try:
- from lxml import etree as ET
- except ImportError:
- try:
- from xml.etree import cElementTree as ET
- except ImportError:
- import cElementTree as ET
- GSETTINGS_SIMPLE_SCHEMA_INDENT = ' '
- TYPES_FOR_CHOICES = [ 's' ]
- TYPES_FOR_RANGE = [ 'y', 'n', 'q', 'i', 'u', 'x', 't', 'h', 'd' ]
- ######################################
- def is_schema_id_valid(id):
- # FIXME: there's currently no restriction on what an id should contain,
- # but there might be some later on
- return True
- def is_key_name_valid(name):
- # FIXME: we could check that name is valid ([-a-z0-9], no leading/trailing
- # -, no leading digit, 32 char max). Note that we don't want to validate
- # the key when converting from gconf, though, since gconf keys use
- # underscores.
- return True
- def are_choices_valid(choices):
- # FIXME: we could check that all values have the same type with GVariant
- return True
- def is_range_valid(minmax):
- # FIXME: we'll be able to easily check min < max once we can convert the
- # values with GVariant
- return True
- ######################################
- class GSettingsSchemaConvertException(Exception):
- pass
- ######################################
- class GSettingsSchemaRoot:
- def __init__(self):
- self.gettext_domain = None
- self.schemas = []
- def get_simple_string(self):
- need_empty_line = False
- result = ''
- for schema in self.schemas:
- if need_empty_line:
- result += '\n'
- result += schema.get_simple_string()
- if result:
- need_empty_line = True
- # Only put the gettext domain if we have some content
- if result and self.gettext_domain:
- result = 'gettext-domain: %s\n\n%s' % (self.gettext_domain, result)
- return result
- def get_xml_node(self):
- schemalist_node = ET.Element('schemalist')
- if self.gettext_domain:
- schemalist_node.set('gettext-domain', self.gettext_domain)
- for schema in self.schemas:
- for schema_node in schema.get_xml_nodes():
- schemalist_node.append(schema_node)
- return schemalist_node
- ######################################
- class GSettingsSchema:
- def __init__(self):
- self.id = None
- self.path = None
- # only set when this schema is a child
- self.name = None
- self.gettext_domain = None
- self.children = []
- self.keys = []
- def get_simple_string(self, current_indent = '', parent_path = ''):
- if not self.children and not self.keys:
- return ''
- content = self._get_simple_string_for_content(current_indent)
- if not content:
- return ''
- if self.name:
- id = 'child %s' % self.name
- force_empty_line = False
- else:
- id = 'schema %s' % self.id
- force_empty_line = True
- result = ''
- result += '%s%s:\n' % (current_indent, id)
- result += self._get_simple_string_for_attributes(current_indent, parent_path, force_empty_line)
- result += content
- return result
- def _get_simple_string_for_attributes(self, current_indent, parent_path, force_empty_line):
- need_empty_line = force_empty_line
- result = ''
- if self.gettext_domain:
- result += '%sgettext-domain: %s\n' % (current_indent + GSETTINGS_SIMPLE_SCHEMA_INDENT, self.gettext_domain)
- need_empty_line = True
- if self.path and (not parent_path or (self.path != '%s%s/' % (parent_path, self.name))):
- result += '%spath: %s\n' % (current_indent + GSETTINGS_SIMPLE_SCHEMA_INDENT, self.path)
- need_empty_line = True
- if need_empty_line:
- result += '\n'
- return result
- def _get_simple_string_for_content(self, current_indent):
- need_empty_line = False
- result = ''
- for key in self.keys:
- result += key.get_simple_string(current_indent + GSETTINGS_SIMPLE_SCHEMA_INDENT)
- need_empty_line = True
- for child in self.children:
- if need_empty_line:
- result += '\n'
- result += child.get_simple_string(current_indent + GSETTINGS_SIMPLE_SCHEMA_INDENT, self.path)
- if result:
- need_empty_line = True
- return result
- def get_xml_nodes(self):
- if not self.children and not self.keys:
- return []
- (node, children_nodes) = self._get_xml_nodes_for_content()
- if node is None:
- return []
- node.set('id', self.id)
- if self.path:
- node.set('path', self.path)
- nodes = [ node ]
- nodes.extend(children_nodes)
- return nodes
- def _get_xml_nodes_for_content(self):
- if not self.keys and not self.children:
- return (None, None)
- children_nodes = []
- schema_node = ET.Element('schema')
- if self.gettext_domain:
- schema_node.set('gettext-domain', self.gettext_domain)
- for key in self.keys:
- key_node = key.get_xml_node()
- schema_node.append(key_node)
- for child in self.children:
- child_nodes = child.get_xml_nodes()
- children_nodes.extend(child_nodes)
- child_node = ET.SubElement(schema_node, 'child')
- if not child.name:
- raise GSettingsSchemaConvertException('Internal error: child being processed with no schema id.')
- child_node.set('name', child.name)
- child_node.set('schema', '%s' % child.id)
- return (schema_node, children_nodes)
- ######################################
- class GSettingsSchemaKey:
- def __init__(self):
- self.name = None
- self.type = None
- self.default = None
- self.typed_default = None
- self.l10n = None
- self.l10n_context = None
- self.summary = None
- self.description = None
- self.choices = None
- self.range = None
- def fill(self, name, type, default, typed_default, l10n, l10n_context, summary, description, choices, range):
- self.name = name
- self.type = type
- self.default = default
- self.typed_default = typed_default
- self.l10n = l10n
- self.l10n_context = l10n_context
- self.summary = summary
- self.description = description
- self.choices = choices
- self.range = range
- def _has_range_choices(self):
- return self.choices is not None and self.type in TYPES_FOR_CHOICES
- def _has_range_minmax(self):
- return self.range is not None and len(self.range) == 2 and self.type in TYPES_FOR_RANGE
- def get_simple_string(self, current_indent):
- # FIXME: kill this when we'll have python bindings for GVariant. Right
- # now, every simple format schema we'll generate has to have an
- # explicit type since we can't guess the type later on when converting
- # to XML.
- self.typed_default = '@%s %s' % (self.type, self.default)
- result = ''
- result += '%skey %s = %s\n' % (current_indent, self.name, self.typed_default or self.default)
- current_indent += GSETTINGS_SIMPLE_SCHEMA_INDENT
- if self.l10n:
- l10n = self.l10n
- if self.l10n_context:
- l10n += ' %s' % self.l10n_context
- result += '%sl10n: %s\n' % (current_indent, l10n)
- if self.summary:
- result += '%ssummary: %s\n' % (current_indent, self.summary)
- if self.description:
- result += '%sdescription: %s\n' % (current_indent, self.description)
- if self._has_range_choices():
- result += '%schoices: %s\n' % (current_indent, ', '.join(self.choices))
- elif self._has_range_minmax():
- result += '%srange: %s\n' % (current_indent, '%s..%s' % (self.range[0] or '', self.range[1] or ''))
- return result
- def get_xml_node(self):
- key_node = ET.Element('key')
- key_node.set('name', self.name)
- key_node.set('type', self.type)
- default_node = ET.SubElement(key_node, 'default')
- default_node.text = self.default
- if self.l10n:
- default_node.set('l10n', self.l10n)
- if self.l10n_context:
- default_node.set('context', self.l10n_context)
- if self.summary:
- summary_node = ET.SubElement(key_node, 'summary')
- summary_node.text = self.summary
- if self.description:
- description_node = ET.SubElement(key_node, 'description')
- description_node.text = self.description
- if self._has_range_choices():
- choices_node = ET.SubElement(key_node, 'choices')
- for choice in self.choices:
- choice_node = ET.SubElement(choices_node, 'choice')
- choice_node.set('value', choice)
- elif self._has_range_minmax():
- (min, max) = self.range
- range_node = ET.SubElement(key_node, 'range')
- min_node = ET.SubElement(range_node, 'min')
- if min:
- min_node.text = min
- max_node = ET.SubElement(range_node, 'max')
- if max:
- max_node.text = max
- return key_node
- ######################################
- class SimpleSchemaParser:
- allowed_tokens = {
- '' : [ 'gettext-domain', 'schema' ],
- 'gettext-domain' : [ ],
- 'schema' : [ 'gettext-domain', 'path', 'child', 'key' ],
- 'path' : [ ],
- 'child' : [ 'gettext-domain', 'child', 'key' ],
- 'key' : [ 'l10n', 'summary', 'description', 'choices', 'range' ],
- 'l10n' : [ ],
- 'summary' : [ ],
- 'description' : [ ],
- 'choices' : [ ],
- 'range' : [ ]
- }
- allowed_separators = [ ':', '=' ]
- def __init__(self, file):
- self.file = file
- self.root = GSettingsSchemaRoot()
- # this is just a convenient helper to remove the leading indentation
- # that should be common to all lines
- self.leading_indent = None
- self.indent_stack = []
- self.token_stack = []
- self.object_stack = [ self.root ]
- self.previous_token = None
- self.current_token = None
- self.unparsed_line = ''
- def _eat_indent(self):
- line = self.unparsed_line
- i = 0
- buf = ''
- previous_max_index = len(self.indent_stack) - 1
- index = -1
- while i < len(line) - 1 and line[i].isspace():
- buf += line[i]
- i += 1
- if previous_max_index > index:
- if buf == self.indent_stack[index + 1]:
- buf = ''
- index += 1
- continue
- elif self.indent_stack[index + 1].startswith(buf):
- continue
- else:
- raise GSettingsSchemaConvertException('Inconsistent indentation.')
- else:
- continue
- if buf and previous_max_index > index:
- raise GSettingsSchemaConvertException('Inconsistent indentation.')
- elif buf and previous_max_index <= index:
- self.indent_stack.append(buf)
- elif previous_max_index > index:
- self.indent_stack = self.indent_stack[:index + 1]
- self.unparsed_line = line[i:]
- def _parse_word(self):
- line = self.unparsed_line
- i = 0
- while i < len(line) and not line[i].isspace() and not line[i] in self.allowed_separators:
- i += 1
- self.unparsed_line = line[i:]
- return line[:i]
- def _word_to_token(self, word):
- lower = word.lower()
- if lower and lower in self.allowed_tokens.keys():
- return lower
- raise GSettingsSchemaConvertException('\'%s\' is not a valid token.' % lower)
- def _token_allow_separator(self):
- return self.current_token in [ 'gettext-domain', 'path', 'l10n', 'summary', 'description', 'choices', 'range' ]
- def _parse_id_without_separator(self):
- line = self.unparsed_line
- if line[-1] in self.allowed_separators:
- line = line[:-1].strip()
- if not is_schema_id_valid(line):
- raise GSettingsSchemaConvertException('\'%s\' is not a valid schema id.' % line)
- self.unparsed_line = ''
- return line
- def _parse_key(self):
- line = self.unparsed_line
- split = False
- for separator in self.allowed_separators:
- items = line.split(separator)
- if len(items) == 2:
- split = True
- break
- if not split:
- raise GSettingsSchemaConvertException('Key \'%s\' cannot be parsed.' % line)
- name = items[0].strip()
- if not is_key_name_valid(name):
- raise GSettingsSchemaConvertException('\'%s\' is not a valid key name.' % name)
- type = ''
- value = items[1].strip()
- if value[0] == '@':
- i = 1
- while not value[i].isspace():
- i += 1
- type = value[1:i]
- value = value[i:].strip()
- if not value:
- raise GSettingsSchemaConvertException('No value specified for key \'%s\' (\'%s\').' % (name, line))
- self.unparsed_line = ''
- object = GSettingsSchemaKey()
- object.name = name
- object.type = type
- object.default = value
- return object
- def _parse_l10n(self):
- line = self.unparsed_line
- items = [ item.strip() for item in line.split(' ', 1) if item.strip() ]
- if not items:
- self.unparsed_line = ''
- return (None, None)
- if len(items) == 1:
- self.unparsed_line = ''
- return (items[0], None)
- if len(items) == 2:
- self.unparsed_line = ''
- return (items[0], items[1])
- raise GSettingsSchemaConvertException('Internal error: more items than expected for localization \'%s\'.' % line)
- def _parse_choices(self, object):
- if object.type not in TYPES_FOR_CHOICES:
- raise GSettingsSchemaConvertException('Key \'%s\' of type \'%s\' cannot have choices.' % (object.name, object.type))
- line = self.unparsed_line
- choices = [ item.strip() for item in line.split(',') ]
- if not are_choices_valid(choices):
- raise GSettingsSchemaConvertException('\'%s\' is not a valid choice.' % line)
- self.unparsed_line = ''
- return choices
- def _parse_range(self, object):
- if object.type not in TYPES_FOR_RANGE:
- raise GSettingsSchemaConvertException('Key \'%s\' of type \'%s\' cannot have a range.' % (object.name, object.type))
- line = self.unparsed_line
- minmax = [ item.strip() for item in line.split('..') ]
- if len(minmax) != 2:
- raise GSettingsSchemaConvertException('Range \'%s\' cannot be parsed.' % line)
- if not is_range_valid(minmax):
- raise GSettingsSchemaConvertException('\'%s\' is not a valid range.' % line)
- self.unparsed_line = ''
- return tuple(minmax)
- def parse_line(self, line):
- # make sure that lines with only spaces are ignored and considered as
- # empty lines
- self.unparsed_line = line.rstrip()
- # ignore empty line
- if not self.unparsed_line:
- return
- # look at the indentation to know where we should be
- self._eat_indent()
- if self.leading_indent is None:
- self.leading_indent = len(self.indent_stack)
- # ignore comments
- if self.unparsed_line[0] == '#':
- return
- word = self._parse_word()
- if self.current_token:
- self.previous_token = self.current_token
- self.current_token = self._word_to_token(word)
- self.unparsed_line = self.unparsed_line.lstrip()
- allow_separator = self._token_allow_separator()
- if len(self.unparsed_line) > 0 and self.unparsed_line[0] in self.allowed_separators:
- if allow_separator:
- self.unparsed_line = self.unparsed_line[1:].lstrip()
- else:
- raise GSettingsSchemaConvertException('Separator \'%s\' is not allowed after \'%s\'.' % (self.unparsed_line[0], self.current_token))
- new_level = len(self.indent_stack) - self.leading_indent
- old_level = len(self.token_stack)
- if new_level > old_level + 1:
- raise GSettingsSchemaConvertException('Internal error: stacks not in sync.')
- elif new_level <= old_level:
- self.token_stack = self.token_stack[:new_level]
- # we always have the root
- self.object_stack = self.object_stack[:new_level + 1]
- if new_level == 0:
- parent_token = ''
- else:
- parent_token = self.token_stack[-1]
- # there's new indentation, but no token is allowed under the previous
- # one
- if new_level == old_level + 1 and self.previous_token != parent_token:
- raise GSettingsSchemaConvertException('\'%s\' is not allowed under \'%s\'.' % (self.current_token, self.previous_token))
- if not self.current_token in self.allowed_tokens[parent_token]:
- if parent_token:
- error = '\'%s\' is not allowed under \'%s\'.' % (self.current_token, parent_token)
- else:
- error = '\'%s\' is not allowed at the root level.' % self.current_token
- raise GSettingsSchemaConvertException(error)
- current_object = self.object_stack[-1]
- new_object = None
- if self.current_token == 'gettext-domain':
- current_object.gettext_domain = self.unparsed_line
- elif self.current_token == 'schema':
- name = self._parse_id_without_separator()
- new_object = GSettingsSchema()
- new_object.id = name
- current_object.schemas.append(new_object)
- elif self.current_token == 'path':
- current_object.path = self.unparsed_line
- elif self.current_token == 'child':
- if not isinstance(current_object, GSettingsSchema):
- raise GSettingsSchemaConvertException('Internal error: child being processed with no parent schema.')
- name = self._parse_id_without_separator()
- new_object = GSettingsSchema()
- new_object.id = '%s.%s' % (current_object.id, name)
- if current_object.path:
- new_object.path = '%s%s/' % (current_object.path, name)
- new_object.name = name
- current_object.children.append(new_object)
- elif self.current_token == 'key':
- new_object = self._parse_key()
- current_object.keys.append(new_object)
- elif self.current_token == 'l10n':
- (current_object.l10n, current_object.l10n_context) = self._parse_l10n()
- elif self.current_token == 'summary':
- current_object.summary = self.unparsed_line
- elif self.current_token == 'description':
- current_object.description = self.unparsed_line
- elif self.current_token == 'choices':
- current_object.choices = self._parse_choices(current_object)
- elif self.current_token == 'range':
- current_object.range = self._parse_range(current_object)
- if new_object:
- self.token_stack.append(self.current_token)
- self.object_stack.append(new_object)
- def parse(self):
- f = open(self.file, 'r')
- lines = [ line[:-1] for line in f.readlines() ]
- f.close()
- try:
- current_line_nb = 0
- for line in lines:
- current_line_nb += 1
- self.parse_line(line)
- except GSettingsSchemaConvertException, e:
- raise GSettingsSchemaConvertException('%s:%s: %s' % (os.path.basename(self.file), current_line_nb, e))
- return self.root
- ######################################
- class XMLSchemaParser:
- def __init__(self, file):
- self.file = file
- self.root = None
- def _parse_key(self, key_node, schema):
- key = GSettingsSchemaKey()
- key.name = key_node.get('name')
- if not key.name:
- raise GSettingsSchemaConvertException('A key in schema \'%s\' has no name.' % schema.id)
- key.type = key_node.get('type')
- if not key.type:
- raise GSettingsSchemaConvertException('Key \'%s\' in schema \'%s\' has no type.' % (key.name, schema.id))
- default_node = key_node.find('default')
- if default_node is None or not default_node.text.strip():
- raise GSettingsSchemaConvertException('Key \'%s\' in schema \'%s\' has no default value.' % (key.name, schema.id))
- key.l10n = default_node.get('l10n')
- key.l10n_context = default_node.get('context')
- key.default = default_node.text.strip()
- summary_node = key_node.find('summary')
- if summary_node is not None:
- key.summary = summary_node.text.strip()
- description_node = key_node.find('description')
- if description_node is not None:
- key.description = description_node.text.strip()
- range_node = key_node.find('range')
- if range_node is not None:
- min = None
- max = None
- min_node = range_node.find('min')
- if min_node is not None:
- min = min_node.text.strip()
- max_node = range_node.find('max')
- if max_node is not None:
- max = max_node.text.strip()
- if min or max:
- self.range = (min, max)
- choices_node = key_node.find('choices')
- if choices_node is not None:
- self.choices = []
- for choice_node in choices_node.findall('choice'):
- value = choice_node.get('value')
- if value:
- self.choices.append(value)
- else:
- raise GSettingsSchemaConvertException('A choice for key \'%s\' in schema \'%s\' has no value.' % (key.name, schema.id))
- return key
- def _parse_schema(self, schema_node):
- schema = GSettingsSchema()
- schema._children = []
- schema.id = schema_node.get('id')
- if not schema.id:
- raise GSettingsSchemaConvertException('A schema has no id.')
- schema.path = schema_node.get('path')
- schema.gettext_domain = schema_node.get('gettext-domain')
- for key_node in schema_node.findall('key'):
- key = self._parse_key(key_node, schema)
- schema.keys.append(key)
- for child_node in schema_node.findall('child'):
- child_name = child_node.get('name')
- if not child_name:
- raise GSettingsSchemaConvertException('A child of schema \'%s\' has no name.' % schema.id)
- child_schema = child_node.get('schema')
- if not child_schema:
- raise GSettingsSchemaConvertException('Child \'%s\' of schema \'%s\' has no schema.' % (child_name, schema.id))
- expected_id = schema.id + '.' + child_name
- if child_schema != expected_id:
- raise GSettingsSchemaConvertException('\'%s\' is too complex for this tool: child \'%s\' of schema \'%s\' has a schema that is not the expected one (\'%s\' vs \'%s\').' % (os.path.basename(self.file), child_name, schema.id, child_schema, expected_id))
- schema._children.append((child_schema, child_name))
- return schema
- def parse(self):
- self.root = GSettingsSchemaRoot()
- schemas = []
- parent = {}
- schemalist_node = ET.parse(self.file).getroot()
- self.root.gettext_domain = schemalist_node.get('gettext-domain')
- for schema_node in schemalist_node.findall('schema'):
- schema = self._parse_schema(schema_node)
- for (child_schema, child_name) in schema._children:
- if parent.has_key(child_schema):
- raise GSettingsSchemaConvertException('Child \'%s\' is declared by two different schemas: \'%s\' and \'%s\'.' % (child_schema, parent[child_schema], schema.id))
- parent[child_schema] = schema
- schemas.append(schema)
- # now let's move all schemas where they should leave
- for schema in schemas:
- if parent.has_key(schema.id):
- parent_schema = parent[schema.id]
- # check that the paths of parent and child are supported by
- # this tool
- found = False
- for (child_schema, child_name) in parent_schema._children:
- if child_schema == schema.id:
- found = True
- break
- if not found:
- raise GSettingsSchemaConvertException('Internal error: child not found in parent\'s children.')
- schema.name = child_name
- parent_schema.children.append(schema)
- else:
- self.root.schemas.append(schema)
- return self.root
- ######################################
- def map_gconf_type_to_variant_type(gconftype, gconfsubtype):
- typemap = { 'string': 's', 'int': 'i', 'float': 'd', 'bool': 'b', 'list': 'a' }
- try:
- result = typemap[gconftype]
- except KeyError:
- raise GSettingsSchemaConvertException('Type \'%s\' is not a known gconf type.' % gconftype)
- if gconftype == 'list':
- try:
- result = result + typemap[gconfsubtype]
- except KeyError:
- raise GSettingsSchemaConvertException('Type \'%s\' is not a known gconf type.' % gconfsubtype)
- return result
- def fix_value_for_simple_gconf_type(gconftype, gconfvalue):
- '''If there is no value, then we choose a 'neutral' value (false, 0, empty
- string).
- '''
- if gconftype == 'string':
- if not gconfvalue:
- return '\'\''
- return '\'' + gconfvalue.replace('\'', '\\\'') + '\''
- elif gconftype == 'int':
- if not gconfvalue:
- return '0'
- try:
- int(gconfvalue)
- except ValueError:
- raise GSettingsSchemaConvertException()
- return gconfvalue
- elif gconftype == 'float':
- if not gconfvalue:
- return '0.0'
- try:
- float(gconfvalue)
- except ValueError:
- raise GSettingsSchemaConvertException()
- return gconfvalue
- elif gconftype == 'bool':
- if not gconfvalue:
- return 'false'
- value = gconfvalue.lower()
- # gconf schemas can have 0/1 for false/true
- if value == '0':
- return 'false'
- elif value == '1':
- return 'true'
- elif value in ['false', 'true']:
- return value
- else:
- raise GSettingsSchemaConvertException()
- else:
- return gconfvalue
- class GConfSchema:
- def __init__(self, node):
- locale_node = node.find('locale')
- self.key = node.find('key').text
- self.type = node.find('type').text
- if self.type == 'list':
- self.list_type = node.find('list_type').text
- else:
- self.list_type = None
- self.varianttype = map_gconf_type_to_variant_type(self.type, self.list_type)
- applyto_node = node.find('applyto')
- if applyto_node is not None:
- self.applyto = node.find('applyto').text
- self.applyto.strip()
- self.keyname = self.applyto[self.applyto.rfind('/')+1:]
- self.prefix = self.applyto[:self.applyto.rfind('/')+1]
- else:
- self.applyto = None
- self.key.strip()
- self.keyname = self.key[self.key.rfind('/')+1:]
- self.prefix = self.key[:self.key.rfind('/')+1]
- self.prefix = os.path.normpath(self.prefix)
- try:
- self.default = locale_node.find('default').text
- self.localized = 'messages'
- except:
- try:
- self.default = node.find('default').text
- except:
- self.default = ''
- self.localized = None
- self.typed_default = None
- self.short = self._get_value_with_locale(node, locale_node, 'short')
- self.long = self._get_value_with_locale(node, locale_node, 'long')
- if self.short:
- self.short = self._oneline(self.short)
- if self.long:
- self.long = self._oneline(self.long)
- # Fix the default value to be parsable by GVariant
- if self.type == 'list':
- l = self.default.strip()
- if not l:
- l = '[]'
- elif not (l[0] == '[' and l[-1] == ']'):
- raise GSettingsSchemaConvertException('Cannot parse default list value \'%s\' for key \'%s\'.' % (self.default, self.applyto or self.key))
- values = l[1:-1].strip()
- if not values:
- self.default = '[]'
- self.typed_default = '@%s []' % self.varianttype
- else:
- items = [ item.strip() for item in values.split(',') ]
- try:
- items = [ fix_value_for_simple_gconf_type(self.list_type, item) for item in items ]
- except GSettingsSchemaConvertException:
- raise GSettingsSchemaConvertException('Invalid item(s) of type \'%s\' in default list \'%s\' for key \'%s\'.' % (self.list_type, self.default, self.applyto or self.key))
- values = ', '.join(items)
- self.default = '[ %s ]' % values
- else:
- try:
- self.default = fix_value_for_simple_gconf_type(self.type, self.default)
- except GSettingsSchemaConvertException:
- raise GSettingsSchemaConvertException('Invalid default value \'%s\' of type \'%s\' for key \'%s\'.' % (self.default, self.type, self.applyto or self.key))
- def _get_value_with_locale(self, node, locale_node, element):
- element_node = None
- if locale_node is not None:
- element_node = locale_node.find(element)
- if element_node is None:
- element_node = node.find(element)
- if element_node is not None:
- return element_node.text
- else:
- return None
- def _oneline(self, s):
- lines = s.splitlines()
- result = ''
- for line in lines:
- result += ' ' + line.lstrip()
- return result.strip()
- def convert_underscores(self):
- self.prefix = self.prefix.replace('_', '-')
- self.keyname = self.keyname.replace('_', '-')
- def get_gsettings_schema_key(self):
- key = GSettingsSchemaKey()
- key.fill(self.keyname, self.varianttype, self.default, self.typed_default, self.localized, self.keyname, self.short, self.long, None, None)
- return key
- ######################################
- class GConfSchemaParser:
- def __init__(self, file, default_gettext_domain, default_schema_id, keep_underscores):
- self.file = file
- self.default_gettext_domain = default_gettext_domain
- self.default_schema_id = default_schema_id
- self.keep_underscores = keep_underscores
- self.root = None
- self.default_schema_id_count = 0
- def _insert_schema(self, gconf_schema):
- if not self.keep_underscores:
- gconf_schema.convert_underscores()
- schemas_only = (gconf_schema.applyto is None)
- dirpath = gconf_schema.prefix
- if dirpath[0] != '/':
- raise GSettingsSchemaConvertException('Key \'%s\' has a relative path. There is no relative path in GSettings schemas.' % gconf_schema.applyto or gconf_schema.key)
- # remove leading 'schemas/' for schemas-only keys
- if schemas_only and dirpath.startswith('/schemas/'):
- dirpath = dirpath[len('/schemas'):]
- if len(dirpath) == 1:
- raise GSettingsSchemaConvertException('Key \'%s\' is a toplevel key. Toplevel keys are not accepted in GSettings schemas.' % gconf_schema.applyto or gconf_schema.key)
- # remove trailing slash because we'll split the string
- if dirpath[-1] == '/':
- dirpath = dirpath[:-1]
- # and also remove leading slash when splitting
- hierarchy = dirpath[1:].split('/')
- # we don't want to put apps/ and desktop/ keys in the same schema,
- # so we have a first step where we make sure to create a new schema
- # to avoid this case if necessary
- gsettings_schema = None
- for schema in self.root.schemas:
- if schemas_only:
- schema_path = schema._hacky_path
- else:
- schema_path = schema.path
- if dirpath.startswith(schema_path):
- gsettings_schema = schema
- break
- if not gsettings_schema:
- gsettings_schema = GSettingsSchema()
- if schemas_only:
- gsettings_schema._hacky_path = '/' + hierarchy[0] + '/'
- else:
- gsettings_schema.path = '/' + hierarchy[0] + '/'
- self.root.schemas.append(gsettings_schema)
- # we create the schema hierarchy that leads to this key
- gsettings_dir = gsettings_schema
- for item in hierarchy[1:]:
- subdir = None
- for child in gsettings_dir.children:
- if child.name == item:
- subdir = child
- break
- if not subdir:
- subdir = GSettingsSchema()
- # note: the id will be set later on
- if gsettings_dir.path:
- subdir.path = '%s%s/' % (gsettings_dir.path, item)
- subdir.name = item
- gsettings_dir.children.append(subdir)
- gsettings_dir = subdir
- # we have the final directory, so we can put the key there
- gsettings_dir.keys.append(gconf_schema.get_gsettings_schema_key())
- def _set_children_id(self, schema):
- for child in schema.children:
- child.id = '%s.%s' % (schema.id, child.name)
- self._set_children_id(child)
- def _fix_hierarchy(self):
- for schema in self.root.schemas:
- # we created one schema per level, starting at the root level;
- # however, we don't need to go that far and we can simplify the
- # hierarchy
- while len(schema.children) == 1 and not schema.keys:
- child = schema.children[0]
- schema.children = child.children
- schema.keys = child.keys
- if schema.path:
- schema.path += child.name + '/'
- # now that we have a toplevel schema, set the id
- if self.default_schema_id:
- schema.id = self.default_schema_id
- if self.default_schema_id_count > 0:
- schema.id += '.FIXME-%s' % self.default_schema_id_count
- self.default_schema_id_count += 1
- else:
- schema.id = 'FIXME'
- self._set_children_id(schema)
- def parse(self):
- # reset the state of the parser
- self.root = GSettingsSchemaRoot()
- if self.default_gettext_domain:
- self.root.gettext_domain = self.default_gettext_domain
- self.default_schema_id_count = 0
- gconfschemafile_node = ET.parse(self.file).getroot()
- for schemalist_node in gconfschemafile_node.findall('schemalist'):
- for schema_node in schemalist_node.findall('schema'):
- gconf_schema = GConfSchema(schema_node)
- if gconf_schema.localized and not self.root.gettext_domain:
- self.root.gettext_domain = 'FIXME'
- self._insert_schema(gconf_schema)
- self._fix_hierarchy()
- return self.root
- ######################################
- def main(args):
- parser = optparse.OptionParser()
- parser.add_option("-o", "--output", dest="output",
- help="output file")
- parser.add_option("-g", "--gconf", action="store_true", dest="gconf",
- default=False, help="convert a gconf schema file")
- parser.add_option("-d", "--gettext-domain", dest="gettext_domain",
- help="default gettext domain to use when converting gconf schema file")
- parser.add_option("-i", "--schema-id", dest="schema_id",
- help="default schema ID to use when converting gconf schema file")
- parser.add_option("-u", "--keep-underscores", action="store_true", dest="keep_underscores",
- help="keep underscores in key names instead of replacing them with dashes when converting gconf schema file")
- parser.add_option("-s", "--simple", action="store_true", dest="simple",
- default=False, help="use the simple schema format as output")
- parser.add_option("-x", "--xml", action="store_true", dest="xml",
- default=False, help="use the xml schema format as output")
- parser.add_option("-f", "--force", action="store_true", dest="force",
- default=False, help="overwrite output file if already existing")
- (options, args) = parser.parse_args()
- if len(args) < 1:
- print >> sys.stderr, 'Need a filename to work on.'
- return 1
- elif len(args) > 1:
- print >> sys.stderr, 'Too many arguments.'
- return 1
- if options.simple and options.xml:
- print >> sys.stderr, 'Too many output formats requested.'
- return 1
- if not options.gconf and options.gettext_domain:
- print >> sys.stderr, 'Default gettext domain can only be specified when converting a gconf schema.'
- return 1
- if not options.gconf and options.schema_id:
- print >> sys.stderr, 'Default schema ID can only be specified when converting a gconf schema.'
- return 1
- if not options.gconf and options.keep_underscores:
- print >> sys.stderr, 'The --keep-underscores option can only be specified when converting a gconf schema.'
- return 1
- argfile = os.path.expanduser(args[0])
- if not os.path.exists(argfile):
- print >> sys.stderr, '\'%s\' does not exist.' % argfile
- return 1
- if options.output:
- options.output = os.path.expanduser(options.output)
- try:
- if options.output and not options.force and os.path.exists(options.output):
- raise GSettingsSchemaConvertException('\'%s\' already exists. Use --force to overwrite it.' % options.output)
- if options.gconf:
- if not options.simple and not options.xml:
- options.xml = True
- try:
- parser = GConfSchemaParser(argfile, options.gettext_domain, options.schema_id, options.keep_underscores)
- schema_root = parser.parse()
- except SyntaxError, e:
- raise GSettingsSchemaConvertException('\'%s\' does not look like a valid gconf schema file: %s' % (argfile, e))
- else:
- # autodetect if file is XML or not
- try:
- parser = XMLSchemaParser(argfile)
- schema_root = parser.parse()
- if not options.simple and not options.xml:
- options.simple = True
- except SyntaxError, e:
- parser = SimpleSchemaParser(argfile)
- schema_root = parser.parse()
- if not options.simple and not options.xml:
- options.xml = True
- if options.xml:
- node = schema_root.get_xml_node()
- try:
- output = ET.tostring(node, pretty_print = True)
- except TypeError:
- # pretty_print only works with lxml
- output = ET.tostring(node)
- else:
- output = schema_root.get_simple_string()
- if not options.output:
- sys.stdout.write(output)
- else:
- try:
- fout = open(options.output, 'w')
- fout.write(output)
- fout.close()
- except GSettingsSchemaConvertException, e:
- fout.close()
- if os.path.exists(options.output):
- os.unlink(options.output)
- raise e
- except GSettingsSchemaConvertException, e:
- print >> sys.stderr, '%s' % e
- return 1
- return 0
- if __name__ == '__main__':
- try:
- res = main(sys.argv)
- sys.exit(res)
- except KeyboardInterrupt:
- pass
|