1 // © 2017 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
4 #include "unicode/utypes.h"
6 #if !UCONFIG_NO_FORMATTING
8 #include "unicode/simpleformatter.h"
9 #include "unicode/ures.h"
13 #include "number_longnames.h"
14 #include "number_microprops.h"
20 using namespace icu::number
;
21 using namespace icu::number::impl
;
25 constexpr int32_t DNAM_INDEX
= StandardPlural::Form::COUNT
;
26 constexpr int32_t PER_INDEX
= StandardPlural::Form::COUNT
+ 1;
27 constexpr int32_t ARRAY_LENGTH
= StandardPlural::Form::COUNT
+ 2;
29 static int32_t getIndex(const char* pluralKeyword
, UErrorCode
& status
) {
30 // pluralKeyword can also be "dnam" or "per"
31 if (uprv_strcmp(pluralKeyword
, "dnam") == 0) {
33 } else if (uprv_strcmp(pluralKeyword
, "per") == 0) {
36 StandardPlural::Form plural
= StandardPlural::fromString(pluralKeyword
, status
);
41 static UnicodeString
getWithPlural(
42 const UnicodeString
* strings
,
43 StandardPlural::Form plural
,
45 UnicodeString result
= strings
[plural
];
46 if (result
.isBogus()) {
47 result
= strings
[StandardPlural::Form::OTHER
];
49 if (result
.isBogus()) {
50 // There should always be data in the "other" plural variant.
51 status
= U_INTERNAL_PROGRAM_ERROR
;
57 //////////////////////////
58 /// BEGIN DATA LOADING ///
59 //////////////////////////
61 class PluralTableSink
: public ResourceSink
{
63 explicit PluralTableSink(UnicodeString
*outArray
) : outArray(outArray
) {
64 // Initialize the array to bogus strings.
65 for (int32_t i
= 0; i
< ARRAY_LENGTH
; i
++) {
66 outArray
[i
].setToBogus();
70 void put(const char *key
, ResourceValue
&value
, UBool
/*noFallback*/, UErrorCode
&status
) U_OVERRIDE
{
71 ResourceTable pluralsTable
= value
.getTable(status
);
72 if (U_FAILURE(status
)) { return; }
73 for (int32_t i
= 0; pluralsTable
.getKeyAndValue(i
, key
, value
); ++i
) {
74 int32_t index
= getIndex(key
, status
);
75 if (U_FAILURE(status
)) { return; }
76 if (!outArray
[index
].isBogus()) {
79 outArray
[index
] = value
.getUnicodeString(status
);
80 if (U_FAILURE(status
)) { return; }
85 UnicodeString
*outArray
;
88 // NOTE: outArray MUST have room for all StandardPlural values. No bounds checking is performed.
90 void getMeasureData(const Locale
&locale
, const MeasureUnit
&unit
, const UNumberUnitWidth
&width
,
91 UnicodeString
*outArray
, UErrorCode
&status
) {
92 PluralTableSink
sink(outArray
);
93 LocalUResourceBundlePointer
unitsBundle(ures_open(U_ICUDATA_UNIT
, locale
.getName(), &status
));
94 if (U_FAILURE(status
)) { return; }
96 // Map duration-year-person, duration-week-person, etc. to duration-year, duration-week, ...
97 // TODO(ICU-20400): Get duration-*-person data properly with aliases.
98 StringPiece subtypeForResource
;
99 int32_t subtypeLen
= static_cast<int32_t>(uprv_strlen(unit
.getSubtype()));
100 if (subtypeLen
> 7 && uprv_strcmp(unit
.getSubtype() + subtypeLen
- 7, "-person") == 0) {
101 subtypeForResource
= {unit
.getSubtype(), subtypeLen
- 7};
103 subtypeForResource
= unit
.getSubtype();
107 key
.append("units", status
);
108 if (width
== UNUM_UNIT_WIDTH_NARROW
) {
109 key
.append("Narrow", status
);
110 } else if (width
== UNUM_UNIT_WIDTH_SHORT
) {
111 key
.append("Short", status
);
113 key
.append("/", status
);
114 key
.append(unit
.getType(), status
);
115 key
.append("/", status
);
116 key
.append(subtypeForResource
, status
);
118 UErrorCode localStatus
= U_ZERO_ERROR
;
119 ures_getAllItemsWithFallback(unitsBundle
.getAlias(), key
.data(), sink
, localStatus
);
120 if (width
== UNUM_UNIT_WIDTH_SHORT
) {
121 if (U_FAILURE(localStatus
)) {
122 status
= localStatus
;
127 // TODO(ICU-13353): The fallback to short does not work in ICU4C.
128 // Manually fall back to short (this is done automatically in Java).
130 key
.append("unitsShort/", status
);
131 key
.append(unit
.getType(), status
);
132 key
.append("/", status
);
133 key
.append(subtypeForResource
, status
);
134 ures_getAllItemsWithFallback(unitsBundle
.getAlias(), key
.data(), sink
, status
);
137 void getCurrencyLongNameData(const Locale
&locale
, const CurrencyUnit
¤cy
, UnicodeString
*outArray
,
138 UErrorCode
&status
) {
139 // In ICU4J, this method gets a CurrencyData from CurrencyData.provider.
140 // TODO(ICU4J): Implement this without going through CurrencyData, like in ICU4C?
141 PluralTableSink
sink(outArray
);
142 LocalUResourceBundlePointer
unitsBundle(ures_open(U_ICUDATA_CURR
, locale
.getName(), &status
));
143 if (U_FAILURE(status
)) { return; }
144 ures_getAllItemsWithFallback(unitsBundle
.getAlias(), "CurrencyUnitPatterns", sink
, status
);
145 if (U_FAILURE(status
)) { return; }
146 for (int32_t i
= 0; i
< StandardPlural::Form::COUNT
; i
++) {
147 UnicodeString
&pattern
= outArray
[i
];
148 if (pattern
.isBogus()) {
151 UBool isChoiceFormat
= FALSE
;
152 int32_t longNameLen
= 0;
153 const char16_t *longName
= ucurr_getPluralName(
154 currency
.getISOCurrency(),
157 StandardPlural::getKeyword(static_cast<StandardPlural::Form
>(i
)),
160 // Example pattern from data: "{0} {1}"
161 // Example output after find-and-replace: "{0} US dollars"
162 pattern
.findAndReplace(UnicodeString(u
"{1}"), UnicodeString(longName
, longNameLen
));
166 UnicodeString
getPerUnitFormat(const Locale
& locale
, const UNumberUnitWidth
&width
, UErrorCode
& status
) {
167 LocalUResourceBundlePointer
unitsBundle(ures_open(U_ICUDATA_UNIT
, locale
.getName(), &status
));
168 if (U_FAILURE(status
)) { return {}; }
170 key
.append("units", status
);
171 if (width
== UNUM_UNIT_WIDTH_NARROW
) {
172 key
.append("Narrow", status
);
173 } else if (width
== UNUM_UNIT_WIDTH_SHORT
) {
174 key
.append("Short", status
);
176 key
.append("/compound/per", status
);
178 const UChar
* ptr
= ures_getStringByKeyWithFallback(unitsBundle
.getAlias(), key
.data(), &len
, &status
);
179 return UnicodeString(ptr
, len
);
182 ////////////////////////
183 /// END DATA LOADING ///
184 ////////////////////////
189 LongNameHandler::forMeasureUnit(const Locale
&loc
, const MeasureUnit
&unitRef
, const MeasureUnit
&perUnit
,
190 const UNumberUnitWidth
&width
, const PluralRules
*rules
,
191 const MicroPropsGenerator
*parent
, UErrorCode
&status
) {
192 MeasureUnit unit
= unitRef
;
193 if (uprv_strcmp(perUnit
.getType(), "none") != 0) {
194 // Compound unit: first try to simplify (e.g., meters per second is its own unit).
195 bool isResolved
= false;
196 MeasureUnit resolved
= MeasureUnit::resolveUnitPerUnit(unit
, perUnit
, &isResolved
);
200 // No simplified form is available.
201 return forCompoundUnit(loc
, unit
, perUnit
, width
, rules
, parent
, status
);
205 auto* result
= new LongNameHandler(rules
, parent
);
206 if (result
== nullptr) {
207 status
= U_MEMORY_ALLOCATION_ERROR
;
210 UnicodeString simpleFormats
[ARRAY_LENGTH
];
211 getMeasureData(loc
, unit
, width
, simpleFormats
, status
);
212 if (U_FAILURE(status
)) { return result
; }
213 result
->simpleFormatsToModifiers(simpleFormats
, UNUM_MEASURE_UNIT_FIELD
, status
);
218 LongNameHandler::forCompoundUnit(const Locale
&loc
, const MeasureUnit
&unit
, const MeasureUnit
&perUnit
,
219 const UNumberUnitWidth
&width
, const PluralRules
*rules
,
220 const MicroPropsGenerator
*parent
, UErrorCode
&status
) {
221 auto* result
= new LongNameHandler(rules
, parent
);
222 if (result
== nullptr) {
223 status
= U_MEMORY_ALLOCATION_ERROR
;
226 UnicodeString primaryData
[ARRAY_LENGTH
];
227 getMeasureData(loc
, unit
, width
, primaryData
, status
);
228 if (U_FAILURE(status
)) { return result
; }
229 UnicodeString secondaryData
[ARRAY_LENGTH
];
230 getMeasureData(loc
, perUnit
, width
, secondaryData
, status
);
231 if (U_FAILURE(status
)) { return result
; }
233 UnicodeString perUnitFormat
;
234 if (!secondaryData
[PER_INDEX
].isBogus()) {
235 perUnitFormat
= secondaryData
[PER_INDEX
];
237 UnicodeString rawPerUnitFormat
= getPerUnitFormat(loc
, width
, status
);
238 if (U_FAILURE(status
)) { return result
; }
239 // rawPerUnitFormat is something like "{0}/{1}"; we need to substitute in the secondary unit.
240 SimpleFormatter
compiled(rawPerUnitFormat
, 2, 2, status
);
241 if (U_FAILURE(status
)) { return result
; }
242 UnicodeString secondaryFormat
= getWithPlural(secondaryData
, StandardPlural::Form::ONE
, status
);
243 if (U_FAILURE(status
)) { return result
; }
244 SimpleFormatter
secondaryCompiled(secondaryFormat
, 1, 1, status
);
245 if (U_FAILURE(status
)) { return result
; }
246 UnicodeString secondaryString
= secondaryCompiled
.getTextWithNoArguments().trim();
247 // TODO: Why does UnicodeString need to be explicit in the following line?
248 compiled
.format(UnicodeString(u
"{0}"), secondaryString
, perUnitFormat
, status
);
249 if (U_FAILURE(status
)) { return result
; }
251 result
->multiSimpleFormatsToModifiers(primaryData
, perUnitFormat
, UNUM_MEASURE_UNIT_FIELD
, status
);
255 UnicodeString
LongNameHandler::getUnitDisplayName(
257 const MeasureUnit
& unit
,
258 UNumberUnitWidth width
,
259 UErrorCode
& status
) {
260 if (U_FAILURE(status
)) {
261 return ICU_Utility::makeBogusString();
263 UnicodeString simpleFormats
[ARRAY_LENGTH
];
264 getMeasureData(loc
, unit
, width
, simpleFormats
, status
);
265 return simpleFormats
[DNAM_INDEX
];
268 UnicodeString
LongNameHandler::getUnitPattern( // Apple-specific
270 const MeasureUnit
& unit
,
271 UNumberUnitWidth width
,
272 StandardPlural::Form pluralForm
,
273 UErrorCode
& status
) {
274 if (U_FAILURE(status
)) {
275 return ICU_Utility::makeBogusString();
277 UnicodeString simpleFormats
[ARRAY_LENGTH
];
278 getMeasureData(loc
, unit
, width
, simpleFormats
, status
);
279 return simpleFormats
[pluralForm
];
282 LongNameHandler
* LongNameHandler::forCurrencyLongNames(const Locale
&loc
, const CurrencyUnit
¤cy
,
283 const PluralRules
*rules
,
284 const MicroPropsGenerator
*parent
,
285 UErrorCode
&status
) {
286 auto* result
= new LongNameHandler(rules
, parent
);
287 if (result
== nullptr) {
288 status
= U_MEMORY_ALLOCATION_ERROR
;
291 UnicodeString simpleFormats
[ARRAY_LENGTH
];
292 getCurrencyLongNameData(loc
, currency
, simpleFormats
, status
);
293 if (U_FAILURE(status
)) { return nullptr; }
294 result
->simpleFormatsToModifiers(simpleFormats
, UNUM_CURRENCY_FIELD
, status
);
298 void LongNameHandler::simpleFormatsToModifiers(const UnicodeString
*simpleFormats
, Field field
,
299 UErrorCode
&status
) {
300 for (int32_t i
= 0; i
< StandardPlural::Form::COUNT
; i
++) {
301 StandardPlural::Form plural
= static_cast<StandardPlural::Form
>(i
);
302 UnicodeString simpleFormat
= getWithPlural(simpleFormats
, plural
, status
);
303 if (U_FAILURE(status
)) { return; }
304 SimpleFormatter
compiledFormatter(simpleFormat
, 0, 1, status
);
305 if (U_FAILURE(status
)) { return; }
306 fModifiers
[i
] = SimpleModifier(compiledFormatter
, field
, false, {this, 0, plural
});
310 void LongNameHandler::multiSimpleFormatsToModifiers(const UnicodeString
*leadFormats
, UnicodeString trailFormat
,
311 Field field
, UErrorCode
&status
) {
312 SimpleFormatter
trailCompiled(trailFormat
, 1, 1, status
);
313 if (U_FAILURE(status
)) { return; }
314 for (int32_t i
= 0; i
< StandardPlural::Form::COUNT
; i
++) {
315 StandardPlural::Form plural
= static_cast<StandardPlural::Form
>(i
);
316 UnicodeString leadFormat
= getWithPlural(leadFormats
, plural
, status
);
317 if (U_FAILURE(status
)) { return; }
318 UnicodeString compoundFormat
;
319 trailCompiled
.format(leadFormat
, compoundFormat
, status
);
320 if (U_FAILURE(status
)) { return; }
321 SimpleFormatter
compoundCompiled(compoundFormat
, 0, 1, status
);
322 if (U_FAILURE(status
)) { return; }
323 fModifiers
[i
] = SimpleModifier(compoundCompiled
, field
, false, {this, 0, plural
});
327 void LongNameHandler::processQuantity(DecimalQuantity
&quantity
, MicroProps
µs
,
328 UErrorCode
&status
) const {
329 parent
->processQuantity(quantity
, micros
, status
);
330 StandardPlural::Form pluralForm
= utils::getPluralSafe(micros
.rounder
, rules
, quantity
, status
);
331 micros
.modOuter
= &fModifiers
[pluralForm
];
334 const Modifier
* LongNameHandler::getModifier(int8_t /*signum*/, StandardPlural::Form plural
) const {
335 return &fModifiers
[plural
];
338 #endif /* #if !UCONFIG_NO_FORMATTING */