node/deps/icu-small/source/i18n/messageformat2_formattable.cpp
Node.js GitHub Bot ab9660b55a
Some checks failed
Coverage Linux (without intl) / coverage-linux-without-intl (push) Waiting to run
Coverage Linux / coverage-linux (push) Waiting to run
Coverage Windows / coverage-windows (push) Waiting to run
Test and upload documentation to artifacts / build-docs (push) Waiting to run
Linters / lint-addon-docs (push) Waiting to run
Linters / lint-cpp (push) Waiting to run
Linters / format-cpp (push) Waiting to run
Linters / lint-js-and-md (push) Waiting to run
Linters / lint-py (push) Waiting to run
Linters / lint-yaml (push) Waiting to run
Linters / lint-sh (push) Waiting to run
Linters / lint-codeowners (push) Waiting to run
Linters / lint-pr-url (push) Waiting to run
Linters / lint-readme (push) Waiting to run
Notify on Push / Notify on Force Push on `main` (push) Waiting to run
Notify on Push / Notify on Push on `main` that lacks metadata (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
License update / update_license (push) Has been cancelled
Find inactive collaborators / find (push) Has been cancelled
deps: update icu to 77.1
PR-URL: https://github.com/nodejs/node/pull/57455
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Michaël Zasso <targos@protonmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
Reviewed-By: LiviaMedeiros <livia@cirno.name>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Steven R Loomis <srl295@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Richard Lau <rlau@redhat.com>
2025-03-16 20:10:32 +00:00

343 lines
12 KiB
C++

// © 2024 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#include "unicode/utypes.h"
#if !UCONFIG_NO_NORMALIZATION
#if !UCONFIG_NO_FORMATTING
#if !UCONFIG_NO_MF2
#include "unicode/messageformat2_formattable.h"
#include "unicode/smpdtfmt.h"
#include "messageformat2_macros.h"
#include "limits.h"
U_NAMESPACE_BEGIN
namespace message2 {
// Fallback values are enclosed in curly braces;
// see https://github.com/unicode-org/message-format-wg/blob/main/spec/formatting.md#formatting-fallback-values
static UnicodeString fallbackToString(const UnicodeString& s) {
UnicodeString result;
result += LEFT_CURLY_BRACE;
result += s;
result += RIGHT_CURLY_BRACE;
return result;
}
Formattable& Formattable::operator=(Formattable other) noexcept {
swap(*this, other);
return *this;
}
Formattable::Formattable(const Formattable& other) {
contents = other.contents;
holdsDate = other.holdsDate;
}
Formattable Formattable::forDecimal(std::string_view number, UErrorCode &status) {
Formattable f;
// The relevant overload of the StringPiece constructor
// casts the string length to int32_t, so we have to check
// that the length makes sense
if (number.size() > INT_MAX) {
status = U_ILLEGAL_ARGUMENT_ERROR;
} else {
f.contents = icu::Formattable(StringPiece(number), status);
}
return f;
}
UFormattableType Formattable::getType() const {
if (std::holds_alternative<double>(contents)) {
return holdsDate ? UFMT_DATE : UFMT_DOUBLE;
}
if (std::holds_alternative<int64_t>(contents)) {
return UFMT_INT64;
}
if (std::holds_alternative<UnicodeString>(contents)) {
return UFMT_STRING;
}
if (isDecimal()) {
switch (std::get_if<icu::Formattable>(&contents)->getType()) {
case icu::Formattable::Type::kLong: {
return UFMT_LONG;
}
case icu::Formattable::Type::kDouble: {
return UFMT_DOUBLE;
}
default: {
return UFMT_INT64;
}
}
}
if (std::holds_alternative<const FormattableObject*>(contents)) {
return UFMT_OBJECT;
}
return UFMT_ARRAY;
}
const Formattable* Formattable::getArray(int32_t& len, UErrorCode& status) const {
NULL_ON_ERROR(status);
if (getType() != UFMT_ARRAY) {
len = 0;
status = U_ILLEGAL_ARGUMENT_ERROR;
return nullptr;
}
const std::pair<const Formattable*, int32_t>& p = *std::get_if<std::pair<const Formattable*, int32_t>>(&contents);
U_ASSERT(p.first != nullptr);
len = p.second;
return p.first;
}
int64_t Formattable::getInt64(UErrorCode& status) const {
if (isDecimal() && isNumeric()) {
return std::get_if<icu::Formattable>(&contents)->getInt64(status);
}
switch (getType()) {
case UFMT_LONG:
case UFMT_INT64: {
return *std::get_if<int64_t>(&contents);
}
case UFMT_DOUBLE: {
return icu::Formattable(*std::get_if<double>(&contents)).getInt64(status);
}
default: {
status = U_INVALID_FORMAT_ERROR;
return 0;
}
}
}
icu::Formattable Formattable::asICUFormattable(UErrorCode& status) const {
if (U_FAILURE(status)) {
return {};
}
// Type must not be UFMT_ARRAY or UFMT_OBJECT
if (getType() == UFMT_ARRAY || getType() == UFMT_OBJECT) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return {};
}
if (isDecimal()) {
return *std::get_if<icu::Formattable>(&contents);
}
switch (getType()) {
case UFMT_DATE: {
return icu::Formattable(*std::get_if<double>(&contents), icu::Formattable::kIsDate);
}
case UFMT_DOUBLE: {
return icu::Formattable(*std::get_if<double>(&contents));
}
case UFMT_LONG: {
return icu::Formattable(static_cast<int32_t>(*std::get_if<double>(&contents)));
}
case UFMT_INT64: {
return icu::Formattable(*std::get_if<int64_t>(&contents));
}
case UFMT_STRING: {
return icu::Formattable(*std::get_if<UnicodeString>(&contents));
}
default: {
// Already checked for UFMT_ARRAY and UFMT_OBJECT
return icu::Formattable();
}
}
}
Formattable::~Formattable() {}
FormattableObject::~FormattableObject() {}
FormattedMessage::~FormattedMessage() {}
FormattedValue::FormattedValue(const UnicodeString& s) {
type = kString;
stringOutput = std::move(s);
}
FormattedValue::FormattedValue(number::FormattedNumber&& n) {
type = kNumber;
numberOutput = std::move(n);
}
FormattedValue& FormattedValue::operator=(FormattedValue&& other) noexcept {
type = other.type;
if (type == kString) {
stringOutput = std::move(other.stringOutput);
} else {
numberOutput = std::move(other.numberOutput);
}
return *this;
}
FormattedValue::~FormattedValue() {}
FormattedPlaceholder& FormattedPlaceholder::operator=(FormattedPlaceholder&& other) noexcept {
type = other.type;
source = other.source;
if (type == kEvaluated) {
formatted = std::move(other.formatted);
previousOptions = std::move(other.previousOptions);
}
fallback = other.fallback;
return *this;
}
const Formattable& FormattedPlaceholder::asFormattable() const {
return source;
}
// Default formatters
// ------------------
number::FormattedNumber formatNumberWithDefaults(const Locale& locale, double toFormat, UErrorCode& errorCode) {
return number::NumberFormatter::withLocale(locale).formatDouble(toFormat, errorCode);
}
number::FormattedNumber formatNumberWithDefaults(const Locale& locale, int32_t toFormat, UErrorCode& errorCode) {
return number::NumberFormatter::withLocale(locale).formatInt(toFormat, errorCode);
}
number::FormattedNumber formatNumberWithDefaults(const Locale& locale, int64_t toFormat, UErrorCode& errorCode) {
return number::NumberFormatter::withLocale(locale).formatInt(toFormat, errorCode);
}
number::FormattedNumber formatNumberWithDefaults(const Locale& locale, StringPiece toFormat, UErrorCode& errorCode) {
return number::NumberFormatter::withLocale(locale).formatDecimal(toFormat, errorCode);
}
DateFormat* defaultDateTimeInstance(const Locale& locale, UErrorCode& errorCode) {
NULL_ON_ERROR(errorCode);
LocalPointer<DateFormat> df(DateFormat::createDateTimeInstance(DateFormat::SHORT, DateFormat::SHORT, locale));
if (!df.isValid()) {
errorCode = U_MEMORY_ALLOCATION_ERROR;
return nullptr;
}
return df.orphan();
}
void formatDateWithDefaults(const Locale& locale, UDate date, UnicodeString& result, UErrorCode& errorCode) {
CHECK_ERROR(errorCode);
LocalPointer<DateFormat> df(defaultDateTimeInstance(locale, errorCode));
CHECK_ERROR(errorCode);
df->format(date, result, 0, errorCode);
}
// Called when output is required and the contents are an unevaluated `Formattable`;
// formats the source `Formattable` to a string with defaults, if it can be
// formatted with a default formatter
static FormattedPlaceholder formatWithDefaults(const Locale& locale, const FormattedPlaceholder& input, UErrorCode& status) {
if (U_FAILURE(status)) {
return {};
}
const Formattable& toFormat = input.asFormattable();
// Try as decimal number first
if (toFormat.isNumeric()) {
// Note: the ICU Formattable has to be created here since the StringPiece
// refers to state inside the Formattable; so otherwise we'll have a reference
// to a temporary object
icu::Formattable icuFormattable = toFormat.asICUFormattable(status);
StringPiece asDecimal = icuFormattable.getDecimalNumber(status);
if (U_FAILURE(status)) {
return {};
}
if (asDecimal != nullptr) {
return FormattedPlaceholder(input, FormattedValue(formatNumberWithDefaults(locale, asDecimal, status)));
}
}
UFormattableType type = toFormat.getType();
switch (type) {
case UFMT_DATE: {
UnicodeString result;
UDate d = toFormat.getDate(status);
U_ASSERT(U_SUCCESS(status));
formatDateWithDefaults(locale, d, result, status);
return FormattedPlaceholder(input, FormattedValue(std::move(result)));
}
case UFMT_DOUBLE: {
double d = toFormat.getDouble(status);
U_ASSERT(U_SUCCESS(status));
return FormattedPlaceholder(input, FormattedValue(formatNumberWithDefaults(locale, d, status)));
}
case UFMT_LONG: {
int32_t l = toFormat.getLong(status);
U_ASSERT(U_SUCCESS(status));
return FormattedPlaceholder(input, FormattedValue(formatNumberWithDefaults(locale, l, status)));
}
case UFMT_INT64: {
int64_t i = toFormat.getInt64Value(status);
U_ASSERT(U_SUCCESS(status));
return FormattedPlaceholder(input, FormattedValue(formatNumberWithDefaults(locale, i, status)));
}
case UFMT_STRING: {
const UnicodeString& s = toFormat.getString(status);
U_ASSERT(U_SUCCESS(status));
return FormattedPlaceholder(input, FormattedValue(UnicodeString(s)));
}
default: {
// No default formatters for other types; use fallback
status = U_MF_FORMATTING_ERROR;
// Note: it would be better to set an internal formatting error so that a string
// (e.g. the type tag) can be provided. However, this method is called by the
// public method formatToString() and thus can't take a MessageContext
return FormattedPlaceholder(input.getFallback());
}
}
}
// Called when string output is required; forces output to be produced
// if none is present (including formatting number output as a string)
UnicodeString FormattedPlaceholder::formatToString(const Locale& locale,
UErrorCode& status) const {
if (U_FAILURE(status)) {
return {};
}
if (isFallback() || isNullOperand()) {
return fallbackToString(fallback);
}
// Evaluated value: either just return the string, or format the number
// as a string and return it
if (isEvaluated()) {
if (formatted.isString()) {
return formatted.getString();
} else {
return formatted.getNumber().toString(status);
}
}
// Unevaluated value: first evaluate it fully, then format
UErrorCode savedStatus = status;
FormattedPlaceholder evaluated = formatWithDefaults(locale, *this, status);
if (status == U_MF_FORMATTING_ERROR) {
U_ASSERT(evaluated.isFallback());
return evaluated.getFallback();
}
// Ignore U_USING_DEFAULT_WARNING
if (status == U_USING_DEFAULT_WARNING) {
status = savedStatus;
}
return evaluated.formatToString(locale, status);
}
} // namespace message2
U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_MF2 */
#endif /* #if !UCONFIG_NO_FORMATTING */
#endif /* #if !UCONFIG_NO_NORMALIZATION */