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