1 // © 2017 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 "unicode/simpleformatter.h"
9 #include "unicode/ures.h"
10 #include "ureslocs.h"
11 #include "charstr.h"
12 #include "uresimp.h"
13 #include "measunit_impl.h"
14 #include "number_longnames.h"
15 #include "number_microprops.h"
16 #include <algorithm>
17 #include "cstring.h"
18 #include "util.h"
19
20 using namespace icu;
21 using namespace icu::number;
22 using namespace icu::number::impl;
23
24 namespace {
25
26 /**
27 * Display Name (this format has no placeholder).
28 *
29 * Used as an index into the LongNameHandler::simpleFormats array. Units
30 * resources cover the normal set of PluralRules keys, as well as `dnam` and
31 * `per` forms.
32 */
33 constexpr int32_t DNAM_INDEX = StandardPlural::Form::COUNT;
34 /**
35 * "per" form (e.g. "{0} per day" is day's "per" form).
36 *
37 * Used as an index into the LongNameHandler::simpleFormats array. Units
38 * resources cover the normal set of PluralRules keys, as well as `dnam` and
39 * `per` forms.
40 */
41 constexpr int32_t PER_INDEX = StandardPlural::Form::COUNT + 1;
42 // Number of keys in the array populated by PluralTableSink.
43 constexpr int32_t ARRAY_LENGTH = StandardPlural::Form::COUNT + 2;
44
getIndex(const char * pluralKeyword,UErrorCode & status)45 static int32_t getIndex(const char* pluralKeyword, UErrorCode& status) {
46 // pluralKeyword can also be "dnam" or "per"
47 if (uprv_strcmp(pluralKeyword, "dnam") == 0) {
48 return DNAM_INDEX;
49 } else if (uprv_strcmp(pluralKeyword, "per") == 0) {
50 return PER_INDEX;
51 } else {
52 StandardPlural::Form plural = StandardPlural::fromString(pluralKeyword, status);
53 return plural;
54 }
55 }
56
57 // Selects a string out of the `strings` array which corresponds to the
58 // specified plural form, with fallback to the OTHER form.
59 //
60 // The `strings` array must have ARRAY_LENGTH items: one corresponding to each
61 // of the plural forms, plus a display name ("dnam") and a "per" form.
getWithPlural(const UnicodeString * strings,StandardPlural::Form plural,UErrorCode & status)62 static UnicodeString getWithPlural(
63 const UnicodeString* strings,
64 StandardPlural::Form plural,
65 UErrorCode& status) {
66 UnicodeString result = strings[plural];
67 if (result.isBogus()) {
68 result = strings[StandardPlural::Form::OTHER];
69 }
70 if (result.isBogus()) {
71 // There should always be data in the "other" plural variant.
72 status = U_INTERNAL_PROGRAM_ERROR;
73 }
74 return result;
75 }
76
77
78 //////////////////////////
79 /// BEGIN DATA LOADING ///
80 //////////////////////////
81
82 class PluralTableSink : public ResourceSink {
83 public:
PluralTableSink(UnicodeString * outArray)84 explicit PluralTableSink(UnicodeString *outArray) : outArray(outArray) {
85 // Initialize the array to bogus strings.
86 for (int32_t i = 0; i < ARRAY_LENGTH; i++) {
87 outArray[i].setToBogus();
88 }
89 }
90
put(const char * key,ResourceValue & value,UBool,UErrorCode & status)91 void put(const char *key, ResourceValue &value, UBool /*noFallback*/, UErrorCode &status) U_OVERRIDE {
92 ResourceTable pluralsTable = value.getTable(status);
93 if (U_FAILURE(status)) { return; }
94 for (int32_t i = 0; pluralsTable.getKeyAndValue(i, key, value); ++i) {
95 int32_t index = getIndex(key, status);
96 if (U_FAILURE(status)) { return; }
97 if (!outArray[index].isBogus()) {
98 continue;
99 }
100 outArray[index] = value.getUnicodeString(status);
101 if (U_FAILURE(status)) { return; }
102 }
103 }
104
105 private:
106 UnicodeString *outArray;
107 };
108
109 // NOTE: outArray MUST have room for all StandardPlural values. No bounds checking is performed.
110
111 /**
112 * Populates outArray with `locale`-specific values for `unit` through use of
113 * PluralTableSink. Only the set of basic units are supported!
114 *
115 * Reading from resources *unitsNarrow* and *unitsShort* (for width
116 * UNUM_UNIT_WIDTH_NARROW), or just *unitsShort* (for width
117 * UNUM_UNIT_WIDTH_SHORT). For other widths, it reads just "units".
118 *
119 * @param unit must have a type and subtype (i.e. it must be a unit listed in
120 * gTypes and gSubTypes in measunit.cpp).
121 * @param outArray must be of fixed length ARRAY_LENGTH.
122 */
getMeasureData(const Locale & locale,const MeasureUnit & unit,const UNumberUnitWidth & width,UnicodeString * outArray,UErrorCode & status)123 void getMeasureData(const Locale &locale, const MeasureUnit &unit, const UNumberUnitWidth &width,
124 UnicodeString *outArray, UErrorCode &status) {
125 PluralTableSink sink(outArray);
126 LocalUResourceBundlePointer unitsBundle(ures_open(U_ICUDATA_UNIT, locale.getName(), &status));
127 if (U_FAILURE(status)) { return; }
128
129 // Map duration-year-person, duration-week-person, etc. to duration-year, duration-week, ...
130 // TODO(ICU-20400): Get duration-*-person data properly with aliases.
131 StringPiece subtypeForResource;
132 int32_t subtypeLen = static_cast<int32_t>(uprv_strlen(unit.getSubtype()));
133 if (subtypeLen > 7 && uprv_strcmp(unit.getSubtype() + subtypeLen - 7, "-person") == 0) {
134 subtypeForResource = {unit.getSubtype(), subtypeLen - 7};
135 } else {
136 subtypeForResource = unit.getSubtype();
137 }
138
139 CharString key;
140 key.append("units", status);
141 if (width == UNUM_UNIT_WIDTH_NARROW) {
142 key.append("Narrow", status);
143 } else if (width == UNUM_UNIT_WIDTH_SHORT) {
144 key.append("Short", status);
145 }
146 key.append("/", status);
147 key.append(unit.getType(), status);
148 key.append("/", status);
149 key.append(subtypeForResource, status);
150
151 UErrorCode localStatus = U_ZERO_ERROR;
152 ures_getAllItemsWithFallback(unitsBundle.getAlias(), key.data(), sink, localStatus);
153 if (width == UNUM_UNIT_WIDTH_SHORT) {
154 if (U_FAILURE(localStatus)) {
155 status = localStatus;
156 }
157 return;
158 }
159
160 // TODO(ICU-13353): The fallback to short does not work in ICU4C.
161 // Manually fall back to short (this is done automatically in Java).
162 key.clear();
163 key.append("unitsShort/", status);
164 key.append(unit.getType(), status);
165 key.append("/", status);
166 key.append(subtypeForResource, status);
167 ures_getAllItemsWithFallback(unitsBundle.getAlias(), key.data(), sink, status);
168 }
169
getCurrencyLongNameData(const Locale & locale,const CurrencyUnit & currency,UnicodeString * outArray,UErrorCode & status)170 void getCurrencyLongNameData(const Locale &locale, const CurrencyUnit ¤cy, UnicodeString *outArray,
171 UErrorCode &status) {
172 // In ICU4J, this method gets a CurrencyData from CurrencyData.provider.
173 // TODO(ICU4J): Implement this without going through CurrencyData, like in ICU4C?
174 PluralTableSink sink(outArray);
175 LocalUResourceBundlePointer unitsBundle(ures_open(U_ICUDATA_CURR, locale.getName(), &status));
176 if (U_FAILURE(status)) { return; }
177 ures_getAllItemsWithFallback(unitsBundle.getAlias(), "CurrencyUnitPatterns", sink, status);
178 if (U_FAILURE(status)) { return; }
179 for (int32_t i = 0; i < StandardPlural::Form::COUNT; i++) {
180 UnicodeString &pattern = outArray[i];
181 if (pattern.isBogus()) {
182 continue;
183 }
184 int32_t longNameLen = 0;
185 const char16_t *longName = ucurr_getPluralName(
186 currency.getISOCurrency(),
187 locale.getName(),
188 nullptr /* isChoiceFormat */,
189 StandardPlural::getKeyword(static_cast<StandardPlural::Form>(i)),
190 &longNameLen,
191 &status);
192 // Example pattern from data: "{0} {1}"
193 // Example output after find-and-replace: "{0} US dollars"
194 pattern.findAndReplace(UnicodeString(u"{1}"), UnicodeString(longName, longNameLen));
195 }
196 }
197
getPerUnitFormat(const Locale & locale,const UNumberUnitWidth & width,UErrorCode & status)198 UnicodeString getPerUnitFormat(const Locale& locale, const UNumberUnitWidth &width, UErrorCode& status) {
199 LocalUResourceBundlePointer unitsBundle(ures_open(U_ICUDATA_UNIT, locale.getName(), &status));
200 if (U_FAILURE(status)) { return {}; }
201 CharString key;
202 key.append("units", status);
203 if (width == UNUM_UNIT_WIDTH_NARROW) {
204 key.append("Narrow", status);
205 } else if (width == UNUM_UNIT_WIDTH_SHORT) {
206 key.append("Short", status);
207 }
208 key.append("/compound/per", status);
209 int32_t len = 0;
210 const UChar* ptr = ures_getStringByKeyWithFallback(unitsBundle.getAlias(), key.data(), &len, &status);
211 return UnicodeString(ptr, len);
212 }
213
214 ////////////////////////
215 /// END DATA LOADING ///
216 ////////////////////////
217
218 } // namespace
219
forMeasureUnit(const Locale & loc,const MeasureUnit & unitRef,const MeasureUnit & perUnit,const UNumberUnitWidth & width,const PluralRules * rules,const MicroPropsGenerator * parent,LongNameHandler * fillIn,UErrorCode & status)220 void LongNameHandler::forMeasureUnit(const Locale &loc, const MeasureUnit &unitRef,
221 const MeasureUnit &perUnit, const UNumberUnitWidth &width,
222 const PluralRules *rules, const MicroPropsGenerator *parent,
223 LongNameHandler *fillIn, UErrorCode &status) {
224 // Not valid for mixed units that aren't built-in units, and there should
225 // not be any built-in mixed units!
226 U_ASSERT(uprv_strcmp(unitRef.getType(), "") != 0 ||
227 unitRef.getComplexity(status) != UMEASURE_UNIT_MIXED);
228 U_ASSERT(fillIn != nullptr);
229
230 MeasureUnit unit = unitRef;
231 if (uprv_strcmp(perUnit.getType(), "none") != 0) {
232 // Compound unit: first try to simplify (e.g., meters per second is its own unit).
233 MeasureUnit simplified = unit.product(perUnit.reciprocal(status), status);
234 if (uprv_strcmp(simplified.getType(), "") != 0) {
235 unit = simplified;
236 } else {
237 // No simplified form is available.
238 forCompoundUnit(loc, unit, perUnit, width, rules, parent, fillIn, status);
239 return;
240 }
241 }
242
243 if (uprv_strcmp(unit.getType(), "") == 0) {
244 // TODO(ICU-20941): Unsanctioned unit. Not yet fully supported. Set an
245 // error code. Once we support not-built-in units here, unitRef may be
246 // anything, but if not built-in, perUnit has to be "none".
247 status = U_UNSUPPORTED_ERROR;
248 return;
249 }
250
251 UnicodeString simpleFormats[ARRAY_LENGTH];
252 getMeasureData(loc, unit, width, simpleFormats, status);
253 if (U_FAILURE(status)) {
254 return;
255 }
256 fillIn->rules = rules;
257 fillIn->parent = parent;
258 fillIn->simpleFormatsToModifiers(simpleFormats, {UFIELD_CATEGORY_NUMBER, UNUM_MEASURE_UNIT_FIELD},
259 status);
260 }
261
forCompoundUnit(const Locale & loc,const MeasureUnit & unit,const MeasureUnit & perUnit,const UNumberUnitWidth & width,const PluralRules * rules,const MicroPropsGenerator * parent,LongNameHandler * fillIn,UErrorCode & status)262 void LongNameHandler::forCompoundUnit(const Locale &loc, const MeasureUnit &unit,
263 const MeasureUnit &perUnit, const UNumberUnitWidth &width,
264 const PluralRules *rules, const MicroPropsGenerator *parent,
265 LongNameHandler *fillIn, UErrorCode &status) {
266 if (uprv_strcmp(unit.getType(), "") == 0 || uprv_strcmp(perUnit.getType(), "") == 0) {
267 // TODO(ICU-20941): Unsanctioned unit. Not yet fully supported. Set an
268 // error code. Once we support not-built-in units here, unitRef may be
269 // anything, but if not built-in, perUnit has to be "none".
270 status = U_UNSUPPORTED_ERROR;
271 return;
272 }
273 if (fillIn == nullptr) {
274 status = U_INTERNAL_PROGRAM_ERROR;
275 return;
276 }
277 UnicodeString primaryData[ARRAY_LENGTH];
278 getMeasureData(loc, unit, width, primaryData, status);
279 if (U_FAILURE(status)) {
280 return;
281 }
282 UnicodeString secondaryData[ARRAY_LENGTH];
283 getMeasureData(loc, perUnit, width, secondaryData, status);
284 if (U_FAILURE(status)) {
285 return;
286 }
287
288 UnicodeString perUnitFormat;
289 if (!secondaryData[PER_INDEX].isBogus()) {
290 perUnitFormat = secondaryData[PER_INDEX];
291 } else {
292 UnicodeString rawPerUnitFormat = getPerUnitFormat(loc, width, status);
293 if (U_FAILURE(status)) {
294 return;
295 }
296 // rawPerUnitFormat is something like "{0}/{1}"; we need to substitute in the secondary unit.
297 SimpleFormatter compiled(rawPerUnitFormat, 2, 2, status);
298 if (U_FAILURE(status)) {
299 return;
300 }
301 UnicodeString secondaryFormat = getWithPlural(secondaryData, StandardPlural::Form::ONE, status);
302 if (U_FAILURE(status)) {
303 return;
304 }
305 // Some "one" pattern may not contain "{0}". For example in "ar" or "ne" locale.
306 SimpleFormatter secondaryCompiled(secondaryFormat, 0, 1, status);
307 if (U_FAILURE(status)) {
308 return;
309 }
310 UnicodeString secondaryString = secondaryCompiled.getTextWithNoArguments().trim();
311 // TODO: Why does UnicodeString need to be explicit in the following line?
312 compiled.format(UnicodeString(u"{0}"), secondaryString, perUnitFormat, status);
313 if (U_FAILURE(status)) {
314 return;
315 }
316 }
317 fillIn->rules = rules;
318 fillIn->parent = parent;
319 fillIn->multiSimpleFormatsToModifiers(primaryData, perUnitFormat,
320 {UFIELD_CATEGORY_NUMBER, UNUM_MEASURE_UNIT_FIELD}, status);
321 }
322
getUnitDisplayName(const Locale & loc,const MeasureUnit & unit,UNumberUnitWidth width,UErrorCode & status)323 UnicodeString LongNameHandler::getUnitDisplayName(
324 const Locale& loc,
325 const MeasureUnit& unit,
326 UNumberUnitWidth width,
327 UErrorCode& status) {
328 if (U_FAILURE(status)) {
329 return ICU_Utility::makeBogusString();
330 }
331 UnicodeString simpleFormats[ARRAY_LENGTH];
332 getMeasureData(loc, unit, width, simpleFormats, status);
333 return simpleFormats[DNAM_INDEX];
334 }
335
getUnitPattern(const Locale & loc,const MeasureUnit & unit,UNumberUnitWidth width,StandardPlural::Form pluralForm,UErrorCode & status)336 UnicodeString LongNameHandler::getUnitPattern(
337 const Locale& loc,
338 const MeasureUnit& unit,
339 UNumberUnitWidth width,
340 StandardPlural::Form pluralForm,
341 UErrorCode& status) {
342 if (U_FAILURE(status)) {
343 return ICU_Utility::makeBogusString();
344 }
345 UnicodeString simpleFormats[ARRAY_LENGTH];
346 getMeasureData(loc, unit, width, simpleFormats, status);
347 // The above already handles fallback from other widths to short
348 if (U_FAILURE(status)) {
349 return ICU_Utility::makeBogusString();
350 }
351 // Now handle fallback from other plural forms to OTHER
352 return (!(simpleFormats[pluralForm]).isBogus())? simpleFormats[pluralForm]:
353 simpleFormats[StandardPlural::Form::OTHER];
354 }
355
forCurrencyLongNames(const Locale & loc,const CurrencyUnit & currency,const PluralRules * rules,const MicroPropsGenerator * parent,UErrorCode & status)356 LongNameHandler* LongNameHandler::forCurrencyLongNames(const Locale &loc, const CurrencyUnit ¤cy,
357 const PluralRules *rules,
358 const MicroPropsGenerator *parent,
359 UErrorCode &status) {
360 auto* result = new LongNameHandler(rules, parent);
361 if (result == nullptr) {
362 status = U_MEMORY_ALLOCATION_ERROR;
363 return nullptr;
364 }
365 UnicodeString simpleFormats[ARRAY_LENGTH];
366 getCurrencyLongNameData(loc, currency, simpleFormats, status);
367 if (U_FAILURE(status)) { return nullptr; }
368 result->simpleFormatsToModifiers(simpleFormats, {UFIELD_CATEGORY_NUMBER, UNUM_CURRENCY_FIELD}, status);
369 return result;
370 }
371
simpleFormatsToModifiers(const UnicodeString * simpleFormats,Field field,UErrorCode & status)372 void LongNameHandler::simpleFormatsToModifiers(const UnicodeString *simpleFormats, Field field,
373 UErrorCode &status) {
374 for (int32_t i = 0; i < StandardPlural::Form::COUNT; i++) {
375 StandardPlural::Form plural = static_cast<StandardPlural::Form>(i);
376 UnicodeString simpleFormat = getWithPlural(simpleFormats, plural, status);
377 if (U_FAILURE(status)) { return; }
378 SimpleFormatter compiledFormatter(simpleFormat, 0, 1, status);
379 if (U_FAILURE(status)) { return; }
380 fModifiers[i] = SimpleModifier(compiledFormatter, field, false, {this, SIGNUM_POS_ZERO, plural});
381 }
382 }
383
multiSimpleFormatsToModifiers(const UnicodeString * leadFormats,UnicodeString trailFormat,Field field,UErrorCode & status)384 void LongNameHandler::multiSimpleFormatsToModifiers(const UnicodeString *leadFormats, UnicodeString trailFormat,
385 Field field, UErrorCode &status) {
386 SimpleFormatter trailCompiled(trailFormat, 1, 1, status);
387 if (U_FAILURE(status)) { return; }
388 for (int32_t i = 0; i < StandardPlural::Form::COUNT; i++) {
389 StandardPlural::Form plural = static_cast<StandardPlural::Form>(i);
390 UnicodeString leadFormat = getWithPlural(leadFormats, plural, status);
391 if (U_FAILURE(status)) { return; }
392 UnicodeString compoundFormat;
393 trailCompiled.format(leadFormat, compoundFormat, status);
394 if (U_FAILURE(status)) { return; }
395 SimpleFormatter compoundCompiled(compoundFormat, 0, 1, status);
396 if (U_FAILURE(status)) { return; }
397 fModifiers[i] = SimpleModifier(compoundCompiled, field, false, {this, SIGNUM_POS_ZERO, plural});
398 }
399 }
400
processQuantity(DecimalQuantity & quantity,MicroProps & micros,UErrorCode & status) const401 void LongNameHandler::processQuantity(DecimalQuantity &quantity, MicroProps µs,
402 UErrorCode &status) const {
403 if (parent != NULL) {
404 parent->processQuantity(quantity, micros, status);
405 }
406 StandardPlural::Form pluralForm = utils::getPluralSafe(micros.rounder, rules, quantity, status);
407 micros.modOuter = &fModifiers[pluralForm];
408 }
409
getModifier(Signum,StandardPlural::Form plural) const410 const Modifier* LongNameHandler::getModifier(Signum /*signum*/, StandardPlural::Form plural) const {
411 return &fModifiers[plural];
412 }
413
forMeasureUnit(const Locale & loc,const MeasureUnit & mixedUnit,const UNumberUnitWidth & width,const PluralRules * rules,const MicroPropsGenerator * parent,MixedUnitLongNameHandler * fillIn,UErrorCode & status)414 void MixedUnitLongNameHandler::forMeasureUnit(const Locale &loc, const MeasureUnit &mixedUnit,
415 const UNumberUnitWidth &width, const PluralRules *rules,
416 const MicroPropsGenerator *parent,
417 MixedUnitLongNameHandler *fillIn, UErrorCode &status) {
418 U_ASSERT(mixedUnit.getComplexity(status) == UMEASURE_UNIT_MIXED);
419 U_ASSERT(fillIn != nullptr);
420
421 MeasureUnitImpl temp;
422 const MeasureUnitImpl& impl = MeasureUnitImpl::forMeasureUnit(mixedUnit, temp, status);
423 fillIn->fMixedUnitCount = impl.units.length();
424 fillIn->fMixedUnitData.adoptInstead(new UnicodeString[fillIn->fMixedUnitCount * ARRAY_LENGTH]);
425 for (int32_t i = 0; i < fillIn->fMixedUnitCount; i++) {
426 // Grab data for each of the components.
427 UnicodeString *unitData = &fillIn->fMixedUnitData[i * ARRAY_LENGTH];
428 getMeasureData(loc, impl.units[i]->build(status), width, unitData, status);
429 }
430
431 UListFormatterWidth listWidth = ULISTFMT_WIDTH_SHORT;
432 if (width == UNUM_UNIT_WIDTH_NARROW) {
433 listWidth = ULISTFMT_WIDTH_NARROW;
434 } else if (width == UNUM_UNIT_WIDTH_FULL_NAME) {
435 // This might be the same as SHORT in most languages:
436 listWidth = ULISTFMT_WIDTH_WIDE;
437 }
438 fillIn->fListFormatter.adoptInsteadAndCheckErrorCode(
439 ListFormatter::createInstance(loc, ULISTFMT_TYPE_UNITS, listWidth, status), status);
440 fillIn->rules = rules;
441 fillIn->parent = parent;
442
443 // We need a localised NumberFormatter for the integers of the bigger units
444 // (providing Arabic numerals, for example).
445 fillIn->fIntegerFormatter = NumberFormatter::withLocale(loc);
446 }
447
processQuantity(DecimalQuantity & quantity,MicroProps & micros,UErrorCode & status) const448 void MixedUnitLongNameHandler::processQuantity(DecimalQuantity &quantity, MicroProps µs,
449 UErrorCode &status) const {
450 U_ASSERT(fMixedUnitCount > 1);
451 if (parent != nullptr) {
452 parent->processQuantity(quantity, micros, status);
453 }
454 micros.modOuter = getMixedUnitModifier(quantity, micros, status);
455 }
456
getMixedUnitModifier(DecimalQuantity & quantity,MicroProps & micros,UErrorCode & status) const457 const Modifier *MixedUnitLongNameHandler::getMixedUnitModifier(DecimalQuantity &quantity,
458 MicroProps µs,
459 UErrorCode &status) const {
460 if (micros.mixedMeasuresCount == 0) {
461 U_ASSERT(micros.mixedMeasuresCount > 0); // Mixed unit: we must have more than one unit value
462 status = U_UNSUPPORTED_ERROR;
463 return µs.helpers.emptyWeakModifier;
464 }
465 // If we don't have at least one mixedMeasure, the LongNameHandler would be
466 // sufficient and we shouldn't be running MixedUnitLongNameHandler code:
467 U_ASSERT(micros.mixedMeasuresCount > 0);
468 // mixedMeasures does not contain the last value:
469 U_ASSERT(fMixedUnitCount == micros.mixedMeasuresCount + 1);
470 U_ASSERT(fListFormatter.isValid());
471
472 // Algorithm:
473 //
474 // For the mixed-units measurement of: "3 yard, 1 foot, 2.6 inch", we should
475 // find "3 yard" and "1 foot" in micros.mixedMeasures.
476 //
477 // Obtain long-names with plural forms corresponding to measure values:
478 // * {0} yards, {0} foot, {0} inches
479 //
480 // Format the integer values appropriately and modify with the format
481 // strings:
482 // - 3 yards, 1 foot
483 //
484 // Use ListFormatter to combine, with one placeholder:
485 // - 3 yards, 1 foot and {0} inches
486 //
487 // Return a SimpleModifier for this pattern, letting the rest of the
488 // pipeline take care of the remaining inches.
489
490 LocalArray<UnicodeString> outputMeasuresList(new UnicodeString[fMixedUnitCount], status);
491 if (U_FAILURE(status)) {
492 return µs.helpers.emptyWeakModifier;
493 }
494
495 for (int32_t i = 0; i < micros.mixedMeasuresCount; i++) {
496 DecimalQuantity fdec;
497 fdec.setToLong(micros.mixedMeasures[i]);
498 if (i > 0 && fdec.isNegative()) {
499 // If numbers are negative, only the first number needs to have its
500 // negative sign formatted.
501 fdec.negate();
502 }
503 StandardPlural::Form pluralForm = utils::getStandardPlural(rules, fdec);
504
505 UnicodeString simpleFormat =
506 getWithPlural(&fMixedUnitData[i * ARRAY_LENGTH], pluralForm, status);
507 SimpleFormatter compiledFormatter(simpleFormat, 0, 1, status);
508
509 UnicodeString num;
510 auto appendable = UnicodeStringAppendable(num);
511 fIntegerFormatter.formatDecimalQuantity(fdec, status).appendTo(appendable, status);
512 compiledFormatter.format(num, outputMeasuresList[i], status);
513 // TODO(icu-units#67): fix field positions
514 }
515
516 // Reiterated: we have at least one mixedMeasure:
517 U_ASSERT(micros.mixedMeasuresCount > 0);
518 // Thus if negative, a negative has already been formatted:
519 if (quantity.isNegative()) {
520 quantity.negate();
521 }
522
523 UnicodeString *finalSimpleFormats = &fMixedUnitData[(fMixedUnitCount - 1) * ARRAY_LENGTH];
524 StandardPlural::Form finalPlural = utils::getPluralSafe(micros.rounder, rules, quantity, status);
525 UnicodeString finalSimpleFormat = getWithPlural(finalSimpleFormats, finalPlural, status);
526 SimpleFormatter finalFormatter(finalSimpleFormat, 0, 1, status);
527 finalFormatter.format(UnicodeString(u"{0}"), outputMeasuresList[fMixedUnitCount - 1], status);
528
529 // Combine list into a "premixed" pattern
530 UnicodeString premixedFormatPattern;
531 fListFormatter->format(outputMeasuresList.getAlias(), fMixedUnitCount, premixedFormatPattern,
532 status);
533 SimpleFormatter premixedCompiled(premixedFormatPattern, 0, 1, status);
534 if (U_FAILURE(status)) {
535 return µs.helpers.emptyWeakModifier;
536 }
537
538 // TODO(icu-units#67): fix field positions
539 // Return a SimpleModifier for the "premixed" pattern
540 micros.helpers.mixedUnitModifier =
541 SimpleModifier(premixedCompiled, kUndefinedField, false, {this, SIGNUM_POS_ZERO, finalPlural});
542 return µs.helpers.mixedUnitModifier;
543 }
544
getModifier(Signum,StandardPlural::Form) const545 const Modifier *MixedUnitLongNameHandler::getModifier(Signum /*signum*/,
546 StandardPlural::Form /*plural*/) const {
547 // TODO(units): investigate this method when investigating where
548 // LongNameHandler::getModifier() gets used. To be sure it remains
549 // unreachable:
550 UPRV_UNREACHABLE;
551 return nullptr;
552 }
553
554 LongNameMultiplexer *
forMeasureUnits(const Locale & loc,const MaybeStackVector<MeasureUnit> & units,const UNumberUnitWidth & width,const PluralRules * rules,const MicroPropsGenerator * parent,UErrorCode & status)555 LongNameMultiplexer::forMeasureUnits(const Locale &loc, const MaybeStackVector<MeasureUnit> &units,
556 const UNumberUnitWidth &width, const PluralRules *rules,
557 const MicroPropsGenerator *parent, UErrorCode &status) {
558 LocalPointer<LongNameMultiplexer> result(new LongNameMultiplexer(parent), status);
559 if (U_FAILURE(status)) {
560 return nullptr;
561 }
562 U_ASSERT(units.length() > 0);
563 if (result->fHandlers.resize(units.length()) == nullptr) {
564 status = U_MEMORY_ALLOCATION_ERROR;
565 return nullptr;
566 }
567 result->fMeasureUnits.adoptInstead(new MeasureUnit[units.length()]);
568 for (int32_t i = 0, length = units.length(); i < length; i++) {
569 const MeasureUnit& unit = *units[i];
570 result->fMeasureUnits[i] = unit;
571 if (unit.getComplexity(status) == UMEASURE_UNIT_MIXED) {
572 MixedUnitLongNameHandler *mlnh = result->fMixedUnitHandlers.createAndCheckErrorCode(status);
573 MixedUnitLongNameHandler::forMeasureUnit(loc, unit, width, rules, NULL, mlnh, status);
574 result->fHandlers[i] = mlnh;
575 } else {
576 LongNameHandler *lnh = result->fLongNameHandlers.createAndCheckErrorCode(status);
577 LongNameHandler::forMeasureUnit(loc, unit, MeasureUnit(), width, rules, NULL, lnh, status);
578 result->fHandlers[i] = lnh;
579 }
580 if (U_FAILURE(status)) {
581 return nullptr;
582 }
583 }
584 return result.orphan();
585 }
586
processQuantity(DecimalQuantity & quantity,MicroProps & micros,UErrorCode & status) const587 void LongNameMultiplexer::processQuantity(DecimalQuantity &quantity, MicroProps µs,
588 UErrorCode &status) const {
589 // We call parent->processQuantity() from the Multiplexer, instead of
590 // letting LongNameHandler handle it: we don't know which LongNameHandler to
591 // call until we've called the parent!
592 fParent->processQuantity(quantity, micros, status);
593
594 // Call the correct LongNameHandler based on outputUnit
595 for (int i = 0; i < fHandlers.getCapacity(); i++) {
596 if (fMeasureUnits[i] == micros.outputUnit) {
597 fHandlers[i]->processQuantity(quantity, micros, status);
598 return;
599 }
600 }
601 if (U_FAILURE(status)) {
602 return;
603 }
604 // We shouldn't receive any outputUnit for which we haven't already got a
605 // LongNameHandler:
606 status = U_INTERNAL_PROGRAM_ERROR;
607 }
608
609 #endif /* #if !UCONFIG_NO_FORMATTING */
610