NumberRangeFormat.cpp (6710B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 #include "mozilla/intl/NumberRangeFormat.h" 6 7 #include "mozilla/Try.h" 8 #include "mozilla/intl/ICU4CGlue.h" 9 #include "mozilla/intl/NumberFormat.h" 10 #include "NumberFormatFields.h" 11 #include "NumberFormatterSkeleton.h" 12 #include "ScopedICUObject.h" 13 14 #include "unicode/uformattedvalue.h" 15 #include "unicode/unumberrangeformatter.h" 16 #include "unicode/upluralrules.h" 17 18 namespace mozilla::intl { 19 20 /*static*/ Result<UniquePtr<NumberRangeFormat>, ICUError> 21 NumberRangeFormat::TryCreate(std::string_view aLocale, 22 const NumberRangeFormatOptions& aOptions) { 23 UniquePtr<NumberRangeFormat> nrf = MakeUnique<NumberRangeFormat>(); 24 MOZ_TRY(nrf->initialize(aLocale, aOptions)); 25 return nrf; 26 } 27 28 NumberRangeFormat::~NumberRangeFormat() { 29 if (mFormattedNumberRange) { 30 unumrf_closeResult(mFormattedNumberRange); 31 } 32 if (mNumberRangeFormatter) { 33 unumrf_close(mNumberRangeFormatter); 34 } 35 } 36 37 Result<Ok, ICUError> NumberRangeFormat::initialize( 38 std::string_view aLocale, const NumberRangeFormatOptions& aOptions) { 39 mFormatForUnit = aOptions.mUnit.isSome(); 40 41 NumberFormatterSkeleton skeleton(aOptions); 42 mNumberRangeFormatter = skeleton.toRangeFormatter( 43 aLocale, aOptions.mRangeCollapse, aOptions.mRangeIdentityFallback); 44 if (mNumberRangeFormatter) { 45 UErrorCode status = U_ZERO_ERROR; 46 mFormattedNumberRange = unumrf_openResult(&status); 47 if (U_FAILURE(status)) { 48 return Err(ToICUError(status)); 49 } 50 return Ok(); 51 } 52 return Err(ICUError::InternalError); 53 } 54 55 Result<int32_t, ICUError> NumberRangeFormat::selectForRange( 56 double start, double end, char16_t* keyword, int32_t keywordSize, 57 const UPluralRules* pluralRules) const { 58 MOZ_ASSERT(keyword); 59 MOZ_ASSERT(pluralRules); 60 61 MOZ_TRY(format(start, end)); 62 63 UErrorCode status = U_ZERO_ERROR; 64 int32_t utf16KeywordLength = uplrules_selectForRange( 65 pluralRules, mFormattedNumberRange, keyword, keywordSize, &status); 66 if (U_FAILURE(status)) { 67 return Err(ToICUError(status)); 68 } 69 70 return utf16KeywordLength; 71 } 72 73 bool NumberRangeFormat::formatInternal(double start, double end) const { 74 // ICU incorrectly formats NaN values with the sign bit set, as if they 75 // were negative. Replace all NaNs with a single pattern with sign bit 76 // unset ("positive", that is) until ICU is fixed. 77 if (MOZ_UNLIKELY(std::isnan(start))) { 78 start = SpecificNaN<double>(0, 1); 79 } 80 if (MOZ_UNLIKELY(std::isnan(end))) { 81 end = SpecificNaN<double>(0, 1); 82 } 83 84 UErrorCode status = U_ZERO_ERROR; 85 unumrf_formatDoubleRange(mNumberRangeFormatter, start, end, 86 mFormattedNumberRange, &status); 87 return U_SUCCESS(status); 88 } 89 90 bool NumberRangeFormat::formatInternal(std::string_view start, 91 std::string_view end) const { 92 UErrorCode status = U_ZERO_ERROR; 93 unumrf_formatDecimalRange(mNumberRangeFormatter, start.data(), start.size(), 94 end.data(), end.size(), mFormattedNumberRange, 95 &status); 96 return U_SUCCESS(status); 97 } 98 99 Result<std::u16string_view, ICUError> NumberRangeFormat::formatResult() const { 100 UErrorCode status = U_ZERO_ERROR; 101 102 const UFormattedValue* formattedValue = 103 unumrf_resultAsValue(mFormattedNumberRange, &status); 104 if (U_FAILURE(status)) { 105 return Err(ToICUError(status)); 106 } 107 108 int32_t utf16Length; 109 const char16_t* utf16Str = 110 ufmtval_getString(formattedValue, &utf16Length, &status); 111 if (U_FAILURE(status)) { 112 return Err(ToICUError(status)); 113 } 114 115 return std::u16string_view(utf16Str, static_cast<size_t>(utf16Length)); 116 } 117 118 Result<std::u16string_view, ICUError> NumberRangeFormat::formatResultToParts( 119 Maybe<double> start, bool startIsNegative, Maybe<double> end, 120 bool endIsNegative, NumberPartVector& parts) const { 121 UErrorCode status = U_ZERO_ERROR; 122 123 const UFormattedValue* formattedValue = 124 unumrf_resultAsValue(mFormattedNumberRange, &status); 125 if (U_FAILURE(status)) { 126 return Err(ToICUError(status)); 127 } 128 129 int32_t utf16Length; 130 const char16_t* utf16Str = 131 ufmtval_getString(formattedValue, &utf16Length, &status); 132 if (U_FAILURE(status)) { 133 return Err(ToICUError(status)); 134 } 135 136 UConstrainedFieldPosition* fpos = ucfpos_open(&status); 137 if (U_FAILURE(status)) { 138 return Err(ToICUError(status)); 139 } 140 ScopedICUObject<UConstrainedFieldPosition, ucfpos_close> toCloseFpos(fpos); 141 142 Maybe<double> number = start; 143 bool isNegative = startIsNegative; 144 145 NumberPartSourceMap sourceMap; 146 147 // Vacuum up fields in the overall formatted string. 148 NumberFormatFields fields; 149 150 while (true) { 151 bool hasMore = ufmtval_nextPosition(formattedValue, fpos, &status); 152 if (U_FAILURE(status)) { 153 return Err(ToICUError(status)); 154 } 155 if (!hasMore) { 156 break; 157 } 158 159 int32_t category = ucfpos_getCategory(fpos, &status); 160 if (U_FAILURE(status)) { 161 return Err(ToICUError(status)); 162 } 163 164 int32_t fieldName = ucfpos_getField(fpos, &status); 165 if (U_FAILURE(status)) { 166 return Err(ToICUError(status)); 167 } 168 169 int32_t beginIndex, endIndex; 170 ucfpos_getIndexes(fpos, &beginIndex, &endIndex, &status); 171 if (U_FAILURE(status)) { 172 return Err(ToICUError(status)); 173 } 174 175 if (category == UFIELD_CATEGORY_NUMBER_RANGE_SPAN) { 176 // The special field category UFIELD_CATEGORY_NUMBER_RANGE_SPAN has only 177 // two allowed values (0 or 1), indicating the begin of the start resp. 178 // end number. 179 MOZ_ASSERT(fieldName == 0 || fieldName == 1, 180 "span category has unexpected value"); 181 182 if (fieldName == 0) { 183 number = start; 184 isNegative = startIsNegative; 185 186 sourceMap.start = {uint32_t(beginIndex), uint32_t(endIndex)}; 187 } else { 188 number = end; 189 isNegative = endIsNegative; 190 191 sourceMap.end = {uint32_t(beginIndex), uint32_t(endIndex)}; 192 } 193 194 continue; 195 } 196 197 // Ignore categories other than UFIELD_CATEGORY_NUMBER. 198 if (category != UFIELD_CATEGORY_NUMBER) { 199 continue; 200 } 201 202 Maybe<NumberPartType> partType = GetPartTypeForNumberField( 203 UNumberFormatFields(fieldName), number, isNegative, mFormatForUnit); 204 if (!partType || !fields.append(*partType, beginIndex, endIndex)) { 205 return Err(ToICUError(status)); 206 } 207 } 208 209 if (!fields.toPartsVector(utf16Length, sourceMap, parts)) { 210 return Err(ToICUError(status)); 211 } 212 213 return std::u16string_view(utf16Str, static_cast<size_t>(utf16Length)); 214 } 215 216 } // namespace mozilla::intl