/src/icu/icu4c/source/i18n/units_data.cpp
Line | Count | Source |
1 | | // © 2020 and later: Unicode, Inc. and others. |
2 | | // License & terms of use: http://www.unicode.org/copyright.html |
3 | | |
4 | | #include "unicode/utypes.h" |
5 | | |
6 | | #if !UCONFIG_NO_FORMATTING |
7 | | |
8 | | #include "bytesinkutil.h" |
9 | | #include "charstr.h" |
10 | | #include "cstring.h" |
11 | | #include "measunit_impl.h" |
12 | | #include "number_decimalquantity.h" |
13 | | #include "resource.h" |
14 | | #include "uassert.h" |
15 | | #include "ulocimp.h" |
16 | | #include "unicode/locid.h" |
17 | | #include "unicode/unistr.h" |
18 | | #include "unicode/ures.h" |
19 | | #include "units_data.h" |
20 | | #include "uresimp.h" |
21 | | #include "util.h" |
22 | | #include <utility> |
23 | | |
24 | | U_NAMESPACE_BEGIN |
25 | | namespace units { |
26 | | |
27 | | namespace { |
28 | | |
29 | | using icu::number::impl::DecimalQuantity; |
30 | | |
31 | 13.8k | void trimSpaces(CharString& factor, UErrorCode& status){ |
32 | 13.8k | CharString trimmed; |
33 | 138k | for (int i = 0 ; i < factor.length(); i++) { |
34 | 124k | if (factor[i] == ' ') continue; |
35 | | |
36 | 123k | trimmed.append(factor[i], status); |
37 | 123k | } |
38 | | |
39 | 13.8k | factor = std::move(trimmed); |
40 | 13.8k | } |
41 | | |
42 | | /** |
43 | | * A ResourceSink that collects conversion rate information. |
44 | | * |
45 | | * This class is for use by ures_getAllItemsWithFallback. |
46 | | */ |
47 | | class ConversionRateDataSink : public ResourceSink { |
48 | | public: |
49 | | /** |
50 | | * Constructor. |
51 | | * @param out The vector to which ConversionRateInfo instances are to be |
52 | | * added. This vector must outlive the use of the ResourceSink. |
53 | | */ |
54 | 90 | explicit ConversionRateDataSink(MaybeStackVector<ConversionRateInfo> *out) : outVector(out) {} |
55 | | |
56 | | /** |
57 | | * Method for use by `ures_getAllItemsWithFallback`. Adds the unit |
58 | | * conversion rates that are found in `value` to the output vector. |
59 | | * |
60 | | * @param source This string must be "convertUnits": the resource that this |
61 | | * class supports reading. |
62 | | * @param value The "convertUnits" resource, containing unit conversion rate |
63 | | * information. |
64 | | * @param noFallback Ignored. |
65 | | * @param status The standard ICU error code output parameter. |
66 | | */ |
67 | 90 | void put(const char *source, ResourceValue &value, UBool /*noFallback*/, UErrorCode &status) override { |
68 | 90 | if (U_FAILURE(status)) { return; } |
69 | 90 | if (uprv_strcmp(source, "convertUnits") != 0) { |
70 | | // This is very strict, however it is the cheapest way to be sure |
71 | | // that with `value`, we're looking at the convertUnits table. |
72 | 0 | status = U_ILLEGAL_ARGUMENT_ERROR; |
73 | 0 | return; |
74 | 0 | } |
75 | 90 | ResourceTable conversionRateTable = value.getTable(status); |
76 | 90 | const char *srcUnit; |
77 | | // We're reusing `value`, which seems to be a common pattern: |
78 | 14.0k | for (int32_t unit = 0; conversionRateTable.getKeyAndValue(unit, srcUnit, value); unit++) { |
79 | 13.9k | ResourceTable unitTable = value.getTable(status); |
80 | 13.9k | const char *key; |
81 | 13.9k | UnicodeString baseUnit = ICU_Utility::makeBogusString(); |
82 | 13.9k | UnicodeString factor = ICU_Utility::makeBogusString(); |
83 | 13.9k | UnicodeString offset = ICU_Utility::makeBogusString(); |
84 | 13.9k | UnicodeString special = ICU_Utility::makeBogusString(); |
85 | 13.9k | UnicodeString systems = ICU_Utility::makeBogusString(); |
86 | 55.9k | for (int32_t i = 0; unitTable.getKeyAndValue(i, key, value); i++) { |
87 | 42.0k | if (uprv_strcmp(key, "target") == 0) { |
88 | 13.9k | baseUnit = value.getUnicodeString(status); |
89 | 28.0k | } else if (uprv_strcmp(key, "factor") == 0) { |
90 | 13.8k | factor = value.getUnicodeString(status); |
91 | 14.2k | } else if (uprv_strcmp(key, "offset") == 0) { |
92 | 180 | offset = value.getUnicodeString(status); |
93 | 14.0k | } else if (uprv_strcmp(key, "special") == 0) { |
94 | 90 | special = value.getUnicodeString(status); // the name of a special mapping used instead of factor + optional offset. |
95 | 13.9k | } else if (uprv_strcmp(key, "systems") == 0) { |
96 | 13.9k | systems = value.getUnicodeString(status); |
97 | 13.9k | } |
98 | 42.0k | } |
99 | 13.9k | if (U_FAILURE(status)) { return; } |
100 | 13.9k | if (baseUnit.isBogus() || (factor.isBogus() && special.isBogus())) { |
101 | | // We could not find a usable conversion rate: bad resource. |
102 | 0 | status = U_MISSING_RESOURCE_ERROR; |
103 | 0 | return; |
104 | 0 | } |
105 | | |
106 | | // We don't have this ConversionRateInfo yet: add it. |
107 | 13.9k | ConversionRateInfo *cr = outVector->emplaceBack(); |
108 | 13.9k | if (!cr) { |
109 | 0 | status = U_MEMORY_ALLOCATION_ERROR; |
110 | 0 | return; |
111 | 13.9k | } else { |
112 | 13.9k | cr->sourceUnit = srcUnit; |
113 | 13.9k | if (cr->sourceUnit.isEmpty() != (*srcUnit == '\0')) { |
114 | 0 | status = U_MEMORY_ALLOCATION_ERROR; |
115 | 0 | } |
116 | 13.9k | copyInvariantChars(baseUnit, cr->baseUnit, status); |
117 | 13.9k | if (U_SUCCESS(status) && !factor.isBogus()) { |
118 | 13.8k | CharString tmp; |
119 | 13.8k | tmp.appendInvariantChars(factor, status); |
120 | 13.8k | trimSpaces(tmp, status); |
121 | 13.8k | if (U_SUCCESS(status)) { |
122 | 13.8k | cr->factor = tmp.toStringPiece(); |
123 | 13.8k | if (cr->factor.isEmpty() != tmp.isEmpty()) { |
124 | 0 | status = U_MEMORY_ALLOCATION_ERROR; |
125 | 0 | } |
126 | 13.8k | } |
127 | 13.8k | } |
128 | 13.9k | if (!offset.isBogus()) { copyInvariantChars(offset, cr->offset, status); } |
129 | 13.9k | if (!special.isBogus()) { copyInvariantChars(special, cr->specialMappingName, status); } |
130 | 13.9k | copyInvariantChars(systems, cr->systems, status); |
131 | 13.9k | } |
132 | 13.9k | } |
133 | 90 | } |
134 | | |
135 | | private: |
136 | | MaybeStackVector<ConversionRateInfo> *outVector; |
137 | | }; |
138 | | |
139 | 19.1k | bool operator<(const UnitPreferenceMetadata &a, const UnitPreferenceMetadata &b) { |
140 | 19.1k | return a.compareTo(b) < 0; |
141 | 19.1k | } |
142 | | |
143 | | /** |
144 | | * A ResourceSink that collects unit preferences information. |
145 | | * |
146 | | * This class is for use by ures_getAllItemsWithFallback. |
147 | | */ |
148 | | class UnitPreferencesSink : public ResourceSink { |
149 | | public: |
150 | | /** |
151 | | * Constructor. |
152 | | * @param outPrefs The vector to which UnitPreference instances are to be |
153 | | * added. This vector must outlive the use of the ResourceSink. |
154 | | * @param outMetadata The vector to which UnitPreferenceMetadata instances |
155 | | * are to be added. This vector must outlive the use of the ResourceSink. |
156 | | */ |
157 | | explicit UnitPreferencesSink(MaybeStackVector<UnitPreference> *outPrefs, |
158 | | MaybeStackVector<UnitPreferenceMetadata> *outMetadata) |
159 | 90 | : preferences(outPrefs), metadata(outMetadata) {} |
160 | | |
161 | | /** |
162 | | * Method for use by `ures_getAllItemsWithFallback`. Adds the unit |
163 | | * preferences info that are found in `value` to the output vector. |
164 | | * |
165 | | * @param source This string must be "unitPreferenceData": the resource that |
166 | | * this class supports reading. |
167 | | * @param value The "unitPreferenceData" resource, containing unit |
168 | | * preferences data. |
169 | | * @param noFallback Ignored. |
170 | | * @param status The standard ICU error code output parameter. Note: if an |
171 | | * error is returned, outPrefs and outMetadata may be inconsistent. |
172 | | */ |
173 | 90 | void put(const char *key, ResourceValue &value, UBool /*noFallback*/, UErrorCode &status) override { |
174 | 90 | if (U_FAILURE(status)) { return; } |
175 | 90 | if (uprv_strcmp(key, "unitPreferenceData") != 0) { |
176 | | // This is very strict, however it is the cheapest way to be sure |
177 | | // that with `value`, we're looking at the convertUnits table. |
178 | 0 | status = U_ILLEGAL_ARGUMENT_ERROR; |
179 | 0 | return; |
180 | 0 | } |
181 | | // The unitPreferenceData structure (see data/misc/units.txt) contains a |
182 | | // hierarchy of category/usage/region, within which are a set of |
183 | | // preferences. Hence three for-loops and another loop for the |
184 | | // preferences themselves: |
185 | 90 | ResourceTable unitPreferenceDataTable = value.getTable(status); |
186 | 90 | const char *category; |
187 | 1.35k | for (int32_t i = 0; unitPreferenceDataTable.getKeyAndValue(i, category, value); i++) { |
188 | 1.26k | ResourceTable categoryTable = value.getTable(status); |
189 | 1.26k | const char *usage; |
190 | 4.86k | for (int32_t j = 0; categoryTable.getKeyAndValue(j, usage, value); j++) { |
191 | 3.60k | ResourceTable regionTable = value.getTable(status); |
192 | 3.60k | const char *region; |
193 | 22.8k | for (int32_t k = 0; regionTable.getKeyAndValue(k, region, value); k++) { |
194 | | // `value` now contains the set of preferences for |
195 | | // category/usage/region. |
196 | 19.2k | ResourceArray unitPrefs = value.getArray(status); |
197 | 19.2k | if (U_FAILURE(status)) { return; } |
198 | 19.2k | int32_t prefLen = unitPrefs.getSize(); |
199 | | |
200 | | // Update metadata for this set of preferences. |
201 | 19.2k | UnitPreferenceMetadata *meta = metadata->emplaceBack( |
202 | 19.2k | category, usage, region, preferences->length(), prefLen, status); |
203 | 19.2k | if (!meta) { |
204 | 0 | status = U_MEMORY_ALLOCATION_ERROR; |
205 | 0 | return; |
206 | 0 | } |
207 | 19.2k | if (U_FAILURE(status)) { return; } |
208 | 19.2k | if (metadata->length() > 1) { |
209 | | // Verify that unit preferences are sorted and |
210 | | // without duplicates. |
211 | 19.1k | if (!(*(*metadata)[metadata->length() - 2] < |
212 | 19.1k | *(*metadata)[metadata->length() - 1])) { |
213 | 0 | status = U_INVALID_FORMAT_ERROR; |
214 | 0 | return; |
215 | 0 | } |
216 | 19.1k | } |
217 | | |
218 | | // Collect the individual preferences. |
219 | 45.0k | for (int32_t i = 0; unitPrefs.getValue(i, value); i++) { |
220 | 25.7k | UnitPreference *up = preferences->emplaceBack(); |
221 | 25.7k | if (!up) { |
222 | 0 | status = U_MEMORY_ALLOCATION_ERROR; |
223 | 0 | return; |
224 | 0 | } |
225 | 25.7k | ResourceTable unitPref = value.getTable(status); |
226 | 25.7k | if (U_FAILURE(status)) { return; } |
227 | 54.0k | for (int32_t i = 0; unitPref.getKeyAndValue(i, key, value); ++i) { |
228 | 28.3k | if (uprv_strcmp(key, "unit") == 0) { |
229 | 25.7k | copyInvariantChars(value.getUnicodeString(status), up->unit, status); |
230 | 25.7k | } else if (uprv_strcmp(key, "geq") == 0) { |
231 | 1.53k | int32_t length; |
232 | 1.53k | const char16_t *g = value.getString(length, status); |
233 | 1.53k | CharString geq; |
234 | 1.53k | geq.appendInvariantChars(g, length, status); |
235 | 1.53k | DecimalQuantity dq; |
236 | 1.53k | dq.setToDecNumber(geq.data(), status); |
237 | 1.53k | up->geq = dq.toDouble(); |
238 | 1.53k | } else if (uprv_strcmp(key, "skeleton") == 0) { |
239 | 1.08k | up->skeleton = value.getUnicodeString(status); |
240 | 1.08k | } |
241 | 28.3k | } |
242 | 25.7k | } |
243 | 19.2k | } |
244 | 3.60k | } |
245 | 1.26k | } |
246 | 90 | } |
247 | | |
248 | | private: |
249 | | MaybeStackVector<UnitPreference> *preferences; |
250 | | MaybeStackVector<UnitPreferenceMetadata> *metadata; |
251 | | }; |
252 | | |
253 | | int32_t binarySearch(const MaybeStackVector<UnitPreferenceMetadata> *metadata, |
254 | | const UnitPreferenceMetadata &desired, bool *foundCategory, bool *foundUsage, |
255 | 90 | bool *foundRegion, UErrorCode &status) { |
256 | 90 | if (U_FAILURE(status)) { return -1; } |
257 | 90 | int32_t start = 0; |
258 | 90 | int32_t end = metadata->length(); |
259 | 90 | *foundCategory = false; |
260 | 90 | *foundUsage = false; |
261 | 90 | *foundRegion = false; |
262 | 720 | while (start < end) { |
263 | 630 | int32_t mid = (start + end) / 2; |
264 | 630 | int32_t cmp = (*metadata)[mid]->compareTo(desired, foundCategory, foundUsage, foundRegion); |
265 | 630 | if (cmp < 0) { |
266 | 450 | start = mid + 1; |
267 | 450 | } else if (cmp > 0) { |
268 | 180 | end = mid; |
269 | 180 | } else { |
270 | 0 | return mid; |
271 | 0 | } |
272 | 630 | } |
273 | 90 | return -1; |
274 | 90 | } |
275 | | |
276 | | /** |
277 | | * Finds the UnitPreferenceMetadata instance that matches the given category, |
278 | | * usage and region: if missing, region falls back to "001", and usage |
279 | | * repeatedly drops tailing components, eventually trying "default" |
280 | | * ("land-agriculture-grain" -> "land-agriculture" -> "land" -> "default"). |
281 | | * |
282 | | * @param metadata The full list of UnitPreferenceMetadata instances. |
283 | | * @param category The category to search for. See getUnitCategory(). |
284 | | * @param usage The usage for which formatting preferences is needed. If the |
285 | | * given usage is not known, automatic fallback occurs, see function description |
286 | | * above. |
287 | | * @param region The region for which preferences are needed. If there are no |
288 | | * region-specific preferences, this function automatically falls back to the |
289 | | * "001" region (global). |
290 | | * @param status The standard ICU error code output parameter. |
291 | | * * If an invalid category is given, status will be U_ILLEGAL_ARGUMENT_ERROR. |
292 | | * * If fallback to "default" or "001" didn't resolve, status will be |
293 | | * U_MISSING_RESOURCE. |
294 | | * @return The index into the metadata vector which represents the appropriate |
295 | | * preferences. If appropriate preferences are not found, -1 is returned. |
296 | | */ |
297 | | int32_t getPreferenceMetadataIndex(const MaybeStackVector<UnitPreferenceMetadata> *metadata, |
298 | | StringPiece category, StringPiece usage, StringPiece region, |
299 | 90 | UErrorCode &status) { |
300 | 90 | if (U_FAILURE(status)) { return -1; } |
301 | 90 | bool foundCategory, foundUsage, foundRegion; |
302 | 90 | UnitPreferenceMetadata desired(category, usage, region, -1, -1, status); |
303 | 90 | int32_t idx = binarySearch(metadata, desired, &foundCategory, &foundUsage, &foundRegion, status); |
304 | 90 | if (U_FAILURE(status)) { return -1; } |
305 | 90 | if (idx >= 0) { return idx; } |
306 | 90 | if (!foundCategory) { |
307 | | // TODO: failures can happen if units::getUnitCategory returns a category |
308 | | // that does not appear in unitPreferenceData. Do we want a unit test that |
309 | | // checks unitPreferenceData has full coverage of categories? Or just trust |
310 | | // CLDR? |
311 | 90 | status = U_ILLEGAL_ARGUMENT_ERROR; |
312 | 90 | return -1; |
313 | 90 | } |
314 | 0 | U_ASSERT(foundCategory); |
315 | 0 | while (!foundUsage) { |
316 | 0 | int32_t lastDashIdx = desired.usage.lastIndexOf('-'); |
317 | 0 | if (lastDashIdx > 0) { |
318 | 0 | desired.usage.truncate(lastDashIdx); |
319 | 0 | } else if (uprv_strcmp(desired.usage.data(), "default") != 0) { |
320 | 0 | desired.usage.truncate(0).append("default", status); |
321 | 0 | } else { |
322 | | // "default" is not supposed to be missing for any valid category. |
323 | 0 | status = U_MISSING_RESOURCE_ERROR; |
324 | 0 | return -1; |
325 | 0 | } |
326 | 0 | idx = binarySearch(metadata, desired, &foundCategory, &foundUsage, &foundRegion, status); |
327 | 0 | if (U_FAILURE(status)) { return -1; } |
328 | 0 | } |
329 | 0 | U_ASSERT(foundCategory); |
330 | 0 | U_ASSERT(foundUsage); |
331 | 0 | if (!foundRegion) { |
332 | 0 | if (uprv_strcmp(desired.region.data(), "001") != 0) { |
333 | 0 | desired.region.truncate(0).append("001", status); |
334 | 0 | idx = binarySearch(metadata, desired, &foundCategory, &foundUsage, &foundRegion, status); |
335 | 0 | } |
336 | 0 | if (!foundRegion) { |
337 | | // "001" is not supposed to be missing for any valid usage. |
338 | 0 | status = U_MISSING_RESOURCE_ERROR; |
339 | 0 | return -1; |
340 | 0 | } |
341 | 0 | } |
342 | 0 | U_ASSERT(foundCategory); |
343 | 0 | U_ASSERT(foundUsage); |
344 | 0 | U_ASSERT(foundRegion); |
345 | 0 | U_ASSERT(idx >= 0); |
346 | 0 | return idx; |
347 | 0 | } |
348 | | |
349 | | } // namespace |
350 | | |
351 | | UnitPreferenceMetadata::UnitPreferenceMetadata(StringPiece category, StringPiece usage, |
352 | | StringPiece region, int32_t prefsOffset, |
353 | 19.3k | int32_t prefsCount, UErrorCode &status) { |
354 | 19.3k | this->category.append(category, status); |
355 | 19.3k | this->usage.append(usage, status); |
356 | 19.3k | this->region.append(region, status); |
357 | 19.3k | this->prefsOffset = prefsOffset; |
358 | 19.3k | this->prefsCount = prefsCount; |
359 | 19.3k | } |
360 | | |
361 | 19.1k | int32_t UnitPreferenceMetadata::compareTo(const UnitPreferenceMetadata &other) const { |
362 | 19.1k | int32_t cmp = uprv_strcmp(category.data(), other.category.data()); |
363 | 19.1k | if (cmp == 0) { |
364 | 18.0k | cmp = uprv_strcmp(usage.data(), other.usage.data()); |
365 | 18.0k | } |
366 | 19.1k | if (cmp == 0) { |
367 | 15.6k | cmp = uprv_strcmp(region.data(), other.region.data()); |
368 | 15.6k | } |
369 | 19.1k | return cmp; |
370 | 19.1k | } |
371 | | |
372 | | int32_t UnitPreferenceMetadata::compareTo(const UnitPreferenceMetadata &other, bool *foundCategory, |
373 | 630 | bool *foundUsage, bool *foundRegion) const { |
374 | 630 | int32_t cmp = uprv_strcmp(category.data(), other.category.data()); |
375 | 630 | if (cmp == 0) { |
376 | 0 | *foundCategory = true; |
377 | 0 | cmp = uprv_strcmp(usage.data(), other.usage.data()); |
378 | 0 | } |
379 | 630 | if (cmp == 0) { |
380 | 0 | *foundUsage = true; |
381 | 0 | cmp = uprv_strcmp(region.data(), other.region.data()); |
382 | 0 | } |
383 | 630 | if (cmp == 0) { |
384 | 0 | *foundRegion = true; |
385 | 0 | } |
386 | 630 | return cmp; |
387 | 630 | } |
388 | | |
389 | | // TODO: this may be unnecessary. Fold into ConversionRates class? Or move to anonymous namespace? |
390 | 90 | void U_I18N_API getAllConversionRates(MaybeStackVector<ConversionRateInfo> &result, UErrorCode &status) { |
391 | 90 | LocalUResourceBundlePointer unitsBundle(ures_openDirect(nullptr, "units", &status)); |
392 | 90 | ConversionRateDataSink sink(&result); |
393 | 90 | ures_getAllItemsWithFallback(unitsBundle.getAlias(), "convertUnits", sink, status); |
394 | 90 | } |
395 | | |
396 | | const ConversionRateInfo *ConversionRates::extractConversionInfo(StringPiece source, |
397 | 90 | UErrorCode &status) const { |
398 | 9.54k | for (size_t i = 0, n = conversionInfo_.length(); i < n; ++i) { |
399 | 9.54k | if (uprv_strncmp(conversionInfo_[i]->sourceUnit.data(), source.data(), source.size()) == 0) { |
400 | 90 | return conversionInfo_[i]; |
401 | 90 | } |
402 | 9.54k | } |
403 | | |
404 | 0 | status = U_INTERNAL_PROGRAM_ERROR; |
405 | 0 | return nullptr; |
406 | 90 | } |
407 | | |
408 | 90 | UnitPreferences::UnitPreferences(UErrorCode& status) { |
409 | 90 | LocalUResourceBundlePointer unitsBundle(ures_openDirect(nullptr, "units", &status)); |
410 | 90 | UnitPreferencesSink sink(&unitPrefs_, &metadata_); |
411 | 90 | ures_getAllItemsWithFallback(unitsBundle.getAlias(), "unitPreferenceData", sink, status); |
412 | 90 | } |
413 | | |
414 | 90 | CharString getKeyWordValue(const Locale &locale, StringPiece kw, UErrorCode &status) { |
415 | 90 | if (U_FAILURE(status)) { return {}; } |
416 | 90 | auto result = locale.getKeywordValue<CharString>(kw, status); |
417 | 90 | if (U_SUCCESS(status) && result.isEmpty()) { |
418 | 90 | status = U_MISSING_RESOURCE_ERROR; |
419 | 90 | } |
420 | 90 | return result; |
421 | 90 | } |
422 | | |
423 | | MaybeStackVector<UnitPreference> UnitPreferences::getPreferencesFor(StringPiece category, |
424 | | StringPiece usage, |
425 | | const Locale& locale, |
426 | 90 | UErrorCode& status) const { |
427 | 90 | MaybeStackVector<UnitPreference> result; |
428 | | |
429 | | // TODO: remove this once all the categories are allowed. |
430 | | // WARNING: when this is removed please make sure to keep the "fahrenhe" => "fahrenheit" mapping |
431 | 90 | UErrorCode internalMuStatus = U_ZERO_ERROR; |
432 | 90 | if (category.compare("temperature") == 0) { |
433 | 0 | CharString localeUnitCharString = getKeyWordValue(locale, "mu", internalMuStatus); |
434 | 0 | if (U_SUCCESS(internalMuStatus)) { |
435 | | // The value for -u-mu- is `fahrenhe`, but CLDR and everything else uses `fahrenheit` |
436 | 0 | if (localeUnitCharString == "fahrenhe") { |
437 | 0 | localeUnitCharString = CharString("fahrenheit", status); |
438 | 0 | } |
439 | | // TODO: use the unit category as Java especially when all the categories are allowed.. |
440 | 0 | if (localeUnitCharString == "celsius" |
441 | 0 | || localeUnitCharString == "fahrenheit" |
442 | 0 | || localeUnitCharString == "kelvin" |
443 | 0 | ) { |
444 | 0 | UnitPreference unitPref; |
445 | 0 | unitPref.unit = localeUnitCharString.toStringPiece(); |
446 | 0 | if (unitPref.unit.isEmpty() != localeUnitCharString.isEmpty()) { |
447 | 0 | status = U_MISSING_RESOURCE_ERROR; |
448 | 0 | return result; |
449 | 0 | } |
450 | 0 | result.emplaceBackAndCheckErrorCode(status, unitPref); |
451 | 0 | return result; |
452 | 0 | } |
453 | 0 | } |
454 | 0 | } |
455 | | |
456 | 90 | CharString region = ulocimp_getRegionForSupplementalData(locale.getName(), true, status); |
457 | | |
458 | | // Check the locale system tag, e.g `ms=metric`. |
459 | 90 | UErrorCode internalMeasureTagStatus = U_ZERO_ERROR; |
460 | 90 | CharString localeSystem = getKeyWordValue(locale, "measure", internalMeasureTagStatus); |
461 | 90 | bool isLocaleSystem = false; |
462 | 90 | if (U_SUCCESS(internalMeasureTagStatus) && (localeSystem == "metric" || localeSystem == "ussystem" || localeSystem == "uksystem")) { |
463 | 0 | isLocaleSystem = true; |
464 | 0 | } |
465 | | |
466 | 90 | int32_t idx = |
467 | 90 | getPreferenceMetadataIndex(&metadata_, category, usage, region.toStringPiece(), status); |
468 | 90 | if (U_FAILURE(status)) { |
469 | 90 | return result; |
470 | 90 | } |
471 | | |
472 | 0 | U_ASSERT(idx >= 0); // Failures should have been taken care of by `status`. |
473 | 0 | const UnitPreferenceMetadata *m = metadata_[idx]; |
474 | | |
475 | 0 | if (isLocaleSystem) { |
476 | | // if the locale ID specifies a measurment system, check if ALL of the units we got back |
477 | | // are members of that system (or are "metric_adjacent", which we consider to match all |
478 | | // the systems) |
479 | 0 | bool unitsMatchSystem = true; |
480 | 0 | ConversionRates rates(status); |
481 | 0 | for (int32_t i = 0; unitsMatchSystem && i < m->prefsCount; i++) { |
482 | 0 | const UnitPreference& unitPref = *(unitPrefs_[i + m->prefsOffset]); |
483 | 0 | MeasureUnitImpl measureUnit = MeasureUnitImpl::forIdentifier(unitPref.unit.data(), status); |
484 | 0 | for (int32_t j = 0; unitsMatchSystem && j < measureUnit.singleUnits.length(); j++) { |
485 | 0 | const SingleUnitImpl* singleUnit = measureUnit.singleUnits[j]; |
486 | 0 | const ConversionRateInfo* rateInfo = rates.extractConversionInfo(singleUnit->getSimpleUnitID(), status); |
487 | 0 | const char* systems = rateInfo->systems.data(); |
488 | | // "metric-adjacent" is considered to match all the locale systems |
489 | 0 | if (uprv_strstr(systems, "metric_adjacent") == nullptr) { |
490 | 0 | if (uprv_strstr(systems, localeSystem.data()) == nullptr) { |
491 | 0 | unitsMatchSystem = false; |
492 | 0 | } |
493 | 0 | } |
494 | 0 | } |
495 | 0 | } |
496 | | |
497 | | // if any of the units we got back above don't match the mearurement system the locale ID asked for, |
498 | | // throw out the region and just load the units for the base region for the requested measurement system |
499 | 0 | if (!unitsMatchSystem) { |
500 | 0 | region.clear(); |
501 | 0 | if (localeSystem == "ussystem") { |
502 | 0 | region.append("US", status); |
503 | 0 | } else if (localeSystem == "uksystem") { |
504 | 0 | region.append("GB", status); |
505 | 0 | } else { |
506 | 0 | region.append("001", status); |
507 | 0 | } |
508 | 0 | idx = getPreferenceMetadataIndex(&metadata_, category, usage, region.toStringPiece(), status); |
509 | 0 | if (U_FAILURE(status)) { |
510 | 0 | return result; |
511 | 0 | } |
512 | | |
513 | 0 | m = metadata_[idx]; |
514 | 0 | } |
515 | 0 | } |
516 | | |
517 | 0 | for (int32_t i = 0; i < m->prefsCount; i++) { |
518 | 0 | result.emplaceBackAndCheckErrorCode(status, *(unitPrefs_[i + m->prefsOffset])); |
519 | 0 | } |
520 | 0 | return result; |
521 | 0 | } |
522 | | |
523 | | } // namespace units |
524 | | U_NAMESPACE_END |
525 | | |
526 | | #endif /* #if !UCONFIG_NO_FORMATTING */ |