Fix override for fr-CA
[cldr2json.git] / cldr2json.py
blob360030b788897c2f16e3a6b7e33aa1714ea280e7
1 #!/usr/bin/python3
3 # Copyright 2015 Daiki Ueno <dueno@src.gnome.org>
4 # 2016 Parag Nemade <pnemade@redhat.com>
5 # 2017 Alan <alan@boum.org>
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU Lesser General Public License as
9 # published by the Free Software Foundation; either version 2 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 # Lesser General Public License for more details.
17 # You should have received a copy of the GNU Lesser General Public
18 # License along with this program; if not, see
19 # <http://www.gnu.org/licenses/>.
21 import glob
22 import json
23 import locale
24 import logging
25 import os
26 import re
27 import sys
28 import xml.etree.ElementTree
30 import gi
31 gi.require_version('GnomeDesktop', '3.0') # NOQA: E402
32 from gi.repository import GnomeDesktop
34 ESCAPE_PATTERN = re.compile(r'\\u\{([0-9A-Fa-f]+?)\}')
35 ISO_PATTERN = re.compile(r'[A-E]([0-9]+)')
37 LOCALE_TO_XKB_OVERRIDES = {
38 'af': 'za',
39 'en': 'us',
40 'en-GB': 'uk',
41 'es-US': 'latam',
42 'fr-CA': 'ca',
43 'hi': 'in+bolnagri',
44 'ky': 'kg',
45 'nl-BE': 'be',
46 'zu': None
50 def parse_single_key(value):
51 def unescape(m):
52 return chr(int(m.group(1), 16))
53 value = ESCAPE_PATTERN.sub(unescape, value)
54 return value
57 def parse_rows(keymap):
58 unsorted_rows = {}
59 for _map in keymap.iter('map'):
60 value = _map.get('to')
61 key = [parse_single_key(value)]
62 iso = _map.get('iso')
63 if not ISO_PATTERN.match(iso):
64 sys.stderr.write('invalid ISO key name: %s\n' % iso)
65 continue
66 if not iso[0] in unsorted_rows:
67 unsorted_rows[iso[0]] = []
68 unsorted_rows[iso[0]].append((int(iso[1:]), key))
69 # add subkeys
70 longPress = _map.get('longPress')
71 if longPress:
72 for value in longPress.split(' '):
73 subkey = parse_single_key(value)
74 key.append(subkey)
76 rows = []
77 for k, v in sorted(list(unsorted_rows.items()),
78 key=lambda x: x[0],
79 reverse=True):
80 row = []
81 for key in sorted(v, key=lambda x: x):
82 row.append(key[1])
83 rows.append(row)
85 return rows
88 def convert_xml(tree):
89 root = {}
90 for xml_keyboard in tree.iter("keyboard"):
91 locale_full = xml_keyboard.get("locale")
92 locale, sep, end = locale_full.partition("-t-")
93 root["locale"] = locale
94 for xml_name in tree.iter("name"):
95 name = xml_name.get("value")
96 root["name"] = name
97 root["levels"] = []
98 # parse levels
99 for index, keymap in enumerate(tree.iter('keyMap')):
100 # FIXME: heuristics here
101 modifiers = keymap.get('modifiers')
102 if not modifiers:
103 mode = 'default'
104 modifiers = ''
105 elif 'shift' in modifiers.split(' '):
106 mode = 'latched'
107 modifiers = 'shift'
108 else:
109 mode = 'locked'
110 level = {}
111 level["level"] = modifiers
112 level["mode"] = mode
113 level["rows"] = parse_rows(keymap)
114 root["levels"].append(level)
115 return root
118 def locale_to_xkb(locale, name):
119 if locale in sorted(LOCALE_TO_XKB_OVERRIDES.keys()):
120 xkb = LOCALE_TO_XKB_OVERRIDES[locale]
121 logging.debug("override for %s %s",
122 locale, xkb)
123 if xkb:
124 return xkb
125 else:
126 raise KeyError("layout %s explicitely disabled in overrides"
127 % locale)
128 xkb_names = sorted(name_to_xkb.keys())
129 if name in xkb_names:
130 return name_to_xkb[name]
131 else:
132 logging.debug("name %s failed" % name)
133 for sub_name in name.split(' '):
134 if sub_name in xkb_names:
135 xkb = name_to_xkb[sub_name]
136 logging.debug("dumb mapping failed but match with locale word: "
137 "%s (%s) → %s (%s)",
138 locale, name, xkb, sub_name)
139 return xkb
140 else:
141 logging.debug("sub_name failed")
142 for xkb_name in xkb_names:
143 for xkb_sub_name in xkb_name.split(' '):
144 if xkb_sub_name.strip('()') == name:
145 xkb = name_to_xkb[xkb_name]
146 logging.debug("dumb mapping failed but match with xkb word: "
147 "%s (%s) → %s (%s)",
148 locale, name, xkb, xkb_name)
149 return xkb
150 raise KeyError("failed to find XKB mapping for %s" % locale)
153 def convert_file(source_file, destination_path):
154 logging.info("Parsing %s", source_file)
156 itree = xml.etree.ElementTree.ElementTree()
157 itree.parse(source_file)
159 root = convert_xml(itree)
161 try:
162 xkb_name = locale_to_xkb(root["locale"], root["name"])
163 except KeyError as e:
164 logging.warn(e)
165 return False
166 destination_file = os.path.join(destination_path, xkb_name + ".json")
168 with open(destination_file, 'w', encoding="utf-8") as dest_fd:
169 json.dump(root, dest_fd, ensure_ascii=False, indent=2, sort_keys=True)
171 logging.debug("written %s", destination_file)
174 def load_xkb_mappings():
175 xkb = GnomeDesktop.XkbInfo()
176 layouts = xkb.get_all_layouts()
177 name_to_xkb = {}
179 for layout in layouts:
180 name = xkb.get_layout_info(layout).display_name
181 name_to_xkb[name] = layout
183 return name_to_xkb
186 locale.setlocale(locale.LC_ALL, "C")
187 name_to_xkb = load_xkb_mappings()
190 if __name__ == "__main__":
191 if "DEBUG" in os.environ:
192 logging.basicConfig(level=logging.DEBUG)
194 if len(sys.argv) < 2:
195 print("supply a CLDR keyboard file")
196 sys.exit(1)
198 if len(sys.argv) < 3:
199 print("supply an output directory")
200 sys.exit(1)
202 source = sys.argv[1]
203 destination = sys.argv[2]
204 if os.path.isfile(source):
205 convert_file(source, destination)
206 elif os.path.isdir(source):
207 for path in glob.glob(source + "/*-t-k0-android.xml"):
208 convert_file(path, destination)