summaryrefslogtreecommitdiffstats
path: root/intl/components/src/NumberRangeFormat.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'intl/components/src/NumberRangeFormat.cpp')
-rw-r--r--intl/components/src/NumberRangeFormat.cpp216
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