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