/src/mozilla-central/intl/locale/MozLocale.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
3 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
4 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
5 | | |
6 | | #include "mozilla/intl/MozLocale.h" |
7 | | |
8 | | #include "nsReadableUtils.h" |
9 | | #include "nsUnicharUtils.h" |
10 | | |
11 | | #include "unicode/uloc.h" |
12 | | |
13 | | using namespace mozilla::intl; |
14 | | |
15 | | /** |
16 | | * Note: The file name is `MozLocale` to avoid compilation problems on case-insensitive |
17 | | * Windows. The class name is `Locale`. |
18 | | */ |
19 | | Locale::Locale(const nsACString& aLocale) |
20 | 12 | { |
21 | 12 | MOZ_ASSERT(!aLocale.IsEmpty(), "Locale string cannot be empty"); |
22 | 12 | |
23 | 12 | int32_t position = 0; |
24 | 12 | |
25 | 12 | if (!IsASCII(aLocale)) { |
26 | 0 | mIsWellFormed = false; |
27 | 0 | return; |
28 | 0 | } |
29 | 12 | |
30 | 12 | nsAutoCString normLocale(aLocale); |
31 | 12 | normLocale.ReplaceChar('_', '-'); |
32 | 12 | |
33 | 12 | /** |
34 | 12 | * BCP47 language tag: |
35 | 12 | * |
36 | 12 | * langtag = language 2*3ALPHA |
37 | 12 | * ["-" extlang] 3ALPHA *2("-" 3ALPHA) |
38 | 12 | * ["-" script] 4ALPHA |
39 | 12 | * ["-" region] 2ALPHA / 3DIGIT |
40 | 12 | * *("-" variant) 5*8alphanum / (DIGIT 3alphanum) |
41 | 12 | * *("-" extension) [0-9a-wy-z] 1*("-" (1*8alphanum)) |
42 | 12 | * ["-" privateuse] x 1*("-" (1*8alphanum)) |
43 | 12 | * |
44 | 12 | * This class currently supports a subset of the full BCP47 language tag |
45 | 12 | * with a single extension of allowing variants to be 3ALPHA to support |
46 | 12 | * `ja-JP-mac` code: |
47 | 12 | * |
48 | 12 | * langtag = language 2*3ALPHA |
49 | 12 | * ["-" script] 4ALPHA |
50 | 12 | * ["-" region] 2ALPHA |
51 | 12 | * *("-" variant) 3*8alphanum |
52 | 12 | * ["-"] privateuse] "x" 1*("-" (1*8alphanum)) |
53 | 12 | * |
54 | 12 | * The `position` variable represents the currently expected section of the tag |
55 | 12 | * and intentionally skips positions (like `extlang`) which may be added later. |
56 | 12 | * |
57 | 12 | * language-extlangs-script-region-variant-extension-privateuse |
58 | 12 | * --- 0 -- --- 1 -- -- 2 - -- 3 - -- 4 -- --- x --- ---- 6 --- |
59 | 12 | */ |
60 | 30 | for (const nsACString& subTag : normLocale.Split('-')) { |
61 | 30 | auto slen = subTag.Length(); |
62 | 30 | if (slen > 8) { |
63 | 0 | mIsWellFormed = false; |
64 | 0 | return; |
65 | 30 | } else if (position == 6) { |
66 | 0 | ToLowerCase(*mPrivateUse.AppendElement(subTag)); |
67 | 30 | } else if (subTag.LowerCaseEqualsLiteral("x")) { |
68 | 0 | position = 6; |
69 | 30 | } else if (position == 0) { |
70 | 12 | if (slen < 2 || slen > 3) { |
71 | 0 | mIsWellFormed = false; |
72 | 0 | return; |
73 | 0 | } |
74 | 12 | mLanguage = subTag; |
75 | 12 | ToLowerCase(mLanguage); |
76 | 12 | position = 2; |
77 | 18 | } else if (position <= 2 && slen == 4) { |
78 | 6 | mScript = subTag; |
79 | 6 | ToLowerCase(mScript); |
80 | 6 | mScript.Replace(0, 1, ToUpperCase(mScript[0])); |
81 | 6 | position = 3; |
82 | 12 | } else if (position <= 3 && slen == 2) { |
83 | 12 | mRegion = subTag; |
84 | 12 | ToUpperCase(mRegion); |
85 | 12 | position = 4; |
86 | 12 | } else if (position <= 4 && slen >= 5 && slen <= 8) { |
87 | 0 | nsAutoCString lcSubTag(subTag); |
88 | 0 | ToLowerCase(lcSubTag); |
89 | 0 | mVariants.InsertElementSorted(lcSubTag); |
90 | 0 | position = 4; |
91 | 0 | } |
92 | 30 | } |
93 | 12 | } |
94 | | |
95 | | const nsCString |
96 | | Locale::AsString() const |
97 | 9 | { |
98 | 9 | nsCString tag; |
99 | 9 | |
100 | 9 | if (!mIsWellFormed) { |
101 | 0 | tag.AppendLiteral("und"); |
102 | 0 | return tag; |
103 | 0 | } |
104 | 9 | |
105 | 9 | tag.Append(mLanguage); |
106 | 9 | |
107 | 9 | if (!mScript.IsEmpty()) { |
108 | 3 | tag.AppendLiteral("-"); |
109 | 3 | tag.Append(mScript); |
110 | 3 | } |
111 | 9 | |
112 | 9 | if (!mRegion.IsEmpty()) { |
113 | 6 | tag.AppendLiteral("-"); |
114 | 6 | tag.Append(mRegion); |
115 | 6 | } |
116 | 9 | |
117 | 9 | for (const auto& variant : mVariants) { |
118 | 0 | tag.AppendLiteral("-"); |
119 | 0 | tag.Append(variant); |
120 | 0 | } |
121 | 9 | |
122 | 9 | if (!mPrivateUse.IsEmpty()) { |
123 | 0 | if (tag.IsEmpty()) { |
124 | 0 | tag.AppendLiteral("x"); |
125 | 0 | } else { |
126 | 0 | tag.AppendLiteral("-x"); |
127 | 0 | } |
128 | 0 |
|
129 | 0 | for (const auto& subTag : mPrivateUse) { |
130 | 0 | tag.AppendLiteral("-"); |
131 | 0 | tag.Append(subTag); |
132 | 0 | } |
133 | 0 | } |
134 | 9 | return tag; |
135 | 9 | } |
136 | | |
137 | | const nsACString& |
138 | | Locale::GetLanguage() const |
139 | 0 | { |
140 | 0 | return mLanguage; |
141 | 0 | } |
142 | | |
143 | | const nsACString& |
144 | | Locale::GetScript() const |
145 | 0 | { |
146 | 0 | return mScript; |
147 | 0 | } |
148 | | |
149 | | const nsACString& |
150 | | Locale::GetRegion() const |
151 | 0 | { |
152 | 0 | return mRegion; |
153 | 0 | } |
154 | | |
155 | | const nsTArray<nsCString>& |
156 | | Locale::GetVariants() const |
157 | 0 | { |
158 | 0 | return mVariants; |
159 | 0 | } |
160 | | |
161 | | bool |
162 | | Locale::Matches(const Locale& aOther, bool aThisRange, bool aOtherRange) const |
163 | 15 | { |
164 | 15 | if (!IsWellFormed() || !aOther.IsWellFormed()) { |
165 | 15 | return false; |
166 | 15 | } |
167 | 0 | |
168 | 0 | if ((!aThisRange || !mLanguage.IsEmpty()) && |
169 | 0 | (!aOtherRange || !aOther.mLanguage.IsEmpty()) && |
170 | 0 | !mLanguage.Equals(aOther.mLanguage)) { |
171 | 0 | return false; |
172 | 0 | } |
173 | 0 | |
174 | 0 | if ((!aThisRange || !mScript.IsEmpty()) && |
175 | 0 | (!aOtherRange || !aOther.mScript.IsEmpty()) && |
176 | 0 | !mScript.Equals(aOther.mScript)) { |
177 | 0 | return false; |
178 | 0 | } |
179 | 0 | if ((!aThisRange || !mRegion.IsEmpty()) && |
180 | 0 | (!aOtherRange || !aOther.mRegion.IsEmpty()) && |
181 | 0 | !mRegion.Equals(aOther.mRegion)) { |
182 | 0 | return false; |
183 | 0 | } |
184 | 0 | if ((!aThisRange || !mVariants.IsEmpty()) && |
185 | 0 | (!aOtherRange || !aOther.mVariants.IsEmpty()) && |
186 | 0 | mVariants != aOther.mVariants) { |
187 | 0 | return false; |
188 | 0 | } |
189 | 0 | return true; |
190 | 0 | } |
191 | | |
192 | | bool |
193 | | Locale::AddLikelySubtags() |
194 | 6 | { |
195 | 6 | const int32_t kLocaleMax = 160; |
196 | 6 | char maxLocale[kLocaleMax]; |
197 | 6 | |
198 | 6 | UErrorCode status = U_ZERO_ERROR; |
199 | 6 | uloc_addLikelySubtags(AsString().get(), maxLocale, kLocaleMax, &status); |
200 | 6 | |
201 | 6 | if (U_FAILURE(status)) { |
202 | 0 | return false; |
203 | 0 | } |
204 | 6 | |
205 | 6 | nsDependentCString maxLocStr(maxLocale); |
206 | 6 | Locale loc = Locale(maxLocStr); |
207 | 6 | |
208 | 6 | if (loc == *this) { |
209 | 0 | return false; |
210 | 0 | } |
211 | 6 | |
212 | 6 | mLanguage = loc.mLanguage; |
213 | 6 | mScript = loc.mScript; |
214 | 6 | mRegion = loc.mRegion; |
215 | 6 | |
216 | 6 | // We don't update variant from likelySubtag since it's not going to |
217 | 6 | // provide it and we want to preserve the range |
218 | 6 | |
219 | 6 | return true; |
220 | 6 | } |
221 | | |
222 | | void |
223 | | Locale::ClearVariants() |
224 | 3 | { |
225 | 3 | mVariants.Clear(); |
226 | 3 | } |
227 | | |
228 | | void |
229 | | Locale::ClearRegion() |
230 | 6 | { |
231 | 6 | mRegion.Truncate(); |
232 | 6 | } |