From: Shane Carr Date: Thu, 6 Sep 2018 03:44:27 +0000 (-0700) Subject: ICU-11276 Wiring SimpleFormatter logic into NumberRangeFormatter. X-Git-Tag: release-63-rc~63^2~21 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=7365e2c85a615bb0f36dc8e0f68222f608164630;p=icu ICU-11276 Wiring SimpleFormatter logic into NumberRangeFormatter. --- diff --git a/icu4c/source/i18n/number_formatimpl.cpp b/icu4c/source/i18n/number_formatimpl.cpp index d84ba2cfead..a48f810a679 100644 --- a/icu4c/source/i18n/number_formatimpl.cpp +++ b/icu4c/source/i18n/number_formatimpl.cpp @@ -71,13 +71,14 @@ NumberFormatterImpl::NumberFormatterImpl(const MacroProps& macros, UErrorCode& s : NumberFormatterImpl(macros, true, status) { } -void NumberFormatterImpl::formatStatic(const MacroProps& macros, DecimalQuantity& inValue, +int32_t NumberFormatterImpl::formatStatic(const MacroProps& macros, DecimalQuantity& inValue, NumberStringBuilder& outString, UErrorCode& status) { NumberFormatterImpl impl(macros, false, status); MicroProps& micros = impl.preProcessUnsafe(inValue, status); - if (U_FAILURE(status)) { return; } + if (U_FAILURE(status)) { return 0; } int32_t length = writeNumber(micros, inValue, outString, 0, status); - writeAffixes(micros, outString, 0, length, status); + length += writeAffixes(micros, outString, 0, length, status); + return length; } int32_t NumberFormatterImpl::getPrefixSuffixStatic(const MacroProps& macros, int8_t signum, @@ -92,13 +93,14 @@ int32_t NumberFormatterImpl::getPrefixSuffixStatic(const MacroProps& macros, int // The "unsafe" method simply re-uses fMicros, eliminating the extra copy operation. // See MicroProps::processQuantity() for details. -void NumberFormatterImpl::format(DecimalQuantity& inValue, NumberStringBuilder& outString, +int32_t NumberFormatterImpl::format(DecimalQuantity& inValue, NumberStringBuilder& outString, UErrorCode& status) const { MicroProps micros; preProcess(inValue, micros, status); - if (U_FAILURE(status)) { return; } + if (U_FAILURE(status)) { return 0; } int32_t length = writeNumber(micros, inValue, outString, 0, status); - writeAffixes(micros, outString, 0, length, status); + length += writeAffixes(micros, outString, 0, length, status); + return length; } void NumberFormatterImpl::preProcess(DecimalQuantity& inValue, MicroProps& microsOut, diff --git a/icu4c/source/i18n/number_formatimpl.h b/icu4c/source/i18n/number_formatimpl.h index 9b89e5b88e6..fda38c92845 100644 --- a/icu4c/source/i18n/number_formatimpl.h +++ b/icu4c/source/i18n/number_formatimpl.h @@ -34,7 +34,7 @@ class NumberFormatterImpl : public UMemory { /** * Builds and evaluates an "unsafe" MicroPropsGenerator, which is cheaper but can be used only once. */ - static void + static int32_t formatStatic(const MacroProps ¯os, DecimalQuantity &inValue, NumberStringBuilder &outString, UErrorCode &status); @@ -51,7 +51,7 @@ class NumberFormatterImpl : public UMemory { /** * Evaluates the "safe" MicroPropsGenerator created by "fromMacros". */ - void format(DecimalQuantity& inValue, NumberStringBuilder& outString, UErrorCode& status) const; + int32_t format(DecimalQuantity& inValue, NumberStringBuilder& outString, UErrorCode& status) const; /** * Like format(), but saves the result into an output MicroProps without additional processing. diff --git a/icu4c/source/i18n/number_modifiers.cpp b/icu4c/source/i18n/number_modifiers.cpp index d69413f1f63..07adce16c49 100644 --- a/icu4c/source/i18n/number_modifiers.cpp +++ b/icu4c/source/i18n/number_modifiers.cpp @@ -107,15 +107,19 @@ SimpleModifier::SimpleModifier(const SimpleFormatter &simpleFormatter, Field fie } else { U_ASSERT(argLimit == 1); if (fCompiledPattern.charAt(1) != 0) { + // Found prefix fPrefixLength = fCompiledPattern.charAt(1) - ARG_NUM_LIMIT; fSuffixOffset = 3 + fPrefixLength; } else { + // No prefix fPrefixLength = 0; fSuffixOffset = 2; } if (3 + fPrefixLength < fCompiledPattern.length()) { + // Found suffix fSuffixLength = fCompiledPattern.charAt(fSuffixOffset) - ARG_NUM_LIMIT; } else { + // No suffix fSuffixLength = 0; } } @@ -170,7 +174,7 @@ bool SimpleModifier::operator==(const Modifier& other) const { int32_t SimpleModifier::formatAsPrefixSuffix(NumberStringBuilder &result, int32_t startIndex, int32_t endIndex, Field field, UErrorCode &status) const { - if (fSuffixOffset == -1) { + if (fSuffixOffset == -1 && fPrefixLength + fSuffixLength > 0) { // There is no argument for the inner number; overwrite the entire segment with our string. return result.splice(startIndex, endIndex, fCompiledPattern, 2, 2 + fPrefixLength, field, status); } else { @@ -190,6 +194,65 @@ SimpleModifier::formatAsPrefixSuffix(NumberStringBuilder &result, int32_t startI } } + +int32_t +SimpleModifier::formatTwoArgPattern(const SimpleFormatter& compiled, NumberStringBuilder& result, + int32_t index, int32_t* outPrefixLength, int32_t* outSuffixLength, + Field field, UErrorCode& status) { + const UnicodeString& compiledPattern = compiled.compiledPattern; + int32_t argLimit = SimpleFormatter::getArgumentLimit( + compiledPattern.getBuffer(), compiledPattern.length()); + if (argLimit != 2) { + status = U_INTERNAL_PROGRAM_ERROR; + return 0; + } + int32_t offset = 1; // offset into compiledPattern + int32_t length = 0; // chars added to result + + int32_t prefixLength = compiledPattern.charAt(offset); + offset++; + if (prefixLength < ARG_NUM_LIMIT) { + // No prefix + prefixLength = 0; + } else { + prefixLength -= ARG_NUM_LIMIT; + result.insert(index + length, compiledPattern, offset, offset + prefixLength, field, status); + offset += prefixLength; + length += prefixLength; + offset++; + } + + int32_t infixLength = compiledPattern.charAt(offset); + offset++; + if (infixLength < ARG_NUM_LIMIT) { + // No infix + infixLength = 0; + } else { + infixLength -= ARG_NUM_LIMIT; + result.insert(index + length, compiledPattern, offset, offset + infixLength, field, status); + offset += infixLength; + length += infixLength; + offset++; + } + + int32_t suffixLength; + if (offset == compiledPattern.length()) { + // No suffix + suffixLength = 0; + } else { + suffixLength = compiledPattern.charAt(offset) - ARG_NUM_LIMIT; + offset++; + result.insert(index + length, compiledPattern, offset, offset + suffixLength, field, status); + length += suffixLength; + } + + *outPrefixLength = prefixLength; + *outSuffixLength = suffixLength; + + return length; +} + + int32_t ConstantMultiFieldModifier::apply(NumberStringBuilder &output, int leftIndex, int rightIndex, UErrorCode &status) const { int32_t length = output.insert(leftIndex, fPrefix, status); diff --git a/icu4c/source/i18n/number_modifiers.h b/icu4c/source/i18n/number_modifiers.h index fa69eeb2a5c..9209e3c0f34 100644 --- a/icu4c/source/i18n/number_modifiers.h +++ b/icu4c/source/i18n/number_modifiers.h @@ -74,7 +74,7 @@ class U_I18N_API SimpleModifier : public Modifier, public UMemory { /** * TODO: This belongs in SimpleFormatterImpl. The only reason I haven't moved it there yet is because - * DoubleSidedStringBuilder is an internal class and SimpleFormatterImpl feels like it should not depend on it. + * NumberStringBuilder is an internal class and SimpleFormatterImpl feels like it should not depend on it. * *

* Formats a value that is already stored inside the StringBuilder result between the indices @@ -93,16 +93,32 @@ class U_I18N_API SimpleModifier : public Modifier, public UMemory { * @return The number of characters (UTF-16 code points) that were added to the StringBuilder. */ int32_t - formatAsPrefixSuffix(NumberStringBuilder &result, int32_t startIndex, int32_t endIndex, Field field, - UErrorCode &status) const; + formatAsPrefixSuffix(NumberStringBuilder& result, int32_t startIndex, int32_t endIndex, Field field, + UErrorCode& status) const; + + /** + * TODO: Like above, this belongs with the rest of the SimpleFormatterImpl code. + * I put it here so that the SimpleFormatter uses in NumberStringBuilder are near each other. + * + *

+ * Applies the compiled two-argument pattern to the NumberStringBuilder. + * + *

+ * This method is optimized for the case where the prefix and suffix are often empty, such as + * in the range pattern like "{0}-{1}". + */ + static int32_t + formatTwoArgPattern(const SimpleFormatter& compiled, NumberStringBuilder& result, + int32_t index, int32_t* outPrefixLength, int32_t* outSuffixLength, + Field field, UErrorCode& status); private: UnicodeString fCompiledPattern; Field fField; - bool fStrong; - int32_t fPrefixLength; - int32_t fSuffixOffset; - int32_t fSuffixLength; + bool fStrong = false; + int32_t fPrefixLength = 0; + int32_t fSuffixOffset = -1; + int32_t fSuffixLength = 0; }; /** diff --git a/icu4c/source/i18n/number_stringbuilder.cpp b/icu4c/source/i18n/number_stringbuilder.cpp index 37770d11d51..2806fefbaa9 100644 --- a/icu4c/source/i18n/number_stringbuilder.cpp +++ b/icu4c/source/i18n/number_stringbuilder.cpp @@ -241,6 +241,9 @@ NumberStringBuilder::insert(int32_t index, const NumberStringBuilder &other, UEr } int32_t NumberStringBuilder::prepareForInsert(int32_t index, int32_t count, UErrorCode &status) { + U_ASSERT(index >= 0); + U_ASSERT(index <= fLength); + U_ASSERT(count >= 0); if (index == 0 && fZero - count >= 0) { // Append to start fZero -= count; diff --git a/icu4c/source/i18n/numrange_impl.cpp b/icu4c/source/i18n/numrange_impl.cpp index f2da9b70438..022d8faa76f 100644 --- a/icu4c/source/i18n/numrange_impl.cpp +++ b/icu4c/source/i18n/numrange_impl.cpp @@ -11,6 +11,8 @@ #include "unicode/numberrangeformatter.h" #include "numrange_impl.h" +#include "uresimp.h" +#include "util.h" using namespace icu; using namespace icu::number; @@ -23,6 +25,63 @@ constexpr int8_t identity2d(UNumberIdentityFallback a, UNumberRangeIdentityResul return static_cast(a) | (static_cast(b) << 4); } + +class NumberRangeDataSink : public ResourceSink { + public: + NumberRangeDataSink(NumberRangeData& data) : fData(data) {} + + void put(const char* key, ResourceValue& value, UBool /*noFallback*/, UErrorCode& status) U_OVERRIDE { + ResourceTable miscTable = value.getTable(status); + if (U_FAILURE(status)) { return; } + for (int i = 0; miscTable.getKeyAndValue(i, key, value); i++) { + if (uprv_strcmp(key, "range") == 0) { + if (fData.rangePattern.getArgumentLimit() == 0) { + continue; // have already seen this pattern + } + fData.rangePattern = {value.getUnicodeString(status), status}; + } else if (uprv_strcmp(key, "approximately") == 0) { + if (fData.approximatelyPattern.getArgumentLimit() == 0) { + continue; // have already seen this pattern + } + fData.approximatelyPattern = {value.getUnicodeString(status), status}; + } + } + } + + private: + NumberRangeData& fData; +}; + +void getNumberRangeData(const char* localeName, const char* nsName, NumberRangeData& data, UErrorCode& status) { + if (U_FAILURE(status)) { return; } + LocalUResourceBundlePointer rb(ures_open(NULL, localeName, &status)); + if (U_FAILURE(status)) { return; } + NumberRangeDataSink sink(data); + + CharString dataPath; + dataPath.append("NumberElements/", -1, status); + dataPath.append(nsName, -1, status); + dataPath.append("/miscPatterns", -1, status); + ures_getAllItemsWithFallback(rb.getAlias(), dataPath.data(), sink, status); + if (U_FAILURE(status)) { return; } + + if (uprv_strcmp(nsName, "latn") != 0 && (data.rangePattern.getArgumentLimit() == 0 + || data.approximatelyPattern.getArgumentLimit() == 0)) { + // fall back to Latin data + ures_getAllItemsWithFallback(rb.getAlias(), "NumberElements/latn/miscPatterns", sink, status); + if (U_FAILURE(status)) { return; } + } + + if (data.rangePattern.getArgumentLimit() == 0) { + // No data! + data.rangePattern = {u"{0} --- {1}", status}; + } + if (data.approximatelyPattern.getArgumentLimit() == 0) { + // No data! + data.approximatelyPattern = {u"~{0}", status}; + } +} + } // namespace @@ -32,6 +91,12 @@ NumberRangeFormatterImpl::NumberRangeFormatterImpl(const RangeMacroProps& macros fSameFormatters(true), // FIXME fCollapse(macros.collapse), fIdentityFallback(macros.identityFallback) { + // TODO: get local ns + NumberRangeData data; + getNumberRangeData(macros.locale.getName(), "latn", data, status); + if (U_FAILURE(status)) { return; } + fRangeFormatter = data.rangePattern; + fApproximatelyModifier = {data.approximatelyPattern, UNUM_FIELD_COUNT, false}; } void NumberRangeFormatterImpl::format(UFormattedNumberRangeData& data, bool equalBeforeRounding, UErrorCode& status) const { @@ -116,9 +181,8 @@ void NumberRangeFormatterImpl::formatApproximately (UFormattedNumberRangeData& d MicroProps& micros1, MicroProps& micros2, UErrorCode& status) const { if (fSameFormatters) { - // FIXME - formatterImpl1.format(data.quantity1, data.string, status); - data.string.insertCodePoint(0, u'~', UNUM_FIELD_COUNT, status); + int32_t length = formatterImpl1.format(data.quantity1, data.string, status); + fApproximatelyModifier.apply(data.string, 0, length, status); } else { formatRange(data, micros1, micros2, status); } @@ -198,30 +262,41 @@ void NumberRangeFormatterImpl::formatRange(UFormattedNumberRangeData& data, } NumberStringBuilder& string = data.string; + int32_t lengthPrefix = 0; int32_t length1 = 0; - int32_t lengthShared = 0; + int32_t lengthInfix = 0; int32_t length2 = 0; - #define UPRV_INDEX_0 0 - #define UPRV_INDEX_1 length1 - #define UPRV_INDEX_2 length1 + lengthShared - #define UPRV_INDEX_3 length1 + lengthShared + length2 + int32_t lengthSuffix = 0; + #define UPRV_INDEX_0 (lengthPrefix) + #define UPRV_INDEX_1 (lengthPrefix + length1) + #define UPRV_INDEX_2 (lengthPrefix + length1 + lengthInfix) + #define UPRV_INDEX_3 (lengthPrefix + length1 + lengthInfix + length2) + + int32_t lengthRange = SimpleModifier::formatTwoArgPattern( + fRangeFormatter, + string, + 0, + &lengthPrefix, + &lengthSuffix, + UNUM_FIELD_COUNT, + status); + if (U_FAILURE(status)) { return; } + lengthInfix = lengthRange - lengthPrefix - lengthSuffix; - // TODO: Use localized pattern - lengthShared += string.insert(UPRV_INDEX_0, u" --- ", UNUM_FIELD_COUNT, status); length1 += NumberFormatterImpl::writeNumber(micros1, data.quantity1, string, UPRV_INDEX_0, status); length2 += NumberFormatterImpl::writeNumber(micros2, data.quantity2, string, UPRV_INDEX_2, status); // TODO: Support padding? if (collapseInner) { - lengthShared += micros1.modInner->apply(string, UPRV_INDEX_0, UPRV_INDEX_3, status); + lengthInfix += micros1.modInner->apply(string, UPRV_INDEX_0, UPRV_INDEX_3, status); } else { length1 += micros1.modInner->apply(string, UPRV_INDEX_0, UPRV_INDEX_1, status); length2 += micros1.modInner->apply(string, UPRV_INDEX_2, UPRV_INDEX_3, status); } if (collapseMiddle) { - lengthShared += micros1.modMiddle->apply(string, UPRV_INDEX_0, UPRV_INDEX_3, status); + lengthInfix += micros1.modMiddle->apply(string, UPRV_INDEX_0, UPRV_INDEX_3, status); } else { length1 += micros1.modMiddle->apply(string, UPRV_INDEX_0, UPRV_INDEX_1, status); length2 += micros1.modMiddle->apply(string, UPRV_INDEX_2, UPRV_INDEX_3, status); diff --git a/icu4c/source/i18n/numrange_impl.h b/icu4c/source/i18n/numrange_impl.h index 8a478abe29a..5e08fad581c 100644 --- a/icu4c/source/i18n/numrange_impl.h +++ b/icu4c/source/i18n/numrange_impl.h @@ -9,6 +9,7 @@ #include "unicode/numberformatter.h" #include "unicode/numberrangeformatter.h" +#include "unicode/simpleformatter.h" #include "number_types.h" #include "number_decimalquantity.h" #include "number_formatimpl.h" @@ -43,6 +44,12 @@ struct UFormattedNumberRangeData : public UMemory { }; +struct NumberRangeData { + SimpleFormatter rangePattern; + SimpleFormatter approximatelyPattern; +}; + + class NumberRangeFormatterImpl : public UMemory { public: NumberRangeFormatterImpl(const RangeMacroProps& macros, UErrorCode& status); @@ -57,6 +64,9 @@ class NumberRangeFormatterImpl : public UMemory { UNumberRangeCollapse fCollapse; UNumberRangeIdentityFallback fIdentityFallback; + SimpleFormatter fRangeFormatter; + SimpleModifier fApproximatelyModifier; + void formatSingleValue(UFormattedNumberRangeData& data, MicroProps& micros1, MicroProps& micros2, UErrorCode& status) const;