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