xref: /aosp_15_r20/external/icu/icu4c/source/i18n/messageformat2_formattable.cpp (revision 0e209d3975ff4a8c132096b14b0e9364a753506e)
1 // © 2024 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 #if !UCONFIG_NO_MF2
9 
10 #include "unicode/messageformat2_formattable.h"
11 #include "unicode/smpdtfmt.h"
12 #include "messageformat2_macros.h"
13 
14 #include "limits.h"
15 
16 U_NAMESPACE_BEGIN
17 
18 namespace message2 {
19 
20     // Fallback values are enclosed in curly braces;
21     // see https://github.com/unicode-org/message-format-wg/blob/main/spec/formatting.md#formatting-fallback-values
22 
fallbackToString(const UnicodeString & s)23     static UnicodeString fallbackToString(const UnicodeString& s) {
24         UnicodeString result;
25         result += LEFT_CURLY_BRACE;
26         result += s;
27         result += RIGHT_CURLY_BRACE;
28         return result;
29     }
30 
operator =(Formattable other)31     Formattable& Formattable::operator=(Formattable other) noexcept {
32         swap(*this, other);
33         return *this;
34     }
35 
Formattable(const Formattable & other)36     Formattable::Formattable(const Formattable& other) {
37         contents = other.contents;
38         holdsDate = other.holdsDate;
39     }
40 
forDecimal(std::string_view number,UErrorCode & status)41     Formattable Formattable::forDecimal(std::string_view number, UErrorCode &status) {
42         Formattable f;
43         // The relevant overload of the StringPiece constructor
44         // casts the string length to int32_t, so we have to check
45         // that the length makes sense
46         if (number.size() > INT_MAX) {
47             status = U_ILLEGAL_ARGUMENT_ERROR;
48         } else {
49             f.contents = icu::Formattable(StringPiece(number), status);
50         }
51         return f;
52     }
53 
getType() const54     UFormattableType Formattable::getType() const {
55         if (std::holds_alternative<double>(contents)) {
56             return holdsDate ? UFMT_DATE : UFMT_DOUBLE;
57         }
58         if (std::holds_alternative<int64_t>(contents)) {
59             return UFMT_INT64;
60         }
61         if (std::holds_alternative<UnicodeString>(contents)) {
62             return UFMT_STRING;
63         }
64         if (isDecimal()) {
65             switch (std::get_if<icu::Formattable>(&contents)->getType()) {
66             case icu::Formattable::Type::kLong: {
67                 return UFMT_LONG;
68             }
69             case icu::Formattable::Type::kDouble: {
70                 return UFMT_DOUBLE;
71             }
72             default: {
73                 return UFMT_INT64;
74             }
75             }
76         }
77         if (std::holds_alternative<const FormattableObject*>(contents)) {
78             return UFMT_OBJECT;
79         }
80         return UFMT_ARRAY;
81     }
82 
getArray(int32_t & len,UErrorCode & status) const83     const Formattable* Formattable::getArray(int32_t& len, UErrorCode& status) const {
84         NULL_ON_ERROR(status);
85 
86         if (getType() != UFMT_ARRAY) {
87             len = 0;
88             status = U_ILLEGAL_ARGUMENT_ERROR;
89             return nullptr;
90         }
91         const std::pair<const Formattable*, int32_t>& p = *std::get_if<std::pair<const Formattable*, int32_t>>(&contents);
92         U_ASSERT(p.first != nullptr);
93         len = p.second;
94         return p.first;
95     }
96 
getInt64(UErrorCode & status) const97     int64_t Formattable::getInt64(UErrorCode& status) const {
98         if (isDecimal() && isNumeric()) {
99             return std::get_if<icu::Formattable>(&contents)->getInt64(status);
100         }
101 
102         switch (getType()) {
103         case UFMT_LONG:
104         case UFMT_INT64: {
105             return *std::get_if<int64_t>(&contents);
106         }
107         case UFMT_DOUBLE: {
108             return icu::Formattable(*std::get_if<double>(&contents)).getInt64(status);
109         }
110         default: {
111             status = U_INVALID_FORMAT_ERROR;
112             return 0;
113         }
114         }
115     }
116 
asICUFormattable(UErrorCode & status) const117     icu::Formattable Formattable::asICUFormattable(UErrorCode& status) const {
118         if (U_FAILURE(status)) {
119             return {};
120         }
121         // Type must not be UFMT_ARRAY or UFMT_OBJECT
122         if (getType() == UFMT_ARRAY || getType() == UFMT_OBJECT) {
123             status = U_ILLEGAL_ARGUMENT_ERROR;
124             return {};
125         }
126 
127         if (isDecimal()) {
128             return *std::get_if<icu::Formattable>(&contents);
129         }
130 
131         switch (getType()) {
132         case UFMT_DATE: {
133             return icu::Formattable(*std::get_if<double>(&contents), icu::Formattable::kIsDate);
134         }
135         case UFMT_DOUBLE: {
136             return icu::Formattable(*std::get_if<double>(&contents));
137         }
138         case UFMT_LONG: {
139             return icu::Formattable(static_cast<int32_t>(*std::get_if<double>(&contents)));
140         }
141         case UFMT_INT64: {
142             return icu::Formattable(*std::get_if<int64_t>(&contents));
143         }
144         case UFMT_STRING: {
145             return icu::Formattable(*std::get_if<UnicodeString>(&contents));
146         }
147         default: {
148             // Already checked for UFMT_ARRAY and UFMT_OBJECT
149             return icu::Formattable();
150         }
151         }
152     }
153 
~Formattable()154     Formattable::~Formattable() {}
155 
~FormattableObject()156     FormattableObject::~FormattableObject() {}
157 
~FormattedMessage()158     FormattedMessage::~FormattedMessage() {}
159 
FormattedValue(const UnicodeString & s)160     FormattedValue::FormattedValue(const UnicodeString& s) {
161         type = kString;
162         stringOutput = std::move(s);
163     }
164 
FormattedValue(number::FormattedNumber && n)165     FormattedValue::FormattedValue(number::FormattedNumber&& n) {
166         type = kNumber;
167         numberOutput = std::move(n);
168     }
169 
operator =(FormattedValue && other)170     FormattedValue& FormattedValue::operator=(FormattedValue&& other) noexcept {
171         type = other.type;
172         if (type == kString) {
173             stringOutput = std::move(other.stringOutput);
174         } else {
175             numberOutput = std::move(other.numberOutput);
176         }
177         return *this;
178     }
179 
~FormattedValue()180     FormattedValue::~FormattedValue() {}
181 
operator =(FormattedPlaceholder && other)182     FormattedPlaceholder& FormattedPlaceholder::operator=(FormattedPlaceholder&& other) noexcept {
183         type = other.type;
184         source = other.source;
185         if (type == kEvaluated) {
186             formatted = std::move(other.formatted);
187             previousOptions = std::move(other.previousOptions);
188         }
189         fallback = other.fallback;
190         return *this;
191     }
192 
asFormattable() const193     const Formattable& FormattedPlaceholder::asFormattable() const {
194         return source;
195     }
196 
197     // Default formatters
198     // ------------------
199 
formatNumberWithDefaults(const Locale & locale,double toFormat,UErrorCode & errorCode)200     number::FormattedNumber formatNumberWithDefaults(const Locale& locale, double toFormat, UErrorCode& errorCode) {
201         return number::NumberFormatter::withLocale(locale).formatDouble(toFormat, errorCode);
202     }
203 
formatNumberWithDefaults(const Locale & locale,int32_t toFormat,UErrorCode & errorCode)204     number::FormattedNumber formatNumberWithDefaults(const Locale& locale, int32_t toFormat, UErrorCode& errorCode) {
205         return number::NumberFormatter::withLocale(locale).formatInt(toFormat, errorCode);
206     }
207 
formatNumberWithDefaults(const Locale & locale,int64_t toFormat,UErrorCode & errorCode)208     number::FormattedNumber formatNumberWithDefaults(const Locale& locale, int64_t toFormat, UErrorCode& errorCode) {
209         return number::NumberFormatter::withLocale(locale).formatInt(toFormat, errorCode);
210     }
211 
formatNumberWithDefaults(const Locale & locale,StringPiece toFormat,UErrorCode & errorCode)212     number::FormattedNumber formatNumberWithDefaults(const Locale& locale, StringPiece toFormat, UErrorCode& errorCode) {
213         return number::NumberFormatter::withLocale(locale).formatDecimal(toFormat, errorCode);
214     }
215 
defaultDateTimeInstance(const Locale & locale,UErrorCode & errorCode)216     DateFormat* defaultDateTimeInstance(const Locale& locale, UErrorCode& errorCode) {
217         NULL_ON_ERROR(errorCode);
218         LocalPointer<DateFormat> df(DateFormat::createDateTimeInstance(DateFormat::SHORT, DateFormat::SHORT, locale));
219         if (!df.isValid()) {
220             errorCode = U_MEMORY_ALLOCATION_ERROR;
221             return nullptr;
222         }
223         return df.orphan();
224     }
225 
formatDateWithDefaults(const Locale & locale,UDate date,UnicodeString & result,UErrorCode & errorCode)226     void formatDateWithDefaults(const Locale& locale, UDate date, UnicodeString& result, UErrorCode& errorCode) {
227         CHECK_ERROR(errorCode);
228 
229         LocalPointer<DateFormat> df(defaultDateTimeInstance(locale, errorCode));
230         CHECK_ERROR(errorCode);
231         df->format(date, result, 0, errorCode);
232     }
233 
234     // Called when output is required and the contents are an unevaluated `Formattable`;
235     // formats the source `Formattable` to a string with defaults, if it can be
236     // formatted with a default formatter
formatWithDefaults(const Locale & locale,const FormattedPlaceholder & input,UErrorCode & status)237     static FormattedPlaceholder formatWithDefaults(const Locale& locale, const FormattedPlaceholder& input, UErrorCode& status) {
238         if (U_FAILURE(status)) {
239             return {};
240         }
241 
242         const Formattable& toFormat = input.asFormattable();
243         // Try as decimal number first
244         if (toFormat.isNumeric()) {
245             // Note: the ICU Formattable has to be created here since the StringPiece
246             // refers to state inside the Formattable; so otherwise we'll have a reference
247             // to a temporary object
248             icu::Formattable icuFormattable = toFormat.asICUFormattable(status);
249             StringPiece asDecimal = icuFormattable.getDecimalNumber(status);
250             if (U_FAILURE(status)) {
251                 return {};
252             }
253             if (asDecimal != nullptr) {
254                 return FormattedPlaceholder(input, FormattedValue(formatNumberWithDefaults(locale, asDecimal, status)));
255             }
256         }
257 
258         UFormattableType type = toFormat.getType();
259         switch (type) {
260         case UFMT_DATE: {
261             UnicodeString result;
262             UDate d = toFormat.getDate(status);
263             U_ASSERT(U_SUCCESS(status));
264             formatDateWithDefaults(locale, d, result, status);
265             return FormattedPlaceholder(input, FormattedValue(std::move(result)));
266         }
267         case UFMT_DOUBLE: {
268             double d = toFormat.getDouble(status);
269             U_ASSERT(U_SUCCESS(status));
270             return FormattedPlaceholder(input, FormattedValue(formatNumberWithDefaults(locale, d, status)));
271         }
272         case UFMT_LONG: {
273             int32_t l = toFormat.getLong(status);
274             U_ASSERT(U_SUCCESS(status));
275             return FormattedPlaceholder(input, FormattedValue(formatNumberWithDefaults(locale, l, status)));
276         }
277         case UFMT_INT64: {
278             int64_t i = toFormat.getInt64Value(status);
279             U_ASSERT(U_SUCCESS(status));
280             return FormattedPlaceholder(input, FormattedValue(formatNumberWithDefaults(locale, i, status)));
281         }
282         case UFMT_STRING: {
283             const UnicodeString& s = toFormat.getString(status);
284             U_ASSERT(U_SUCCESS(status));
285             return FormattedPlaceholder(input, FormattedValue(UnicodeString(s)));
286         }
287         default: {
288             // No default formatters for other types; use fallback
289             status = U_MF_FORMATTING_ERROR;
290             // Note: it would be better to set an internal formatting error so that a string
291             // (e.g. the type tag) can be provided. However, this  method is called by the
292             // public method formatToString() and thus can't take a MessageContext
293             return FormattedPlaceholder(input.getFallback());
294         }
295         }
296     }
297 
298     // Called when string output is required; forces output to be produced
299     // if none is present (including formatting number output as a string)
formatToString(const Locale & locale,UErrorCode & status) const300     UnicodeString FormattedPlaceholder::formatToString(const Locale& locale,
301                                                        UErrorCode& status) const {
302         if (U_FAILURE(status)) {
303             return {};
304         }
305         if (isFallback() || isNullOperand()) {
306             return fallbackToString(fallback);
307         }
308 
309         // Evaluated value: either just return the string, or format the number
310         // as a string and return it
311         if (isEvaluated()) {
312             if (formatted.isString()) {
313                 return formatted.getString();
314             } else {
315                 return formatted.getNumber().toString(status);
316             }
317         }
318         // Unevaluated value: first evaluate it fully, then format
319         UErrorCode savedStatus = status;
320         FormattedPlaceholder evaluated = formatWithDefaults(locale, *this, status);
321         if (status == U_MF_FORMATTING_ERROR) {
322             U_ASSERT(evaluated.isFallback());
323             return evaluated.getFallback();
324         }
325         // Ignore U_USING_DEFAULT_WARNING
326         if (status == U_USING_DEFAULT_WARNING) {
327             status = savedStatus;
328         }
329         return evaluated.formatToString(locale, status);
330     }
331 
332 } // namespace message2
333 
334 U_NAMESPACE_END
335 
336 #endif /* #if !UCONFIG_NO_MF2 */
337 
338 #endif /* #if !UCONFIG_NO_FORMATTING */
339