summaryrefslogtreecommitdiffstats
path: root/data/cldr2json
diff options
context:
space:
mode:
Diffstat (limited to 'data/cldr2json')
-rw-r--r--data/cldr2json/README.md40
-rwxr-xr-xdata/cldr2json/cldr2json.py212
-rw-r--r--data/cldr2json/test/__init__.py0
-rw-r--r--data/cldr2json/test/data/fr-t-k0-android.xml138
-rw-r--r--data/cldr2json/test/data/fr.json614
-rwxr-xr-xdata/cldr2json/test/test_cldr2json.py212
6 files changed, 1216 insertions, 0 deletions
diff --git a/data/cldr2json/README.md b/data/cldr2json/README.md
new file mode 100644
index 0000000..0eb54bc
--- /dev/null
+++ b/data/cldr2json/README.md
@@ -0,0 +1,40 @@
+cldr2json
+=========
+
+This script converts Unicode CLDR android keyboard layouts to JSON usable by
+GNOME Shell.
+
+CLDR keyboard layouts can be found at
+<http://www.unicode.org/Public/cldr/latest/keyboards.zip>
+
+
+Usage
+=====
+
+ ./cldr2json <input file or directory> <output directory>
+
+example:
+
+ ./cldr2json cldr/keyboards/android/ json_layouts/
+
+
+Keyboard layout mapping
+=======================
+
+Unicode CLDR layout identifiers are language codes, while XKB layout
+identifiers are... something else. The mapping between the two currently uses
+heuristic based on the layout descriptions, in this order:
+
+- if the CLDR layout description matches an XKB layout description, chose its
+ XKB identifier
+- if one word of the CLDR layout description matches an XKB layout
+ description, chose its XKB identifier
+- if the CLDR layout description matches one word of an XKB layout description,
+ chose its XKB identifier
+
+That doesn't always work. For instance it fails for "en" language, that should
+match "us" XKB identifier. For such cases, there is a mapping in
+LOCALE_TO_XKB_OVERRIDES at the top of the script. If you discover a weird
+mapping of if you get a "failed to find XKB mapping for <locale>" warning then
+please consider adding an override there.
+
diff --git a/data/cldr2json/cldr2json.py b/data/cldr2json/cldr2json.py
new file mode 100755
index 0000000..e5eb3cb
--- /dev/null
+++ b/data/cldr2json/cldr2json.py
@@ -0,0 +1,212 @@
+#!/usr/bin/python3
+#
+# Copyright 2015 Daiki Ueno <dueno@src.gnome.org>
+# 2016 Parag Nemade <pnemade@redhat.com>
+# 2017 Alan <alan@boum.org>
+#
+# 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
+# Lesser 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, see
+# <http://www.gnu.org/licenses/>.
+
+import glob
+import json
+import locale
+import logging
+import os
+import re
+import sys
+import xml.etree.ElementTree
+
+import gi
+gi.require_version('GnomeDesktop', '3.0') # NOQA: E402
+from gi.repository import GnomeDesktop
+
+ESCAPE_PATTERN = re.compile(r'\\u\{([0-9A-Fa-f]+?)\}')
+ISO_PATTERN = re.compile(r'[A-E]([0-9]+)')
+
+LOCALE_TO_XKB_OVERRIDES = {
+ 'af': 'za',
+ 'en': 'us',
+ 'en-GB': 'uk',
+ 'es-US': 'latam',
+ 'fr-CA': 'ca',
+ 'hi': 'in+bolnagri',
+ 'ky': 'kg',
+ 'nl-BE': 'be',
+ 'zu': None
+}
+
+
+def parse_single_key(value):
+ def unescape(m):
+ return chr(int(m.group(1), 16))
+ value = ESCAPE_PATTERN.sub(unescape, value)
+ return value
+
+
+def parse_rows(keymap):
+ unsorted_rows = {}
+ for _map in keymap.iter('map'):
+ value = _map.get('to')
+ key = [parse_single_key(value)]
+ iso = _map.get('iso')
+ if not ISO_PATTERN.match(iso):
+ sys.stderr.write('invalid ISO key name: %s\n' % iso)
+ continue
+ if not iso[0] in unsorted_rows:
+ unsorted_rows[iso[0]] = []
+ unsorted_rows[iso[0]].append((int(iso[1:]), key))
+ # add subkeys
+ longPress = _map.get('longPress')
+ if longPress:
+ for value in longPress.split(' '):
+ subkey = parse_single_key(value)
+ key.append(subkey)
+
+ rows = []
+ for k, v in sorted(list(unsorted_rows.items()),
+ key=lambda x: x[0],
+ reverse=True):
+ row = []
+ for key in sorted(v, key=lambda x: x):
+ row.append({ 'strings': key[1] })
+ rows.append(row)
+
+ return rows
+
+
+def convert_xml(tree):
+ root = {}
+ for xml_keyboard in tree.iter("keyboard"):
+ locale_full = xml_keyboard.get("locale")
+ locale, sep, end = locale_full.partition("-t-")
+ root["locale"] = locale
+ for xml_name in tree.iter("name"):
+ name = xml_name.get("value")
+ root["name"] = name
+ root["levels"] = []
+ # parse levels
+ for index, keymap in enumerate(tree.iter('keyMap')):
+ # FIXME: heuristics here
+ modifiers = keymap.get('modifiers')
+ if not modifiers:
+ mode = 'default'
+ modifiers = ''
+ elif 'shift' in modifiers.split(' '):
+ mode = 'latched'
+ modifiers = 'shift'
+ else:
+ mode = 'locked'
+ level = {}
+ level["level"] = modifiers
+ level["mode"] = mode
+ level["rows"] = parse_rows(keymap)
+ root["levels"].append(level)
+ return root
+
+
+def locale_to_xkb(locale, name):
+ if locale in sorted(LOCALE_TO_XKB_OVERRIDES.keys()):
+ xkb = LOCALE_TO_XKB_OVERRIDES[locale]
+ logging.debug("override for %s → %s",
+ locale, xkb)
+ if xkb:
+ return xkb
+ else:
+ raise KeyError("layout %s explicitly disabled in overrides"
+ % locale)
+ xkb_names = sorted(name_to_xkb.keys())
+ if name in xkb_names:
+ return name_to_xkb[name]
+ else:
+ logging.debug("name %s failed" % name)
+ for sub_name in name.split(' '):
+ if sub_name in xkb_names:
+ xkb = name_to_xkb[sub_name]
+ logging.debug("dumb mapping failed but match with locale word: "
+ "%s (%s) → %s (%s)",
+ locale, name, xkb, sub_name)
+ return xkb
+ else:
+ logging.debug("sub_name failed")
+ for xkb_name in xkb_names:
+ for xkb_sub_name in xkb_name.split(' '):
+ if xkb_sub_name.strip('()') == name:
+ xkb = name_to_xkb[xkb_name]
+ logging.debug("dumb mapping failed but match with xkb word: "
+ "%s (%s) → %s (%s)",
+ locale, name, xkb, xkb_name)
+ return xkb
+ raise KeyError("failed to find XKB mapping for %s" % locale)
+
+
+def convert_file(source_file, destination_path):
+ logging.info("Parsing %s", source_file)
+
+ itree = xml.etree.ElementTree.ElementTree()
+ itree.parse(source_file)
+
+ root = convert_xml(itree)
+
+ try:
+ xkb_name = locale_to_xkb(root["locale"], root["name"])
+ except KeyError as e:
+ logging.warning(e)
+ return False
+ destination_file = os.path.join(destination_path, xkb_name + ".json")
+
+ try:
+ with open(destination_file, 'x', encoding="utf-8") as dest_fd:
+ json.dump(root, dest_fd, ensure_ascii=False, indent=2, sort_keys=True)
+ except FileExistsError as e:
+ logging.info("File %s exists, not updating", destination_file)
+ return False
+
+ logging.debug("written %s", destination_file)
+
+
+def load_xkb_mappings():
+ xkb = GnomeDesktop.XkbInfo()
+ layouts = xkb.get_all_layouts()
+ name_to_xkb = {}
+
+ for layout in layouts:
+ name = xkb.get_layout_info(layout).display_name
+ name_to_xkb[name] = layout
+
+ return name_to_xkb
+
+
+locale.setlocale(locale.LC_ALL, "C")
+name_to_xkb = load_xkb_mappings()
+
+
+if __name__ == "__main__":
+ if "DEBUG" in os.environ:
+ logging.basicConfig(level=logging.DEBUG)
+
+ if len(sys.argv) < 2:
+ print("supply a CLDR keyboard file")
+ sys.exit(1)
+
+ if len(sys.argv) < 3:
+ print("supply an output directory")
+ sys.exit(1)
+
+ source = sys.argv[1]
+ destination = sys.argv[2]
+ if os.path.isfile(source):
+ convert_file(source, destination)
+ elif os.path.isdir(source):
+ for path in glob.glob(source + "/*-t-k0-android.xml"):
+ convert_file(path, destination)
diff --git a/data/cldr2json/test/__init__.py b/data/cldr2json/test/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/data/cldr2json/test/__init__.py
diff --git a/data/cldr2json/test/data/fr-t-k0-android.xml b/data/cldr2json/test/data/fr-t-k0-android.xml
new file mode 100644
index 0000000..1e76b81
--- /dev/null
+++ b/data/cldr2json/test/data/fr-t-k0-android.xml
@@ -0,0 +1,138 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE keyboard SYSTEM "../dtd/ldmlKeyboard.dtd">
+<keyboard locale="fr-t-k0-android">
+ <version platform="4.4" number="$Revision: 11914 $"/>
+ <names>
+ <name value="French"/>
+ </names>
+ <keyMap>
+ <map iso="D01" to="a" longPress="à â % æ á ä ã å ā ª"/> <!-- Q -->
+ <map iso="D02" to="z"/> <!-- W -->
+ <map iso="D03" to="e" longPress="é è ê ë % ę ė ē"/>
+ <map iso="D04" to="r"/>
+ <map iso="D05" to="t"/>
+ <map iso="D06" to="y" longPress="% ÿ"/>
+ <map iso="D07" to="u" longPress="ù û % ü ú ū"/>
+ <map iso="D08" to="i" longPress="î % ï ì í į ī"/>
+ <map iso="D09" to="o" longPress="ô œ % ö ò ó õ ø ō º"/>
+ <map iso="D10" to="p"/>
+ <map iso="C01" to="q"/> <!-- A -->
+ <map iso="C02" to="s"/>
+ <map iso="C03" to="d"/>
+ <map iso="C04" to="f"/>
+ <map iso="C05" to="g"/>
+ <map iso="C06" to="h"/>
+ <map iso="C07" to="j"/>
+ <map iso="C08" to="k"/>
+ <map iso="C09" to="l"/>
+ <map iso="C10" to="m"/> <!-- ; -->
+ <map iso="B01" to="w"/> <!-- Z -->
+ <map iso="B02" to="x"/>
+ <map iso="B03" to="c" longPress="ç ć č"/>
+ <map iso="B04" to="v"/>
+ <map iso="B05" to="b"/>
+ <map iso="B06" to="n"/>
+ <map iso="B07" to="&apos;" longPress="‘ ’ ‹ ›"/> <!-- M -->
+ <map iso="A02" to=","/> <!-- (key to left of space) -->
+ <map iso="A03" to=" "/> <!-- space -->
+ <map iso="A04" to="." longPress="# ! , ? - : ' @"/> <!-- (key to right of space) -->
+ </keyMap>
+ <keyMap modifiers="shift caps">
+ <map iso="D01" to="A" longPress="À Â % Æ Á Ä Ã Å Ā ª"/> <!-- Q -->
+ <map iso="D02" to="Z"/> <!-- W -->
+ <map iso="D03" to="E" longPress="É È Ê Ë % Ę Ė Ē"/>
+ <map iso="D04" to="R"/>
+ <map iso="D05" to="T"/>
+ <map iso="D06" to="Y" longPress="% Ÿ"/>
+ <map iso="D07" to="U" longPress="Ù Û % Ü Ú Ū"/>
+ <map iso="D08" to="I" longPress="Î % Ï Ì Í Į Ī"/>
+ <map iso="D09" to="O" longPress="Ô Œ % Ö Ò Ó Õ Ø Ō º"/>
+ <map iso="D10" to="P"/>
+ <map iso="C01" to="Q"/> <!-- A -->
+ <map iso="C02" to="S"/>
+ <map iso="C03" to="D"/>
+ <map iso="C04" to="F"/>
+ <map iso="C05" to="G"/>
+ <map iso="C06" to="H"/>
+ <map iso="C07" to="J"/>
+ <map iso="C08" to="K"/>
+ <map iso="C09" to="L"/>
+ <map iso="C10" to="M"/> <!-- ; -->
+ <map iso="B01" to="W"/> <!-- Z -->
+ <map iso="B02" to="X"/>
+ <map iso="B03" to="C" longPress="Ç Ć Č"/>
+ <map iso="B04" to="V"/>
+ <map iso="B05" to="B"/>
+ <map iso="B06" to="N"/>
+ <map iso="B07" to="&apos;" longPress="‘ ’ ‹ ›"/> <!-- M -->
+ <map iso="A02" to=","/> <!-- (key to left of space) -->
+ <map iso="A03" to=" "/> <!-- space -->
+ <map iso="A04" to="." longPress="# ! , ? - : ' @"/> <!-- (key to right of space) -->
+ </keyMap>
+ <keyMap modifiers="opt">
+ <map iso="D01" to="1" longPress="¹ ½ ⅓ ¼ ⅛"/> <!-- Q base=a -->
+ <map iso="D02" to="2" longPress="² ⅔"/> <!-- W base=z -->
+ <map iso="D03" to="3" longPress="³ ¾ ⅜"/> <!-- E -->
+ <map iso="D04" to="4" longPress="⁴"/> <!-- R -->
+ <map iso="D05" to="5" longPress="⅝"/> <!-- T -->
+ <map iso="D06" to="6"/> <!-- Y -->
+ <map iso="D07" to="7" longPress="⅞"/> <!-- U -->
+ <map iso="D08" to="8"/> <!-- I -->
+ <map iso="D09" to="9"/> <!-- O -->
+ <map iso="D10" to="0" longPress="ⁿ ∅"/> <!-- P -->
+ <map iso="C01" to="@"/> <!-- A base=q -->
+ <map iso="C02" to="#"/> <!-- S -->
+ <map iso="C03" to="€" longPress="¢ £ $ ¥ ₱"/> <!-- D -->
+ <map iso="C04" to="%" longPress="‰"/> <!-- F -->
+ <map iso="C05" to="&amp;"/> <!-- G -->
+ <map iso="C06" to="-" longPress="_ – — ·"/> <!-- H -->
+ <map iso="C07" to="+" longPress="±"/> <!-- J -->
+ <map iso="C08" to="(" longPress="&lt; { ["/> <!-- K -->
+ <map iso="C09" to=")" longPress="&gt; } ]"/> <!-- L -->
+ <map iso="B01" to="*" longPress="† ‡ ★"/> <!-- Z base=w -->
+ <map iso="B02" to="\u{22}" longPress="“ ” « »"/> <!-- X to= " -->
+ <map iso="B03" to="&apos;" longPress="‘ ’ ‹ ›"/> <!-- C -->
+ <map iso="B04" to=":"/> <!-- V -->
+ <map iso="B05" to=";"/> <!-- B -->
+ <map iso="B06" to="!" longPress="¡"/> <!-- N -->
+ <map iso="B07" to="?" longPress="¿"/> <!-- M base=' -->
+ <map iso="A00" to="_"/> <!-- (3 keys to left of space) -->
+ <map iso="A01" to="/"/> <!-- (2 keys to left of space) -->
+ <map iso="A02" to=" "/> <!-- (key to left of space) base=, -->
+ <map iso="A03" to=","/> <!-- space -->
+ <map iso="A04" to="." longPress="…"/> <!-- (key to right of space) -->
+ </keyMap>
+ <keyMap modifiers="opt+shift">
+ <map iso="D01" to="~"/> <!-- Q base=a -->
+ <map iso="D02" to="`"/> <!-- W base=z -->
+ <map iso="D03" to="|"/> <!-- E -->
+ <map iso="D04" to="•" longPress="♪ ♥ ♠ ♦ ♣"/> <!-- R -->
+ <map iso="D05" to="√"/> <!-- T -->
+ <map iso="D06" to="Π" longPress="π"/> <!-- Y -->
+ <map iso="D07" to="÷"/> <!-- U -->
+ <map iso="D08" to="×"/> <!-- I -->
+ <map iso="D09" to="¶" longPress="§"/> <!-- O -->
+ <map iso="D10" to="∆"/> <!-- P -->
+ <map iso="C01" to="£"/> <!-- A base=q -->
+ <map iso="C02" to="¥"/> <!-- S -->
+ <map iso="C03" to="$" longPress="¢"/> <!-- D -->
+ <map iso="C04" to="¢"/> <!-- F -->
+ <map iso="C05" to="^" longPress="↑ ↓ ← →"/> <!-- G -->
+ <map iso="C06" to="°" longPress="′ ″"/> <!-- H -->
+ <map iso="C07" to="=" longPress="≠ ≈ ∞"/> <!-- J -->
+ <map iso="C08" to="{"/> <!-- K -->
+ <map iso="C09" to="}"/> <!-- L -->
+ <map iso="B01" to="\"/> <!-- Z base=w -->
+ <map iso="B02" to="©"/> <!-- X -->
+ <map iso="B03" to="®"/> <!-- C -->
+ <map iso="B04" to="™"/> <!-- V -->
+ <map iso="B05" to="℅"/> <!-- B -->
+ <map iso="B06" to="["/> <!-- N -->
+ <map iso="B07" to="]"/> <!-- M base=' -->
+ <map iso="A00" to="&lt;" longPress="‹ ≤ «"/> <!-- (3 keys to left of space) -->
+ <map iso="A01" to="&gt;" longPress="› ≥ »"/> <!-- (2 keys to left of space) -->
+ <map iso="A02" to=" "/> <!-- (key to left of space) base=, -->
+ <map iso="A03" to=","/> <!-- space -->
+ <map iso="A04" to="." longPress="…"/> <!-- (key to right of space) -->
+ </keyMap>
+</keyboard>
diff --git a/data/cldr2json/test/data/fr.json b/data/cldr2json/test/data/fr.json
new file mode 100644
index 0000000..3a8949f
--- /dev/null
+++ b/data/cldr2json/test/data/fr.json
@@ -0,0 +1,614 @@
+{
+ "levels": [
+ {
+ "level": "",
+ "mode": "default",
+ "rows": [
+ [
+ [
+ "a",
+ "à",
+ "â",
+ "%",
+ "æ",
+ "á",
+ "ä",
+ "ã",
+ "å",
+ "ā",
+ "ª"
+ ],
+ [
+ "z"
+ ],
+ [
+ "e",
+ "é",
+ "è",
+ "ê",
+ "ë",
+ "%",
+ "ę",
+ "ė",
+ "ē"
+ ],
+ [
+ "r"
+ ],
+ [
+ "t"
+ ],
+ [
+ "y",
+ "%",
+ "ÿ"
+ ],
+ [
+ "u",
+ "ù",
+ "û",
+ "%",
+ "ü",
+ "ú",
+ "ū"
+ ],
+ [
+ "i",
+ "î",
+ "%",
+ "ï",
+ "ì",
+ "í",
+ "į",
+ "ī"
+ ],
+ [
+ "o",
+ "ô",
+ "œ",
+ "%",
+ "ö",
+ "ò",
+ "ó",
+ "õ",
+ "ø",
+ "ō",
+ "º"
+ ],
+ [
+ "p"
+ ]
+ ],
+ [
+ [
+ "q"
+ ],
+ [
+ "s"
+ ],
+ [
+ "d"
+ ],
+ [
+ "f"
+ ],
+ [
+ "g"
+ ],
+ [
+ "h"
+ ],
+ [
+ "j"
+ ],
+ [
+ "k"
+ ],
+ [
+ "l"
+ ],
+ [
+ "m"
+ ]
+ ],
+ [
+ [
+ "w"
+ ],
+ [
+ "x"
+ ],
+ [
+ "c",
+ "ç",
+ "ć",
+ "č"
+ ],
+ [
+ "v"
+ ],
+ [
+ "b"
+ ],
+ [
+ "n"
+ ],
+ [
+ "'",
+ "‘",
+ "’",
+ "‹",
+ "›"
+ ]
+ ],
+ [
+ [
+ ","
+ ],
+ [
+ " "
+ ],
+ [
+ ".",
+ "#",
+ "!",
+ ",",
+ "?",
+ "-",
+ ":",
+ "'",
+ "@"
+ ]
+ ]
+ ]
+ },
+ {
+ "level": "shift",
+ "mode": "latched",
+ "rows": [
+ [
+ [
+ "A",
+ "À",
+ "Â",
+ "%",
+ "Æ",
+ "Á",
+ "Ä",
+ "Ã",
+ "Å",
+ "Ā",
+ "ª"
+ ],
+ [
+ "Z"
+ ],
+ [
+ "E",
+ "É",
+ "È",
+ "Ê",
+ "Ë",
+ "%",
+ "Ę",
+ "Ė",
+ "Ē"
+ ],
+ [
+ "R"
+ ],
+ [
+ "T"
+ ],
+ [
+ "Y",
+ "%",
+ "Ÿ"
+ ],
+ [
+ "U",
+ "Ù",
+ "Û",
+ "%",
+ "Ü",
+ "Ú",
+ "Ū"
+ ],
+ [
+ "I",
+ "Î",
+ "%",
+ "Ï",
+ "Ì",
+ "Í",
+ "Į",
+ "Ī"
+ ],
+ [
+ "O",
+ "Ô",
+ "Œ",
+ "%",
+ "Ö",
+ "Ò",
+ "Ó",
+ "Õ",
+ "Ø",
+ "Ō",
+ "º"
+ ],
+ [
+ "P"
+ ]
+ ],
+ [
+ [
+ "Q"
+ ],
+ [
+ "S"
+ ],
+ [
+ "D"
+ ],
+ [
+ "F"
+ ],
+ [
+ "G"
+ ],
+ [
+ "H"
+ ],
+ [
+ "J"
+ ],
+ [
+ "K"
+ ],
+ [
+ "L"
+ ],
+ [
+ "M"
+ ]
+ ],
+ [
+ [
+ "W"
+ ],
+ [
+ "X"
+ ],
+ [
+ "C",
+ "Ç",
+ "Ć",
+ "Č"
+ ],
+ [
+ "V"
+ ],
+ [
+ "B"
+ ],
+ [
+ "N"
+ ],
+ [
+ "'",
+ "‘",
+ "’",
+ "‹",
+ "›"
+ ]
+ ],
+ [
+ [
+ ","
+ ],
+ [
+ " "
+ ],
+ [
+ ".",
+ "#",
+ "!",
+ ",",
+ "?",
+ "-",
+ ":",
+ "'",
+ "@"
+ ]
+ ]
+ ]
+ },
+ {
+ "level": "opt",
+ "mode": "locked",
+ "rows": [
+ [
+ [
+ "1",
+ "¹",
+ "½",
+ "⅓",
+ "¼",
+ "⅛"
+ ],
+ [
+ "2",
+ "²",
+ "⅔"
+ ],
+ [
+ "3",
+ "³",
+ "¾",
+ "⅜"
+ ],
+ [
+ "4",
+ "⁴"
+ ],
+ [
+ "5",
+ "⅝"
+ ],
+ [
+ "6"
+ ],
+ [
+ "7",
+ "⅞"
+ ],
+ [
+ "8"
+ ],
+ [
+ "9"
+ ],
+ [
+ "0",
+ "ⁿ",
+ "∅"
+ ]
+ ],
+ [
+ [
+ "@"
+ ],
+ [
+ "#"
+ ],
+ [
+ "€",
+ "¢",
+ "£",
+ "$",
+ "¥",
+ "₱"
+ ],
+ [
+ "%",
+ "‰"
+ ],
+ [
+ "&"
+ ],
+ [
+ "-",
+ "_",
+ "–",
+ "—",
+ "·"
+ ],
+ [
+ "+",
+ "±"
+ ],
+ [
+ "(",
+ "<",
+ "{",
+ "["
+ ],
+ [
+ ")",
+ ">",
+ "}",
+ "]"
+ ]
+ ],
+ [
+ [
+ "*",
+ "†",
+ "‡",
+ "★"
+ ],
+ [
+ "\"",
+ "“",
+ "”",
+ "«",
+ "»"
+ ],
+ [
+ "'",
+ "‘",
+ "’",
+ "‹",
+ "›"
+ ],
+ [
+ ":"
+ ],
+ [
+ ";"
+ ],
+ [
+ "!",
+ "¡"
+ ],
+ [
+ "?",
+ "¿"
+ ]
+ ],
+ [
+ [
+ "_"
+ ],
+ [
+ "/"
+ ],
+ [
+ " "
+ ],
+ [
+ ","
+ ],
+ [
+ ".",
+ "…"
+ ]
+ ]
+ ]
+ },
+ {
+ "level": "opt+shift",
+ "mode": "locked",
+ "rows": [
+ [
+ [
+ "~"
+ ],
+ [
+ "`"
+ ],
+ [
+ "|"
+ ],
+ [
+ "•",
+ "♪",
+ "♥",
+ "♠",
+ "♦",
+ "♣"
+ ],
+ [
+ "√"
+ ],
+ [
+ "Π",
+ "π"
+ ],
+ [
+ "÷"
+ ],
+ [
+ "×"
+ ],
+ [
+ "¶",
+ "§"
+ ],
+ [
+ "∆"
+ ]
+ ],
+ [
+ [
+ "£"
+ ],
+ [
+ "¥"
+ ],
+ [
+ "$",
+ "¢"
+ ],
+ [
+ "¢"
+ ],
+ [
+ "^",
+ "↑",
+ "↓",
+ "←",
+ "→"
+ ],
+ [
+ "°",
+ "′",
+ "″"
+ ],
+ [
+ "=",
+ "≠",
+ "≈",
+ "∞"
+ ],
+ [
+ "{"
+ ],
+ [
+ "}"
+ ]
+ ],
+ [
+ [
+ "\\"
+ ],
+ [
+ "©"
+ ],
+ [
+ "®"
+ ],
+ [
+ "™"
+ ],
+ [
+ "℅"
+ ],
+ [
+ "["
+ ],
+ [
+ "]"
+ ]
+ ],
+ [
+ [
+ "<",
+ "‹",
+ "≤",
+ "«"
+ ],
+ [
+ ">",
+ "›",
+ "≥",
+ "»"
+ ],
+ [
+ " "
+ ],
+ [
+ ","
+ ],
+ [
+ ".",
+ "…"
+ ]
+ ]
+ ]
+ }
+ ],
+ "locale": "fr",
+ "name": "French"
+}
diff --git a/data/cldr2json/test/test_cldr2json.py b/data/cldr2json/test/test_cldr2json.py
new file mode 100755
index 0000000..78a6aa0
--- /dev/null
+++ b/data/cldr2json/test/test_cldr2json.py
@@ -0,0 +1,212 @@
+#!/usr/bin/python3
+
+import json
+import tempfile
+import unittest
+import xml.etree.ElementTree
+
+import cldr2json
+
+
+class TestParseSingleKey(unittest.TestCase):
+ def test_ascii(self):
+ self.assertEqual(cldr2json.parse_single_key("a"), "a")
+
+ def test_nonascii(self):
+ self.assertEqual(cldr2json.parse_single_key("Æ"), "Æ")
+
+ def test_twochars(self):
+ self.assertEqual(cldr2json.parse_single_key("ԵՒ"), "ԵՒ")
+
+ def test_decode(self):
+ self.assertEqual(cldr2json.parse_single_key("\\u{200D}"), "\u200d")
+
+ def test_decode_threechars(self):
+ self.assertEqual(cldr2json.parse_single_key("ज\\u{94D}ञ"), "ज\u094Dञ")
+
+
+class TestParseRow(unittest.TestCase):
+ def test_parse_row(self):
+ xml_string = """
+ <keyMap>
+ <map iso="D01" to="a" longPress="à â % æ á ä ã å ā ª"/> <!-- Q -->
+ <map iso="D02" to="z"/> <!-- W -->
+ <map iso="D03" to="e" longPress="é è ê ë % ę ė ē"/>
+ <map iso="D04" to="r"/>
+ <map iso="D05" to="t"/>
+ <map iso="D06" to="y" longPress="% ÿ"/>
+ <map iso="D07" to="u" longPress="ù û % ü ú ū"/>
+ <map iso="D08" to="i" longPress="î % ï ì í į ī"/>
+ <map iso="D09" to="o" longPress="ô œ % ö ò ó õ ø ō º"/>
+ <map iso="D10" to="p"/>
+ <map iso="C01" to="q"/> <!-- A -->
+ <map iso="C02" to="s"/>
+ <map iso="C03" to="d"/>
+ <map iso="C04" to="f"/>
+ <map iso="C05" to="g"/>
+ <map iso="C06" to="h"/>
+ <map iso="C07" to="j"/>
+ <map iso="C08" to="k"/>
+ <map iso="C09" to="l"/>
+ <map iso="C10" to="m"/> <!-- ; -->
+ <map iso="B01" to="w"/> <!-- Z -->
+ <map iso="B02" to="x"/>
+ <map iso="B03" to="c" longPress="ç ć č"/>
+ <map iso="B04" to="v"/>
+ <map iso="B05" to="b"/>
+ <map iso="B06" to="n"/>
+ <map iso="B07" to="&apos;" longPress="‘ ’ ‹ ›"/> <!-- M -->
+ <map iso="A02" to=","/> <!-- (key to left of space) -->
+ <map iso="A03" to=" "/> <!-- space -->
+ <map iso="A04" to="." longPress="# ! , ? - : ' @"/> <!-- (key to right of space) -->
+ </keyMap>
+ """
+ xml_tree = xml.etree.ElementTree.XML(xml_string)
+ json = [[
+ ['a', 'à', 'â', '%', 'æ', 'á', 'ä', 'ã', 'å', 'ā', 'ª'],
+ ['z'],
+ ['e', 'é', 'è', 'ê', 'ë', '%', 'ę', 'ė', 'ē'],
+ ['r'],
+ ['t'],
+ ['y', '%', 'ÿ'],
+ ['u', 'ù', 'û', '%', 'ü', 'ú', 'ū'],
+ ['i', 'î', '%', 'ï', 'ì', 'í', 'į', 'ī'],
+ ['o', 'ô', 'œ', '%', 'ö', 'ò', 'ó', 'õ', 'ø', 'ō', 'º'],
+ ['p']
+ ], [
+ ['q'],
+ ['s'],
+ ['d'],
+ ['f'],
+ ['g'],
+ ['h'],
+ ['j'],
+ ['k'],
+ ['l'],
+ ['m']
+ ], [
+ ['w'],
+ ['x'],
+ ['c', 'ç', 'ć', 'č'],
+ ['v'],
+ ['b'],
+ ['n'],
+ ["'", '‘', '’', '‹', '›']
+ ], [
+ [','],
+ [' '],
+ ['.', '#', '!', ',', '?', '-', ':', "'", '@']
+ ]]
+ self.assertEqual(cldr2json.parse_rows(xml_tree), json)
+
+
+class TestConvertXml(unittest.TestCase):
+ def test_convert_xml(self):
+ xml_string = """<?xml version="1.0" encoding="UTF-8" ?>
+ <!DOCTYPE keyboard SYSTEM "../dtd/ldmlKeyboard.dtd">
+ <keyboard locale="fr-t-k0-android">
+ <version platform="4.4" number="$Revision: 11914 $"/>
+ <names>
+ <name value="French"/>
+ </names>
+ <keyMap>
+ <map iso="D01" to="a" longPress="à â % æ á ä ã å ā ª"/> <!-- Q -->
+ </keyMap>
+ <keyMap modifiers="shift caps">
+ <map iso="D01" to="A" longPress="À Â % Æ Á Ä Ã Å Ā ª"/> <!-- Q -->
+ </keyMap>
+ <keyMap modifiers="opt">
+ <map iso="D01" to="1" longPress="¹ ½ ⅓ ¼ ⅛"/> <!-- Q base=a -->
+ </keyMap>
+ <keyMap modifiers="opt+shift">
+ <map iso="D01" to="~"/> <!-- Q base=a -->
+ </keyMap>
+ </keyboard>
+ """
+ xml_tree = xml.etree.ElementTree.XML(xml_string)
+ json = {
+ "locale": "fr",
+ "name": "French",
+ "levels": [
+ {
+ "level": "",
+ "mode": "default",
+ "rows": [
+ [
+ ['a', 'à', 'â', '%', 'æ', 'á', 'ä', 'ã', 'å', 'ā', 'ª'],
+ ]
+ ]
+ },
+ {
+ "level": "shift",
+ "mode": "latched",
+ "rows": [
+ [
+ ['A', 'À', 'Â', '%', 'Æ', 'Á', 'Ä', 'Ã', 'Å', 'Ā', 'ª'],
+ ]
+ ]
+ },
+ {
+ "level": "opt",
+ "mode": "locked",
+ "rows": [
+ [
+ ["1", "¹", "½", "⅓", "¼", "⅛"],
+ ]
+ ]
+ },
+ {
+ "level": "opt+shift",
+ "mode": "locked",
+ "rows": [
+ [
+ ["~"],
+ ]
+ ]
+ }
+ ]
+ }
+ self.assertEqual(cldr2json.convert_xml(xml_tree), json)
+
+
+class TestConvertFile(unittest.TestCase):
+ def test_fr(self):
+ outdir = tempfile.mkdtemp()
+ cldr2json.convert_file("test/data/fr-t-k0-android.xml", outdir)
+ with open("test/data/fr.json", encoding="utf-8") as expected_json_fd:
+ expected_json = json.load(expected_json_fd)
+ with open(outdir + "/fr.json", encoding="utf-8") as actual_json_fd:
+ actual_json = json.load(actual_json_fd)
+ self.assertEqual(expected_json, actual_json)
+
+
+class TestLocaleToXKB(unittest.TestCase):
+ def test_simple(self):
+ self.assertEqual(cldr2json.locale_to_xkb("fr", "French"),
+ "fr")
+
+ def test_fallback(self):
+ self.assertEqual(cldr2json.locale_to_xkb("nb", "Norwegian Bokmål"),
+ "no")
+
+ def test_fallback2(self):
+ self.assertEqual(cldr2json.locale_to_xkb("km", "Khmer"),
+ "kh")
+
+ def test_override(self):
+ self.assertEqual(cldr2json.locale_to_xkb("en-GB",
+ "English Great Britain"),
+ "uk")
+
+
+class LoadXKBMapplings(unittest.TestCase):
+ def test_dictionnary(self):
+ self.assertIsInstance(cldr2json.load_xkb_mappings(), dict)
+
+ def test_mapping(self):
+ mapping = cldr2json.load_xkb_mappings()
+ self.assertEqual(mapping["French"], "fr")
+
+
+if __name__ == '__main__':
+ unittest.main()