diff options
Diffstat (limited to 'intl/components/src/NumberRangeFormat.cpp')
-rw-r--r-- | intl/components/src/NumberRangeFormat.cpp | 216 |
1 files changed, 216 insertions, 0 deletions
diff --git a/intl/components/src/NumberRangeFormat.cpp b/intl/components/src/NumberRangeFormat.cpp new file mode 100644 index 0000000000..9de8982c72 --- /dev/null +++ b/intl/components/src/NumberRangeFormat.cpp @@ -0,0 +1,216 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/intl/NumberRangeFormat.h" + +#include "mozilla/Try.h" +#include "mozilla/intl/ICU4CGlue.h" +#include "mozilla/intl/NumberFormat.h" +#include "NumberFormatFields.h" +#include "NumberFormatterSkeleton.h" +#include "ScopedICUObject.h" + +#include "unicode/uformattedvalue.h" +#include "unicode/unumberrangeformatter.h" +#include "unicode/upluralrules.h" + +namespace mozilla::intl { + +/*static*/ Result<UniquePtr<NumberRangeFormat>, ICUError> +NumberRangeFormat::TryCreate(std::string_view aLocale, + const NumberRangeFormatOptions& aOptions) { + UniquePtr<NumberRangeFormat> nrf = MakeUnique<NumberRangeFormat>(); + MOZ_TRY(nrf->initialize(aLocale, aOptions)); + return nrf; +} + +NumberRangeFormat::~NumberRangeFormat() { + if (mFormattedNumberRange) { + unumrf_closeResult(mFormattedNumberRange); + } + if (mNumberRangeFormatter) { + unumrf_close(mNumberRangeFormatter); + } +} + +Result<Ok, ICUError> NumberRangeFormat::initialize( + std::string_view aLocale, const NumberRangeFormatOptions& aOptions) { + mFormatForUnit = aOptions.mUnit.isSome(); + + NumberFormatterSkeleton skeleton(aOptions); + mNumberRangeFormatter = skeleton.toRangeFormatter( + aLocale, aOptions.mRangeCollapse, aOptions.mRangeIdentityFallback); + if (mNumberRangeFormatter) { + UErrorCode status = U_ZERO_ERROR; + mFormattedNumberRange = unumrf_openResult(&status); + if (U_FAILURE(status)) { + return Err(ToICUError(status)); + } + return Ok(); + } + return Err(ICUError::InternalError); +} + +Result<int32_t, ICUError> NumberRangeFormat::selectForRange( + double start, double end, char16_t* keyword, int32_t keywordSize, + const UPluralRules* pluralRules) const { + MOZ_ASSERT(keyword); + MOZ_ASSERT(pluralRules); + + MOZ_TRY(format(start, end)); + + UErrorCode status = U_ZERO_ERROR; + int32_t utf16KeywordLength = uplrules_selectForRange( + pluralRules, mFormattedNumberRange, keyword, keywordSize, &status); + if (U_FAILURE(status)) { + return Err(ToICUError(status)); + } + + return utf16KeywordLength; +} + +bool NumberRangeFormat::formatInternal(double start, double end) const { + // ICU incorrectly formats NaN values with the sign bit set, as if they + // were negative. Replace all NaNs with a single pattern with sign bit + // unset ("positive", that is) until ICU is fixed. + if (MOZ_UNLIKELY(std::isnan(start))) { + start = SpecificNaN<double>(0, 1); + } + if (MOZ_UNLIKELY(std::isnan(end))) { + end = SpecificNaN<double>(0, 1); + } + + UErrorCode status = U_ZERO_ERROR; + unumrf_formatDoubleRange(mNumberRangeFormatter, start, end, + mFormattedNumberRange, &status); + return U_SUCCESS(status); +} + +bool NumberRangeFormat::formatInternal(std::string_view start, + std::string_view end) const { + UErrorCode status = U_ZERO_ERROR; + unumrf_formatDecimalRange(mNumberRangeFormatter, start.data(), start.size(), + end.data(), end.size(), mFormattedNumberRange, + &status); + return U_SUCCESS(status); +} + +Result<std::u16string_view, ICUError> NumberRangeFormat::formatResult() const { + UErrorCode status = U_ZERO_ERROR; + + const UFormattedValue* formattedValue = + unumrf_resultAsValue(mFormattedNumberRange, &status); + if (U_FAILURE(status)) { + return Err(ToICUError(status)); + } + + int32_t utf16Length; + const char16_t* utf16Str = + ufmtval_getString(formattedValue, &utf16Length, &status); + if (U_FAILURE(status)) { + return Err(ToICUError(status)); + } + + return std::u16string_view(utf16Str, static_cast<size_t>(utf16Length)); +} + +Result<std::u16string_view, ICUError> NumberRangeFormat::formatResultToParts( + Maybe<double> start, bool startIsNegative, Maybe<double> end, + bool endIsNegative, NumberPartVector& parts) const { + UErrorCode status = U_ZERO_ERROR; + + const UFormattedValue* formattedValue = + unumrf_resultAsValue(mFormattedNumberRange, &status); + if (U_FAILURE(status)) { + return Err(ToICUError(status)); + } + + int32_t utf16Length; + const char16_t* utf16Str = + ufmtval_getString(formattedValue, &utf16Length, &status); + if (U_FAILURE(status)) { + return Err(ToICUError(status)); + } + + UConstrainedFieldPosition* fpos = ucfpos_open(&status); + if (U_FAILURE(status)) { + return Err(ToICUError(status)); + } + ScopedICUObject<UConstrainedFieldPosition, ucfpos_close> toCloseFpos(fpos); + + Maybe<double> number = start; + bool isNegative = startIsNegative; + + NumberPartSourceMap sourceMap; + + // Vacuum up fields in the overall formatted string. + NumberFormatFields fields; + + while (true) { + bool hasMore = ufmtval_nextPosition(formattedValue, fpos, &status); + if (U_FAILURE(status)) { + return Err(ToICUError(status)); + } + if (!hasMore) { + break; + } + + int32_t category = ucfpos_getCategory(fpos, &status); + if (U_FAILURE(status)) { + return Err(ToICUError(status)); + } + + int32_t fieldName = ucfpos_getField(fpos, &status); + if (U_FAILURE(status)) { + return Err(ToICUError(status)); + } + + int32_t beginIndex, endIndex; + ucfpos_getIndexes(fpos, &beginIndex, &endIndex, &status); + if (U_FAILURE(status)) { + return Err(ToICUError(status)); + } + + if (category == UFIELD_CATEGORY_NUMBER_RANGE_SPAN) { + // The special field category UFIELD_CATEGORY_NUMBER_RANGE_SPAN has only + // two allowed values (0 or 1), indicating the begin of the start resp. + // end number. + MOZ_ASSERT(fieldName == 0 || fieldName == 1, + "span category has unexpected value"); + + if (fieldName == 0) { + number = start; + isNegative = startIsNegative; + + sourceMap.start = {uint32_t(beginIndex), uint32_t(endIndex)}; + } else { + number = end; + isNegative = endIsNegative; + + sourceMap.end = {uint32_t(beginIndex), uint32_t(endIndex)}; + } + + continue; + } + + // Ignore categories other than UFIELD_CATEGORY_NUMBER. + if (category != UFIELD_CATEGORY_NUMBER) { + continue; + } + + Maybe<NumberPartType> partType = GetPartTypeForNumberField( + UNumberFormatFields(fieldName), number, isNegative, mFormatForUnit); + if (!partType || !fields.append(*partType, beginIndex, endIndex)) { + return Err(ToICUError(status)); + } + } + + if (!fields.toPartsVector(utf16Length, sourceMap, parts)) { + return Err(ToICUError(status)); + } + + return std::u16string_view(utf16Str, static_cast<size_t>(utf16Length)); +} + +} // namespace mozilla::intl |