summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibUnicode/Locale.cpp
blob: 4f796c053b996e533e9aec45da051249f165c5ad (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/*
 * Copyright (c) 2021, Tim Flynn <trflynn89@pm.me>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <AK/AllOf.h>
#include <AK/CharacterTypes.h>
#include <AK/GenericLexer.h>
#include <AK/QuickSort.h>
#include <AK/StringBuilder.h>
#include <LibUnicode/Locale.h>

namespace Unicode {

bool is_unicode_language_subtag(StringView subtag)
{
    // unicode_language_subtag = alpha{2,3} | alpha{5,8}
    if ((subtag.length() < 2) || (subtag.length() == 4) || (subtag.length() > 8))
        return false;
    return all_of(subtag, is_ascii_alpha);
}

bool is_unicode_script_subtag(StringView subtag)
{
    // unicode_script_subtag = alpha{4}
    if (subtag.length() != 4)
        return false;
    return all_of(subtag, is_ascii_alpha);
}

bool is_unicode_region_subtag(StringView subtag)
{
    // unicode_region_subtag = (alpha{2} | digit{3})
    if (subtag.length() == 2)
        return all_of(subtag, is_ascii_alpha);
    if (subtag.length() == 3)
        return all_of(subtag, is_ascii_digit);
    return false;
}

bool is_unicode_variant_subtag(StringView subtag)
{
    // unicode_variant_subtag = (alphanum{5,8} | digit alphanum{3})
    if ((subtag.length() >= 5) && (subtag.length() <= 8))
        return all_of(subtag, is_ascii_alphanumeric);
    if (subtag.length() == 4)
        return is_ascii_digit(subtag[0]) && all_of(subtag.substring_view(1), is_ascii_alphanumeric);
    return false;
}

Optional<LanguageID> parse_unicode_language_id(StringView language)
{
    // https://unicode.org/reports/tr35/#Unicode_language_identifier
    //
    // unicode_language_id = "root"
    //     OR
    // unicode_language_id = ((unicode_language_subtag (sep unicode_script_subtag)?) | unicode_script_subtag)
    //                       (sep unicode_region_subtag)?
    //                       (sep unicode_variant_subtag)*
    LanguageID language_id {};

    if (language == "root"sv) {
        language_id.is_root = true;
        return language_id;
    }

    auto segments = language.split_view_if(is_any_of("-_"sv), true); // keep_empty=true to ensure valid data follows a separator.
    size_t index = 0;

    if (segments.size() == index)
        return {};

    if (is_unicode_language_subtag(segments[index])) {
        language_id.language = segments[index];
        if (segments.size() == ++index)
            return language_id;
    }

    if (is_unicode_script_subtag(segments[index])) {
        language_id.script = segments[index];
        if (segments.size() == ++index)
            return language_id;
    } else if (!language_id.language.has_value()) {
        return {};
    }

    if (is_unicode_region_subtag(segments[index])) {
        language_id.region = segments[index];
        if (segments.size() == ++index)
            return language_id;
    }

    while (index < segments.size()) {
        if (!is_unicode_variant_subtag(segments[index]))
            return {};
        language_id.variants.append(segments[index++]);
    }

    return language_id;
}

Optional<LocaleID> parse_unicode_locale_id(StringView locale)
{
    LocaleID locale_id {};

    // https://unicode.org/reports/tr35/#Unicode_locale_identifier
    //
    // unicode_locale_id = unicode_language_id
    //                     extensions*
    //                     pu_extensions?
    auto language_id = parse_unicode_language_id(locale);
    if (!language_id.has_value())
        return {};

    // FIXME: Handle extensions and pu_extensions.
    return LocaleID { language_id.release_value() };
}

Optional<String> canonicalize_unicode_locale_id(LocaleID& locale_id)
{
    // https://unicode.org/reports/tr35/#Canonical_Unicode_Locale_Identifiers
    StringBuilder builder;

    if (!locale_id.language_id.language.has_value())
        return {};

    builder.append(locale_id.language_id.language->to_lowercase_string());

    if (locale_id.language_id.script.has_value()) {
        builder.append('-');
        builder.append(locale_id.language_id.script->to_titlecase_string());
    }

    if (locale_id.language_id.region.has_value()) {
        builder.append('-');
        builder.append(locale_id.language_id.region->to_uppercase_string());
    }

    quick_sort(locale_id.language_id.variants);

    for (auto const& variant : locale_id.language_id.variants) {
        builder.append('-');
        builder.append(variant.to_lowercase_string());
    }

    // FIXME: Handle extensions and pu_extensions.

    return builder.build();
}

}