2 # vim: set ts=4 sw=4 et: coding=UTF-8
4 # Copyright (c) 2010, Novell, Inc.
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU Lesser General Public License
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU Lesser General Public
17 # License along with this program; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
21 # Authors: Vincent Untz <vuntz@gnome.org>
23 # TODO: add alias support for choices
24 # choices: 'this-is-an-alias' = 'real', 'other', 'real'
25 # TODO: we don't support migrating a pair from a gconf schema. It has yet to be
26 # seen in real-world usage, though.
34 from lxml import etree as ET
37 from xml.etree import cElementTree as ET
39 import cElementTree as ET
42 GSETTINGS_SIMPLE_SCHEMA_INDENT = ' '
43 TYPES_FOR_CHOICES = [ 's' ]
44 TYPES_FOR_RANGE = [ 'y', 'n', 'q', 'i', 'u', 'x', 't', 'h', 'd' ]
47 ######################################
50 def is_schema_id_valid(id):
51 # FIXME: there's currently no restriction on what an id should contain,
52 # but there might be some later on
56 def is_key_name_valid(name):
57 # FIXME: we could check that name is valid ([-a-z0-9], no leading/trailing
58 # -, no leading digit, 32 char max). Note that we don't want to validate
59 # the key when converting from gconf, though, since gconf keys use
64 def are_choices_valid(choices):
65 # FIXME: we could check that all values have the same type with GVariant
69 def is_range_valid(minmax):
70 # FIXME: we'll be able to easily check min < max once we can convert the
71 # values with GVariant
75 ######################################
78 class GSettingsSchemaConvertException(Exception):
82 ######################################
85 class GSettingsSchemaRoot:
88 self.gettext_domain = None
91 def get_simple_string(self):
92 need_empty_line = False
95 for schema in self.schemas:
98 result += schema.get_simple_string()
100 need_empty_line = True
102 # Only put the gettext domain if we have some content
103 if result and self.gettext_domain:
104 result = 'gettext-domain: %s\n\n%s' % (self.gettext_domain, result)
108 def get_xml_node(self):
109 schemalist_node = ET.Element('schemalist')
110 if self.gettext_domain:
111 schemalist_node.set('gettext-domain', self.gettext_domain)
112 for schema in self.schemas:
113 for schema_node in schema.get_xml_nodes():
114 schemalist_node.append(schema_node)
115 return schemalist_node
118 ######################################
121 class GSettingsSchema:
126 # only set when this schema is a child
129 self.gettext_domain = None
133 def get_simple_string(self, current_indent = '', parent_path = ''):
134 if not self.children and not self.keys:
137 content = self._get_simple_string_for_content(current_indent)
142 id = 'child %s' % self.name
143 force_empty_line = False
145 id = 'schema %s' % self.id
146 force_empty_line = True
149 result += '%s%s:\n' % (current_indent, id)
150 result += self._get_simple_string_for_attributes(current_indent, parent_path, force_empty_line)
155 def _get_simple_string_for_attributes(self, current_indent, parent_path, force_empty_line):
156 need_empty_line = force_empty_line
159 if self.gettext_domain:
160 result += '%sgettext-domain: %s\n' % (current_indent + GSETTINGS_SIMPLE_SCHEMA_INDENT, self.gettext_domain)
161 need_empty_line = True
162 if self.path and (not parent_path or (self.path != '%s%s/' % (parent_path, self.name))):
163 result += '%spath: %s\n' % (current_indent + GSETTINGS_SIMPLE_SCHEMA_INDENT, self.path)
164 need_empty_line = True
170 def _get_simple_string_for_content(self, current_indent):
171 need_empty_line = False
174 for key in self.keys:
175 result += key.get_simple_string(current_indent + GSETTINGS_SIMPLE_SCHEMA_INDENT)
176 need_empty_line = True
178 for child in self.children:
181 result += child.get_simple_string(current_indent + GSETTINGS_SIMPLE_SCHEMA_INDENT, self.path)
183 need_empty_line = True
187 def get_xml_nodes(self):
188 if not self.children and not self.keys:
191 (node, children_nodes) = self._get_xml_nodes_for_content()
195 node.set('id', self.id)
197 node.set('path', self.path)
200 nodes.extend(children_nodes)
204 def _get_xml_nodes_for_content(self):
205 if not self.keys and not self.children:
210 schema_node = ET.Element('schema')
211 if self.gettext_domain:
212 schema_node.set('gettext-domain', self.gettext_domain)
214 for key in self.keys:
215 key_node = key.get_xml_node()
216 schema_node.append(key_node)
217 for child in self.children:
218 child_nodes = child.get_xml_nodes()
219 children_nodes.extend(child_nodes)
220 child_node = ET.SubElement(schema_node, 'child')
222 raise GSettingsSchemaConvertException('Internal error: child being processed with no schema id.')
223 child_node.set('name', child.name)
224 child_node.set('schema', '%s' % child.id)
226 return (schema_node, children_nodes)
229 ######################################
232 class GSettingsSchemaKey:
238 self.typed_default = None
240 self.l10n_context = None
242 self.description = None
246 def fill(self, name, type, default, typed_default, l10n, l10n_context, summary, description, choices, range):
249 self.default = default
250 self.typed_default = typed_default
252 self.l10n_context = l10n_context
253 self.summary = summary
254 self.description = description
255 self.choices = choices
258 def _has_range_choices(self):
259 return self.choices is not None and self.type in TYPES_FOR_CHOICES
261 def _has_range_minmax(self):
262 return self.range is not None and len(self.range) == 2 and self.type in TYPES_FOR_RANGE
264 def get_simple_string(self, current_indent):
265 # FIXME: kill this when we'll have python bindings for GVariant. Right
266 # now, every simple format schema we'll generate has to have an
267 # explicit type since we can't guess the type later on when converting
269 self.typed_default = '@%s %s' % (self.type, self.default)
272 result += '%skey %s = %s\n' % (current_indent, self.name, self.typed_default or self.default)
273 current_indent += GSETTINGS_SIMPLE_SCHEMA_INDENT
276 if self.l10n_context:
277 l10n += ' %s' % self.l10n_context
278 result += '%sl10n: %s\n' % (current_indent, l10n)
280 result += '%ssummary: %s\n' % (current_indent, self.summary)
282 result += '%sdescription: %s\n' % (current_indent, self.description)
283 if self._has_range_choices():
284 result += '%schoices: %s\n' % (current_indent, ', '.join(self.choices))
285 elif self._has_range_minmax():
286 result += '%srange: %s\n' % (current_indent, '%s..%s' % (self.range[0] or '', self.range[1] or ''))
289 def get_xml_node(self):
290 key_node = ET.Element('key')
291 key_node.set('name', self.name)
292 key_node.set('type', self.type)
293 default_node = ET.SubElement(key_node, 'default')
294 default_node.text = self.default
296 default_node.set('l10n', self.l10n)
297 if self.l10n_context:
298 default_node.set('context', self.l10n_context)
300 summary_node = ET.SubElement(key_node, 'summary')
301 summary_node.text = self.summary
303 description_node = ET.SubElement(key_node, 'description')
304 description_node.text = self.description
305 if self._has_range_choices():
306 choices_node = ET.SubElement(key_node, 'choices')
307 for choice in self.choices:
308 choice_node = ET.SubElement(choices_node, 'choice')
309 choice_node.set('value', choice)
310 elif self._has_range_minmax():
311 (min, max) = self.range
312 range_node = ET.SubElement(key_node, 'range')
313 min_node = ET.SubElement(range_node, 'min')
316 max_node = ET.SubElement(range_node, 'max')
322 ######################################
325 class SimpleSchemaParser:
328 '' : [ 'gettext-domain', 'schema' ],
329 'gettext-domain' : [ ],
330 'schema' : [ 'gettext-domain', 'path', 'child', 'key' ],
332 'child' : [ 'gettext-domain', 'child', 'key' ],
333 'key' : [ 'l10n', 'summary', 'description', 'choices', 'range' ],
341 allowed_separators = [ ':', '=' ]
343 def __init__(self, file):
346 self.root = GSettingsSchemaRoot()
348 # this is just a convenient helper to remove the leading indentation
349 # that should be common to all lines
350 self.leading_indent = None
352 self.indent_stack = []
353 self.token_stack = []
354 self.object_stack = [ self.root ]
356 self.previous_token = None
357 self.current_token = None
358 self.unparsed_line = ''
360 def _eat_indent(self):
361 line = self.unparsed_line
364 previous_max_index = len(self.indent_stack) - 1
367 while i < len(line) - 1 and line[i].isspace():
370 if previous_max_index > index:
371 if buf == self.indent_stack[index + 1]:
375 elif self.indent_stack[index + 1].startswith(buf):
378 raise GSettingsSchemaConvertException('Inconsistent indentation.')
382 if buf and previous_max_index > index:
383 raise GSettingsSchemaConvertException('Inconsistent indentation.')
384 elif buf and previous_max_index <= index:
385 self.indent_stack.append(buf)
386 elif previous_max_index > index:
387 self.indent_stack = self.indent_stack[:index + 1]
389 self.unparsed_line = line[i:]
391 def _parse_word(self):
392 line = self.unparsed_line
394 while i < len(line) and not line[i].isspace() and not line[i] in self.allowed_separators:
396 self.unparsed_line = line[i:]
399 def _word_to_token(self, word):
401 if lower and lower in self.allowed_tokens.keys():
403 raise GSettingsSchemaConvertException('\'%s\' is not a valid token.' % lower)
405 def _token_allow_separator(self):
406 return self.current_token in [ 'gettext-domain', 'path', 'l10n', 'summary', 'description', 'choices', 'range' ]
408 def _parse_id_without_separator(self):
409 line = self.unparsed_line
410 if line[-1] in self.allowed_separators:
411 line = line[:-1].strip()
412 if not is_schema_id_valid(line):
413 raise GSettingsSchemaConvertException('\'%s\' is not a valid schema id.' % line)
415 self.unparsed_line = ''
418 def _parse_key(self):
419 line = self.unparsed_line
422 for separator in self.allowed_separators:
423 items = line.split(separator)
429 raise GSettingsSchemaConvertException('Key \'%s\' cannot be parsed.' % line)
431 name = items[0].strip()
432 if not is_key_name_valid(name):
433 raise GSettingsSchemaConvertException('\'%s\' is not a valid key name.' % name)
436 value = items[1].strip()
439 while not value[i].isspace():
442 value = value[i:].strip()
444 raise GSettingsSchemaConvertException('No value specified for key \'%s\' (\'%s\').' % (name, line))
446 self.unparsed_line = ''
448 object = GSettingsSchemaKey()
451 object.default = value
455 def _parse_l10n(self):
456 line = self.unparsed_line
458 items = [ item.strip() for item in line.split(' ', 1) if item.strip() ]
460 self.unparsed_line = ''
463 self.unparsed_line = ''
464 return (items[0], None)
466 self.unparsed_line = ''
467 return (items[0], items[1])
469 raise GSettingsSchemaConvertException('Internal error: more items than expected for localization \'%s\'.' % line)
471 def _parse_choices(self, object):
472 if object.type not in TYPES_FOR_CHOICES:
473 raise GSettingsSchemaConvertException('Key \'%s\' of type \'%s\' cannot have choices.' % (object.name, object.type))
475 line = self.unparsed_line
476 choices = [ item.strip() for item in line.split(',') ]
477 if not are_choices_valid(choices):
478 raise GSettingsSchemaConvertException('\'%s\' is not a valid choice.' % line)
480 self.unparsed_line = ''
483 def _parse_range(self, object):
484 if object.type not in TYPES_FOR_RANGE:
485 raise GSettingsSchemaConvertException('Key \'%s\' of type \'%s\' cannot have a range.' % (object.name, object.type))
487 line = self.unparsed_line
488 minmax = [ item.strip() for item in line.split('..') ]
491 raise GSettingsSchemaConvertException('Range \'%s\' cannot be parsed.' % line)
492 if not is_range_valid(minmax):
493 raise GSettingsSchemaConvertException('\'%s\' is not a valid range.' % line)
495 self.unparsed_line = ''
498 def parse_line(self, line):
499 # make sure that lines with only spaces are ignored and considered as
501 self.unparsed_line = line.rstrip()
504 if not self.unparsed_line:
507 # look at the indentation to know where we should be
509 if self.leading_indent is None:
510 self.leading_indent = len(self.indent_stack)
513 if self.unparsed_line[0] == '#':
516 word = self._parse_word()
517 if self.current_token:
518 self.previous_token = self.current_token
519 self.current_token = self._word_to_token(word)
520 self.unparsed_line = self.unparsed_line.lstrip()
522 allow_separator = self._token_allow_separator()
523 if len(self.unparsed_line) > 0 and self.unparsed_line[0] in self.allowed_separators:
525 self.unparsed_line = self.unparsed_line[1:].lstrip()
527 raise GSettingsSchemaConvertException('Separator \'%s\' is not allowed after \'%s\'.' % (self.unparsed_line[0], self.current_token))
529 new_level = len(self.indent_stack) - self.leading_indent
530 old_level = len(self.token_stack)
532 if new_level > old_level + 1:
533 raise GSettingsSchemaConvertException('Internal error: stacks not in sync.')
534 elif new_level <= old_level:
535 self.token_stack = self.token_stack[:new_level]
536 # we always have the root
537 self.object_stack = self.object_stack[:new_level + 1]
542 parent_token = self.token_stack[-1]
544 # there's new indentation, but no token is allowed under the previous
546 if new_level == old_level + 1 and self.previous_token != parent_token:
547 raise GSettingsSchemaConvertException('\'%s\' is not allowed under \'%s\'.' % (self.current_token, self.previous_token))
549 if not self.current_token in self.allowed_tokens[parent_token]:
551 error = '\'%s\' is not allowed under \'%s\'.' % (self.current_token, parent_token)
553 error = '\'%s\' is not allowed at the root level.' % self.current_token
554 raise GSettingsSchemaConvertException(error)
556 current_object = self.object_stack[-1]
559 if self.current_token == 'gettext-domain':
560 current_object.gettext_domain = self.unparsed_line
561 elif self.current_token == 'schema':
562 name = self._parse_id_without_separator()
563 new_object = GSettingsSchema()
565 current_object.schemas.append(new_object)
566 elif self.current_token == 'path':
567 current_object.path = self.unparsed_line
568 elif self.current_token == 'child':
569 if not isinstance(current_object, GSettingsSchema):
570 raise GSettingsSchemaConvertException('Internal error: child being processed with no parent schema.')
571 name = self._parse_id_without_separator()
572 new_object = GSettingsSchema()
573 new_object.id = '%s.%s' % (current_object.id, name)
574 if current_object.path:
575 new_object.path = '%s%s/' % (current_object.path, name)
576 new_object.name = name
577 current_object.children.append(new_object)
578 elif self.current_token == 'key':
579 new_object = self._parse_key()
580 current_object.keys.append(new_object)
581 elif self.current_token == 'l10n':
582 (current_object.l10n, current_object.l10n_context) = self._parse_l10n()
583 elif self.current_token == 'summary':
584 current_object.summary = self.unparsed_line
585 elif self.current_token == 'description':
586 current_object.description = self.unparsed_line
587 elif self.current_token == 'choices':
588 current_object.choices = self._parse_choices(current_object)
589 elif self.current_token == 'range':
590 current_object.range = self._parse_range(current_object)
593 self.token_stack.append(self.current_token)
594 self.object_stack.append(new_object)
597 f = open(self.file, 'r')
598 lines = [ line[:-1] for line in f.readlines() ]
605 self.parse_line(line)
606 except GSettingsSchemaConvertException, e:
607 raise GSettingsSchemaConvertException('%s:%s: %s' % (os.path.basename(self.file), current_line_nb, e))
612 ######################################
615 class XMLSchemaParser:
617 def __init__(self, file):
622 def _parse_key(self, key_node, schema):
623 key = GSettingsSchemaKey()
625 key.name = key_node.get('name')
627 raise GSettingsSchemaConvertException('A key in schema \'%s\' has no name.' % schema.id)
628 key.type = key_node.get('type')
630 raise GSettingsSchemaConvertException('Key \'%s\' in schema \'%s\' has no type.' % (key.name, schema.id))
632 default_node = key_node.find('default')
633 if default_node is None or not default_node.text.strip():
634 raise GSettingsSchemaConvertException('Key \'%s\' in schema \'%s\' has no default value.' % (key.name, schema.id))
635 key.l10n = default_node.get('l10n')
636 key.l10n_context = default_node.get('context')
637 key.default = default_node.text.strip()
639 summary_node = key_node.find('summary')
640 if summary_node is not None:
641 key.summary = summary_node.text.strip()
642 description_node = key_node.find('description')
643 if description_node is not None:
644 key.description = description_node.text.strip()
646 range_node = key_node.find('range')
647 if range_node is not None:
650 min_node = range_node.find('min')
651 if min_node is not None:
652 min = min_node.text.strip()
653 max_node = range_node.find('max')
654 if max_node is not None:
655 max = max_node.text.strip()
657 self.range = (min, max)
659 choices_node = key_node.find('choices')
660 if choices_node is not None:
662 for choice_node in choices_node.findall('choice'):
663 value = choice_node.get('value')
665 self.choices.append(value)
667 raise GSettingsSchemaConvertException('A choice for key \'%s\' in schema \'%s\' has no value.' % (key.name, schema.id))
671 def _parse_schema(self, schema_node):
672 schema = GSettingsSchema()
674 schema._children = []
676 schema.id = schema_node.get('id')
678 raise GSettingsSchemaConvertException('A schema has no id.')
679 schema.path = schema_node.get('path')
680 schema.gettext_domain = schema_node.get('gettext-domain')
682 for key_node in schema_node.findall('key'):
683 key = self._parse_key(key_node, schema)
684 schema.keys.append(key)
686 for child_node in schema_node.findall('child'):
687 child_name = child_node.get('name')
689 raise GSettingsSchemaConvertException('A child of schema \'%s\' has no name.' % schema.id)
690 child_schema = child_node.get('schema')
692 raise GSettingsSchemaConvertException('Child \'%s\' of schema \'%s\' has no schema.' % (child_name, schema.id))
694 expected_id = schema.id + '.' + child_name
695 if child_schema != expected_id:
696 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))
698 schema._children.append((child_schema, child_name))
703 self.root = GSettingsSchemaRoot()
707 schemalist_node = ET.parse(self.file).getroot()
708 self.root.gettext_domain = schemalist_node.get('gettext-domain')
710 for schema_node in schemalist_node.findall('schema'):
711 schema = self._parse_schema(schema_node)
713 for (child_schema, child_name) in schema._children:
714 if parent.has_key(child_schema):
715 raise GSettingsSchemaConvertException('Child \'%s\' is declared by two different schemas: \'%s\' and \'%s\'.' % (child_schema, parent[child_schema], schema.id))
716 parent[child_schema] = schema
718 schemas.append(schema)
720 # now let's move all schemas where they should leave
721 for schema in schemas:
722 if parent.has_key(schema.id):
723 parent_schema = parent[schema.id]
725 # check that the paths of parent and child are supported by
728 for (child_schema, child_name) in parent_schema._children:
729 if child_schema == schema.id:
734 raise GSettingsSchemaConvertException('Internal error: child not found in parent\'s children.')
736 schema.name = child_name
737 parent_schema.children.append(schema)
739 self.root.schemas.append(schema)
744 ######################################
747 def map_gconf_type_to_variant_type(gconftype, gconfsubtype):
748 typemap = { 'string': 's', 'int': 'i', 'float': 'd', 'bool': 'b', 'list': 'a' }
749 result = typemap[gconftype]
750 if gconftype == 'list':
751 result = result + typemap[gconfsubtype]
757 def __init__(self, node):
758 locale_node = node.find('locale')
760 self.key = node.find('key').text
761 self.type = node.find('type').text
762 if self.type == 'list':
763 self.list_type = node.find('list_type').text
765 self.list_type = None
766 self.varianttype = map_gconf_type_to_variant_type(self.type, self.list_type)
768 applyto_node = node.find('applyto')
769 if applyto_node is not None:
770 self.applyto = node.find('applyto').text
772 self.keyname = self.applyto[self.applyto.rfind('/')+1:]
773 self.prefix = self.applyto[:self.applyto.rfind('/')+1]
777 self.keyname = self.key[self.key.rfind('/')+1:]
778 self.prefix = self.key[:self.key.rfind('/')+1]
779 self.prefix = os.path.normpath(self.prefix)
782 self.default = locale_node.find('default').text
783 self.localized = 'messages'
786 self.default = node.find('default').text
787 self.localized = None
789 raise GSettingsSchemaConvertException('No default value for key \'%s\'. A default value is always required in GSettings schemas.' % self.applyto or self.key)
790 self.typed_default = None
792 self.short = self._get_value_with_locale(node, locale_node, 'short')
793 self.long = self._get_value_with_locale(node, locale_node, 'long')
796 self.short = self._oneline(self.short)
798 self.long = self._oneline(self.long)
800 # Fix the default to be parsable by GVariant
801 if self.type == 'string':
803 self.default = '\'\''
805 self.default.replace('\'', '\\\'')
806 self.default = '\'%s\'' % self.default
807 elif self.type == 'bool':
808 self.default = self.default.lower()
809 elif self.type == 'list':
810 l = self.default.strip()
811 if not (l[0] == '[' and l[-1] == ']'):
812 raise GSettingsSchemaConvertException('Cannot parse default list value \'%s\' for key \'%s\'.' % (self.default, self.applyto or self.key))
813 values = l[1:-1].strip()
815 self.typed_default = '@%s []' % self.varianttype
816 elif self.list_type == 'string':
817 items = [ item.strip() for item in values.split(',') ]
818 items = [ item.replace('\'', '\\\'') for item in items ]
819 values = ', '.join([ '\'%s\'' % item for item in items ])
820 self.default = '[ %s ]' % values
822 def _get_value_with_locale(self, node, locale_node, element):
824 if locale_node is not None:
825 element_node = locale_node.find(element)
826 if element_node is None:
827 element_node = node.find(element)
828 if element_node is not None:
829 return element_node.text
833 def _oneline(self, s):
834 lines = s.splitlines()
837 result += ' ' + line.lstrip()
838 return result.strip()
840 def get_gsettings_schema_key(self):
841 key = GSettingsSchemaKey()
842 key.fill(self.keyname, self.varianttype, self.default, self.typed_default, self.localized, self.keyname, self.short, self.long, None, None)
846 ######################################
849 class GConfSchemaParser:
851 def __init__(self, file, default_gettext_domain, default_schema_id):
853 self.default_gettext_domain = default_gettext_domain
854 self.default_schema_id = default_schema_id
857 self.default_schema_id_count = 0
859 def _insert_schema(self, gconf_schema):
860 schemas_only = (gconf_schema.applyto is None)
862 dirpath = gconf_schema.prefix
863 if dirpath[0] != '/':
864 raise GSettingsSchemaConvertException('Key \'%s\' has a relative path. There is no relative path in GSettings schemas.' % gconf_schema.applyto or gconf_schema.key)
866 # remove leading 'schemas/' for schemas-only keys
867 if schemas_only and dirpath.startswith('/schemas/'):
868 dirpath = dirpath[len('/schemas'):]
870 if len(dirpath) == 1:
871 raise GSettingsSchemaConvertException('Key \'%s\' is a toplevel key. Toplevel keys are not accepted in GSettings schemas.' % gconf_schema.applyto or gconf_schema.key)
873 # remove trailing slash because we'll split the string
874 if dirpath[-1] == '/':
875 dirpath = dirpath[:-1]
876 # and also remove leading slash when splitting
877 hierarchy = dirpath[1:].split('/')
879 # we don't want to put apps/ and desktop/ keys in the same schema,
880 # so we have a first step where we make sure to create a new schema
881 # to avoid this case if necessary
882 gsettings_schema = None
883 for schema in self.root.schemas:
885 schema_path = schema._hacky_path
887 schema_path = schema.path
888 if dirpath.startswith(schema_path):
889 gsettings_schema = schema
891 if not gsettings_schema:
892 gsettings_schema = GSettingsSchema()
894 gsettings_schema._hacky_path = '/' + hierarchy[0] + '/'
896 gsettings_schema.path = '/' + hierarchy[0] + '/'
897 self.root.schemas.append(gsettings_schema)
899 # we create the schema hierarchy that leads to this key
900 gsettings_dir = gsettings_schema
901 for item in hierarchy[1:]:
903 for child in gsettings_dir.children:
904 if child.name == item:
908 subdir = GSettingsSchema()
909 # note: the id will be set later on
910 if gsettings_dir.path:
911 subdir.path = '%s%s/' % (gsettings_dir.path, item)
913 gsettings_dir.children.append(subdir)
914 gsettings_dir = subdir
916 # we have the final directory, so we can put the key there
917 gsettings_dir.keys.append(gconf_schema.get_gsettings_schema_key())
919 def _set_children_id(self, schema):
920 for child in schema.children:
921 child.id = '%s.%s' % (schema.id, child.name)
922 self._set_children_id(child)
924 def _fix_hierarchy(self):
925 for schema in self.root.schemas:
926 # we created one schema per level, starting at the root level;
927 # however, we don't need to go that far and we can simplify the
929 while len(schema.children) == 1 and not schema.keys:
930 child = schema.children[0]
931 schema.children = child.children
932 schema.keys = child.keys
934 schema.path += child.name + '/'
936 # now that we have a toplevel schema, set the id
937 if self.default_schema_id:
938 schema.id = self.default_schema_id
939 if self.default_schema_id_count > 0:
940 schema.id += '.FIXME-%s' % self.default_schema_id_count
941 self.default_schema_id_count += 1
944 self._set_children_id(schema)
947 # reset the state of the parser
948 self.root = GSettingsSchemaRoot()
949 self.default_schema_id_count = 0
951 gconfschemafile_node = ET.parse(self.file).getroot()
952 for schemalist_node in gconfschemafile_node.findall('schemalist'):
953 for schema_node in schemalist_node.findall('schema'):
954 gconf_schema = GConfSchema(schema_node)
955 if gconf_schema.localized:
956 self.root.gettext_domain = self.default_gettext_domain or 'FIXME'
957 self._insert_schema(gconf_schema)
959 self._fix_hierarchy()
964 ######################################
968 parser = optparse.OptionParser()
970 parser.add_option("-o", "--output", dest="output",
972 parser.add_option("-g", "--gconf", action="store_true", dest="gconf",
973 default=False, help="convert a gconf schema file")
974 parser.add_option("-d", "--gettext-domain", dest="gettext_domain",
975 help="default gettext domain to use when converting gconf schema file")
976 parser.add_option("-i", "--schema-id", dest="schema_id",
977 help="default schema ID to use when converting gconf schema file")
978 parser.add_option("-s", "--simple", action="store_true", dest="simple",
979 default=False, help="use the simple schema format as output (only for gconf schema conversion)")
980 parser.add_option("-x", "--xml", action="store_true", dest="xml",
981 default=False, help="use the xml schema format as output")
982 parser.add_option("-f", "--force", action="store_true", dest="force",
983 default=False, help="overwrite output file if already existing")
985 (options, args) = parser.parse_args()
988 print >> sys.stderr, 'Need a filename to work on.'
991 print >> sys.stderr, 'Too many arguments.'
994 if options.simple and options.xml:
995 print >> sys.stderr, 'Too many output formats requested.'
998 if not options.gconf and options.gettext_domain:
999 print >> sys.stderr, 'Default gettext domain can only be specified when converting a gconf schema.'
1002 if not options.gconf and options.schema_id:
1003 print >> sys.stderr, 'Default schema ID can only be specified when converting a gconf schema.'
1006 argfile = os.path.expanduser(args[0])
1007 if not os.path.exists(argfile):
1008 print >> sys.stderr, '\'%s\' does not exist.' % argfile
1012 options.output = os.path.expanduser(options.output)
1015 if options.output and not options.force and os.path.exists(options.output):
1016 raise GSettingsSchemaConvertException('\'%s\' already exists. Use --force to overwrite it.' % options.output)
1019 if not options.simple and not options.xml:
1020 options.simple = True
1023 parser = GConfSchemaParser(argfile, options.gettext_domain, options.schema_id)
1024 schema_root = parser.parse()
1025 except SyntaxError, e:
1026 raise GSettingsSchemaConvertException('\'%s\' does not look like a valid gconf schema file: %s' % (argfile, e))
1028 # autodetect if file is XML or not
1030 parser = XMLSchemaParser(argfile)
1031 schema_root = parser.parse()
1032 if not options.simple and not options.xml:
1033 options.simple = True
1034 except SyntaxError, e:
1035 parser = SimpleSchemaParser(argfile)
1036 schema_root = parser.parse()
1037 if not options.simple and not options.xml:
1041 node = schema_root.get_xml_node()
1042 tree = ET.ElementTree(node)
1044 output = ET.tostring(tree, pretty_print = True)
1046 # pretty_print only works with lxml
1047 output = ET.tostring(tree)
1049 output = schema_root.get_simple_string()
1051 if not options.output:
1052 sys.stdout.write(output)
1055 fout = open(options.output, 'w')
1058 except GSettingsSchemaConvertException, e:
1060 if os.path.exists(options.output):
1061 os.unlink(options.output)
1064 except GSettingsSchemaConvertException, e:
1065 print >> sys.stderr, '%s' % e
1071 if __name__ == '__main__':
1073 res = main(sys.argv)
1075 except KeyboardInterrupt: