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"
19 using namespace icu::number
;
20 using namespace icu::number::impl
;
24 constexpr int32_t DNAM_INDEX
= StandardPlural::Form::COUNT
;
25 constexpr int32_t PER_INDEX
= StandardPlural::Form::COUNT
+ 1;
26 constexpr int32_t ARRAY_LENGTH
= StandardPlural::Form::COUNT
+ 2;
28 static int32_t getIndex(const char* pluralKeyword
, UErrorCode
& status
) {
29 // pluralKeyword can also be "dnam" or "per"
30 if (uprv_strcmp(pluralKeyword
, "dnam") == 0) {
32 } else if (uprv_strcmp(pluralKeyword
, "per") == 0) {
35 StandardPlural::Form plural
= StandardPlural::fromString(pluralKeyword
, status
);
40 static UnicodeString
getWithPlural(
41 const UnicodeString
* strings
,
44 UnicodeString result
= strings
[plural
];
45 if (result
.isBogus()) {
46 result
= strings
[StandardPlural::Form::OTHER
];
48 if (result
.isBogus()) {
49 // There should always be data in the "other" plural variant.
50 status
= U_INTERNAL_PROGRAM_ERROR
;
56 //////////////////////////
57 /// BEGIN DATA LOADING ///
58 //////////////////////////
60 class PluralTableSink
: public ResourceSink
{
62 explicit PluralTableSink(UnicodeString
*outArray
) : outArray(outArray
) {
63 // Initialize the array to bogus strings.
64 for (int32_t i
= 0; i
< ARRAY_LENGTH
; i
++) {
65 outArray
[i
].setToBogus();
69 void put(const char *key
, ResourceValue
&value
, UBool
/*noFallback*/, UErrorCode
&status
) U_OVERRIDE
{
70 ResourceTable pluralsTable
= value
.getTable(status
);
71 if (U_FAILURE(status
)) { return; }
72 for (int32_t i
= 0; pluralsTable
.getKeyAndValue(i
, key
, value
); ++i
) {
73 int32_t index
= getIndex(key
, status
);
74 if (U_FAILURE(status
)) { return; }
75 if (!outArray
[index
].isBogus()) {
78 outArray
[index
] = value
.getUnicodeString(status
);
79 if (U_FAILURE(status
)) { return; }
84 UnicodeString
*outArray
;
87 // NOTE: outArray MUST have room for all StandardPlural values. No bounds checking is performed.
89 void getMeasureData(const Locale
&locale
, const MeasureUnit
&unit
, const UNumberUnitWidth
&width
,
90 UnicodeString
*outArray
, UErrorCode
&status
) {
91 PluralTableSink
sink(outArray
);
92 LocalUResourceBundlePointer
unitsBundle(ures_open(U_ICUDATA_UNIT
, locale
.getName(), &status
));
93 if (U_FAILURE(status
)) { return; }
95 key
.append("units", status
);
96 if (width
== UNUM_UNIT_WIDTH_NARROW
) {
97 key
.append("Narrow", status
);
98 } else if (width
== UNUM_UNIT_WIDTH_SHORT
) {
99 key
.append("Short", status
);
101 key
.append("/", status
);
102 key
.append(unit
.getType(), status
);
103 key
.append("/", status
);
104 key
.append(unit
.getSubtype(), status
);
105 ures_getAllItemsWithFallback(unitsBundle
.getAlias(), key
.data(), sink
, status
);
108 void getCurrencyLongNameData(const Locale
&locale
, const CurrencyUnit
¤cy
, UnicodeString
*outArray
,
109 UErrorCode
&status
) {
110 // In ICU4J, this method gets a CurrencyData from CurrencyData.provider.
111 // TODO(ICU4J): Implement this without going through CurrencyData, like in ICU4C?
112 PluralTableSink
sink(outArray
);
113 LocalUResourceBundlePointer
unitsBundle(ures_open(U_ICUDATA_CURR
, locale
.getName(), &status
));
114 if (U_FAILURE(status
)) { return; }
115 ures_getAllItemsWithFallback(unitsBundle
.getAlias(), "CurrencyUnitPatterns", sink
, status
);
116 if (U_FAILURE(status
)) { return; }
117 for (int32_t i
= 0; i
< StandardPlural::Form::COUNT
; i
++) {
118 UnicodeString
&pattern
= outArray
[i
];
119 if (pattern
.isBogus()) {
122 UBool isChoiceFormat
= FALSE
;
123 int32_t longNameLen
= 0;
124 const char16_t *longName
= ucurr_getPluralName(
125 currency
.getISOCurrency(),
128 StandardPlural::getKeyword(static_cast<StandardPlural::Form
>(i
)),
131 // Example pattern from data: "{0} {1}"
132 // Example output after find-and-replace: "{0} US dollars"
133 pattern
.findAndReplace(UnicodeString(u
"{1}"), UnicodeString(longName
, longNameLen
));
137 UnicodeString
getPerUnitFormat(const Locale
& locale
, const UNumberUnitWidth
&width
, UErrorCode
& status
) {
138 LocalUResourceBundlePointer
unitsBundle(ures_open(U_ICUDATA_UNIT
, locale
.getName(), &status
));
139 if (U_FAILURE(status
)) { return {}; }
141 key
.append("units", status
);
142 if (width
== UNUM_UNIT_WIDTH_NARROW
) {
143 key
.append("Narrow", status
);
144 } else if (width
== UNUM_UNIT_WIDTH_SHORT
) {
145 key
.append("Short", status
);
147 key
.append("/compound/per", status
);
149 const UChar
* ptr
= ures_getStringByKeyWithFallback(unitsBundle
.getAlias(), key
.data(), &len
, &status
);
150 return UnicodeString(ptr
, len
);
153 ////////////////////////
154 /// END DATA LOADING ///
155 ////////////////////////
160 LongNameHandler::forMeasureUnit(const Locale
&loc
, const MeasureUnit
&unitRef
, const MeasureUnit
&perUnit
,
161 const UNumberUnitWidth
&width
, const PluralRules
*rules
,
162 const MicroPropsGenerator
*parent
, UErrorCode
&status
) {
163 MeasureUnit unit
= unitRef
;
164 if (uprv_strcmp(perUnit
.getType(), "none") != 0) {
165 // Compound unit: first try to simplify (e.g., meters per second is its own unit).
166 bool isResolved
= false;
167 MeasureUnit resolved
= MeasureUnit::resolveUnitPerUnit(unit
, perUnit
, &isResolved
);
171 // No simplified form is available.
172 return forCompoundUnit(loc
, unit
, perUnit
, width
, rules
, parent
, status
);
176 LongNameHandler
result(rules
, parent
);
177 UnicodeString simpleFormats
[ARRAY_LENGTH
];
178 getMeasureData(loc
, unit
, width
, simpleFormats
, status
);
179 if (U_FAILURE(status
)) { return result
; }
180 // TODO: What field to use for units?
181 simpleFormatsToModifiers(simpleFormats
, UNUM_FIELD_COUNT
, result
.fModifiers
, status
);
186 LongNameHandler::forCompoundUnit(const Locale
&loc
, const MeasureUnit
&unit
, const MeasureUnit
&perUnit
,
187 const UNumberUnitWidth
&width
, const PluralRules
*rules
,
188 const MicroPropsGenerator
*parent
, UErrorCode
&status
) {
189 LongNameHandler
result(rules
, parent
);
190 UnicodeString primaryData
[ARRAY_LENGTH
];
191 getMeasureData(loc
, unit
, width
, primaryData
, status
);
192 if (U_FAILURE(status
)) { return result
; }
193 UnicodeString secondaryData
[ARRAY_LENGTH
];
194 getMeasureData(loc
, perUnit
, width
, secondaryData
, status
);
195 if (U_FAILURE(status
)) { return result
; }
197 UnicodeString perUnitFormat
;
198 if (!secondaryData
[PER_INDEX
].isBogus()) {
199 perUnitFormat
= secondaryData
[PER_INDEX
];
201 UnicodeString rawPerUnitFormat
= getPerUnitFormat(loc
, width
, status
);
202 if (U_FAILURE(status
)) { return result
; }
203 // rawPerUnitFormat is something like "{0}/{1}"; we need to substitute in the secondary unit.
204 SimpleFormatter
compiled(rawPerUnitFormat
, 2, 2, status
);
205 if (U_FAILURE(status
)) { return result
; }
206 UnicodeString secondaryFormat
= getWithPlural(secondaryData
, StandardPlural::Form::ONE
, status
);
207 if (U_FAILURE(status
)) { return result
; }
208 SimpleFormatter
secondaryCompiled(secondaryFormat
, 1, 1, status
);
209 if (U_FAILURE(status
)) { return result
; }
210 UnicodeString secondaryString
= secondaryCompiled
.getTextWithNoArguments().trim();
211 // TODO: Why does UnicodeString need to be explicit in the following line?
212 compiled
.format(UnicodeString(u
"{0}"), secondaryString
, perUnitFormat
, status
);
213 if (U_FAILURE(status
)) { return result
; }
215 // TODO: What field to use for units?
216 multiSimpleFormatsToModifiers(primaryData
, perUnitFormat
, UNUM_FIELD_COUNT
, result
.fModifiers
, status
);
220 LongNameHandler
LongNameHandler::forCurrencyLongNames(const Locale
&loc
, const CurrencyUnit
¤cy
,
221 const PluralRules
*rules
,
222 const MicroPropsGenerator
*parent
,
223 UErrorCode
&status
) {
224 LongNameHandler
result(rules
, parent
);
225 UnicodeString simpleFormats
[ARRAY_LENGTH
];
226 getCurrencyLongNameData(loc
, currency
, simpleFormats
, status
);
227 if (U_FAILURE(status
)) { return result
; }
228 simpleFormatsToModifiers(simpleFormats
, UNUM_CURRENCY_FIELD
, result
.fModifiers
, status
);
232 void LongNameHandler::simpleFormatsToModifiers(const UnicodeString
*simpleFormats
, Field field
,
233 SimpleModifier
*output
, UErrorCode
&status
) {
234 for (int32_t i
= 0; i
< StandardPlural::Form::COUNT
; i
++) {
235 UnicodeString simpleFormat
= getWithPlural(simpleFormats
, i
, status
);
236 if (U_FAILURE(status
)) { return; }
237 SimpleFormatter
compiledFormatter(simpleFormat
, 0, 1, status
);
238 if (U_FAILURE(status
)) { return; }
239 output
[i
] = SimpleModifier(compiledFormatter
, field
, false);
243 void LongNameHandler::multiSimpleFormatsToModifiers(const UnicodeString
*leadFormats
, UnicodeString trailFormat
,
244 Field field
, SimpleModifier
*output
, UErrorCode
&status
) {
245 SimpleFormatter
trailCompiled(trailFormat
, 1, 1, status
);
246 if (U_FAILURE(status
)) { return; }
247 for (int32_t i
= 0; i
< StandardPlural::Form::COUNT
; i
++) {
248 UnicodeString leadFormat
= getWithPlural(leadFormats
, i
, status
);
249 if (U_FAILURE(status
)) { return; }
250 UnicodeString compoundFormat
;
251 trailCompiled
.format(leadFormat
, compoundFormat
, status
);
252 if (U_FAILURE(status
)) { return; }
253 SimpleFormatter
compoundCompiled(compoundFormat
, 0, 1, status
);
254 if (U_FAILURE(status
)) { return; }
255 output
[i
] = SimpleModifier(compoundCompiled
, field
, false);
259 void LongNameHandler::processQuantity(DecimalQuantity
&quantity
, MicroProps
µs
,
260 UErrorCode
&status
) const {
261 parent
->processQuantity(quantity
, micros
, status
);
262 // TODO: Avoid the copy here?
263 DecimalQuantity
copy(quantity
);
264 micros
.rounder
.apply(copy
, status
);
265 micros
.modOuter
= &fModifiers
[utils::getStandardPlural(rules
, copy
)];
268 #endif /* #if !UCONFIG_NO_FORMATTING */