216 lines
6.6 KiB
C++
216 lines
6.6 KiB
C++
/* 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
|