Return a Czech sort key for the given string

    :param string: string (unicode in Python 2)

    Comparing the sort keys of two strings will give the result according
    to how the strings would compare in Czech collation order, i.e.
        ``key(s1) < key(s2)``  <=>  ``s1`` comes before ``s2``

    The structure of the sort key may change in the future.
    The only operations guaranteed to work on it are comparisons and equality
    checks (<, ==, etc.) against other keys.

def key(string):
    """Return a Czech sort key for the given string

    :param string: string (unicode in Python 2)

    Comparing the sort keys of two strings will give the result according
    to how the strings would compare in Czech collation order, i.e.
        ``key(s1) < key(s2)``  <=>  ``s1`` comes before ``s2``

    The structure of the sort key may change in the future.
    The only operations guaranteed to work on it are comparisons and equality
    checks (<, ==, etc.) against other keys.
    """
    # The multi-level key is a nested tuple containing strings and ints.
    # The tuple contains sub-keys that roughly correspond to levels in
    # UTS #10 (http://unicode.org/reports/tr10/). Except for fallback strings
    # at the end, each contains a tuple of typically one key per element/letter.
    # - Alphabet:
    #    Separators (0, p, l, w)
    #       p: -no. of paragraph separators
    #       l: -no. of line separators
    #       w: -no. of word separators (spaces)
    #    Letters (1, l); l is the base letter, lowercased
    #       Special letters: 'č' shows up as 'cx'; 'ř' as 'rx', etc.
    #                        the 'ch' digraph becomes 'hx'
    #    Numbers (2, n); n is int(numeric value * 100)
    #    Missing for non-letters
    # - Diacritics (p, n, s)
    #    p: position (above, below, behind, in front, in/over/around, unknown)
    #       (as a sorted tuple of indices)
    #    s: shape (dot, grave, breve, ..., unknown)
    #       (as a sorted tuple of indices)
    #    Missing for non-letters; empty if diacritics included in base (e.g. ř)
    # - Case: True for uppercased letters
    #    Missing for non-letters
    # - Punctuation: see PUNCTUATION_MAP below
    # - (fallback) NFKD-normalized string
    # - (fallback) original string

    subkeys = [], [], [], []
    add_alphabet = subkeys[0].append
    add_diacritic = subkeys[1].append
    add_case = subkeys[2].append
    add_punctuation = subkeys[3].append
    skip = 0
    normal = nfkd(string).rstrip()
    diacritics = []
    for i, char in enumerate(normal):
        if skip > 0:
            skip -= 1
            continue
        category = get_category(char)
        cat0, cat1 = category
        if cat0 == 'L':
            # Letter (Lowercase, Modifier, Other, Titlecase, Uppercase)
            char_lower = char.lower()
            found = False
            if char_lower in DECOMPOSING_EXTRAS:
                # stuff like Ł doesn't decompose in Unicode; do it manually
                char_lower, _extra_diacritics = DECOMPOSING_EXTRAS[char]
                diacritics.extend(_extra_diacritics)
            for next in normal[i+1:]:
                if next == HACEK and char_lower in ('c', 'r', 's', 'z'):
                    skip += 1
                    char_lower = char_lower + 'x'
                elif char_lower == 'c' and next.lower() == 'h':
                    skip += 1
                    char_lower = 'hx'
                    break
                elif next in DIACRITICS_MAP:
                    skip += 1
                    diacritics.extend(DIACRITICS_MAP[next])
                elif unicodedata.category(char)[0] == 'M':
                    skip += 1
                    diacritics.append((POS_UNKNOWN, SH_UNKNOWN))
                else:
                    break
            add_alphabet((1, char_lower))
            if diacritics:
                add_diacritic(make_diacritics_key(diacritics))
            else:
                add_diacritic(())
            add_case(cat1 in ('u', 't'))  # upper & title case
            add_punctuation((0, ))
            diacritics = []
        elif cat0 == 'Z':
            # Separator (Line, Paragraph, Space)
            counts = {'Zp': 0, 'Zl': 0, 'Zs': 0}
            counts[category] = 1
            for next in normal[i+1:]:
                next_cat = get_category(next)
                if next_cat[0] == 'Z':
                    counts[next_cat] += 1
                    skip += 1
                else:
                    break
            add_alphabet((0, -counts['Zp'], -counts['Zl'], -counts['Zs']))
            add_diacritic(())
            add_case(False)
            add_punctuation((0, ))
        elif char in DIACRITICS_BEFORE_MAP:
            diacritics.extend(DIACRITICS_BEFORE_MAP[char])
        elif char in DIACRITICS_MAP:
            diacritics.extend(DIACRITICS_MAP[char])
        elif char in PUNCTUATION_MAP:
            add_punctuation(PUNCTUATION_MAP[char])
        elif cat0 == 'P':
            # Punctuation (Connector, Dash, Open/Close, Final/Initial Quote, Other)
            add_punctuation((3, ))
        elif cat0 == 'N':
            # Number (Decimal digit, Letter, Other)
            add_alphabet((2, int(unicodedata.numeric(char, 0)) * 100))
            add_diacritic(())
            add_case(False)
            add_punctuation((0, ))
        elif cat0 == 'S':
            # Symbol (Currency, Modifier, Math)
            add_punctuation((3, ))
        elif cat0 == 'C':
            # Other (Control, Format, Not Assigned, Private Use, Surrogate)
            pass
        elif cat0 == 'M':
            # Mark (Spacing Combining, Enclosing, Nonspacing)
            # TODO
            diacritics.append((POS_FRONT, SH_UNKNOWN))
        else:
            raise ValueError('Unknown Unicode category')
    if diacritics:
        add_diacritic(make_diacritics_key(diacritics))
        diacritics = []
    return tuple(tuple(k) for k in subkeys) + (normal, string)