]> granicus.if.org Git - icu/commitdiff
ICU-11276 Initial NumberRangeFormatter implementation. Needs data loading and implem...
authorShane Carr <shane@unicode.org>
Thu, 6 Sep 2018 00:42:41 +0000 (17:42 -0700)
committerShane Carr <shane@unicode.org>
Thu, 27 Sep 2018 21:27:40 +0000 (14:27 -0700)
12 files changed:
icu4c/source/i18n/i18n.vcxproj
icu4c/source/i18n/i18n.vcxproj.filters
icu4c/source/i18n/i18n_uwp.vcxproj
icu4c/source/i18n/number_modifiers.h
icu4c/source/i18n/number_patternmodifier.h
icu4c/source/i18n/number_scientific.h
icu4c/source/i18n/number_types.h
icu4c/source/i18n/numrange_fluent.cpp
icu4c/source/i18n/numrange_impl.cpp
icu4c/source/i18n/numrange_impl.h [moved from icu4c/source/i18n/numrange_types.h with 60% similarity]
icu4c/source/i18n/unicode/numberformatter.h
icu4c/source/i18n/unicode/numberrangeformatter.h

index 25b62d36cdbc59c5eb368241e8c1b9b250e6f944..fe1e71d905cae948bda8739f647f4b50689b2c4b 100644 (file)
     <ClInclude Include="numparse_validators.h" />
     <ClInclude Include="numparse_types.h" />
     <ClInclude Include="numparse_utils.h" />
-    <ClInclude Include="numrange_types.h" />
+    <ClInclude Include="numrange_impl.h" />
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="i18n.rc" />
index ca431e607fc924b3c8b5f1a720105fd4db735678..c8f984514271a9127e4bea8af92304b38b095e28 100644 (file)
     <ClInclude Include="numparse_utils.h">
       <Filter>formatting</Filter>
     </ClInclude>
-    <ClInclude Include="numrange_types.h">
+    <ClInclude Include="numrange_impl.h">
       <Filter>formatting</Filter>
     </ClInclude>
     <ClInclude Include="olsontz.h">
index 3b0b18231ae32e0993f8af0124d3104e838afbbb..bdc42e64a66b6480699b7a6e95637c36569536c4 100644 (file)
     <ClInclude Include="numparse_validators.h" />
     <ClInclude Include="numparse_types.h" />
     <ClInclude Include="numparse_utils.h" />
-    <ClInclude Include="numrange_types.h" />
+    <ClInclude Include="numrange_impl.h" />
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="i18n.rc" />
index a553100cd92a9b6fbaeca6dfd69d98d5550fa3b0..c1feafa8ecc37a1c766e24e05017668af5ca0f24 100644 (file)
@@ -37,6 +37,10 @@ class U_I18N_API ConstantAffixModifier : public Modifier, public UObject {
 
     bool isStrong() const U_OVERRIDE;
 
+    bool containsField(UNumberFormatFields field) const U_OVERRIDE;
+
+    bool operator==(const Modifier& other) const U_OVERRIDE;
+
   private:
     UnicodeString fPrefix;
     UnicodeString fSuffix;
@@ -64,6 +68,10 @@ class U_I18N_API SimpleModifier : public Modifier, public UMemory {
 
     bool isStrong() const U_OVERRIDE;
 
+    bool containsField(UNumberFormatFields field) const U_OVERRIDE;
+
+    bool operator==(const Modifier& other) const U_OVERRIDE;
+
     /**
      * 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.
@@ -122,6 +130,10 @@ class U_I18N_API ConstantMultiFieldModifier : public Modifier, public UMemory {
 
     bool isStrong() const U_OVERRIDE;
 
+    bool containsField(UNumberFormatFields field) const U_OVERRIDE;
+
+    bool operator==(const Modifier& other) const U_OVERRIDE;
+
   protected:
     // NOTE: In Java, these are stored as array pointers. In C++, the NumberStringBuilder is stored by
     // value and is treated internally as immutable.
@@ -206,6 +218,16 @@ class U_I18N_API EmptyModifier : public Modifier, public UMemory {
         return fStrong;
     }
 
+    bool containsField(UNumberFormatFields field) const U_OVERRIDE {
+        (void)field;
+        return false;
+    }
+
+    bool operator==(const Modifier& other) const U_OVERRIDE {
+        UErrorCode status = U_ZERO_ERROR;
+        return other.getCodePointCount(status) == 0;
+    }
+
   private:
     bool fStrong;
 };
index 2854ca056a115c47a639e576354a2a5f65a0e877..eb1457d92b434a96a1f35358c40d919704726348 100644 (file)
@@ -184,6 +184,10 @@ class U_I18N_API MutablePatternModifier
 
     bool isStrong() const U_OVERRIDE;
 
+    bool containsField(UNumberFormatFields field) const U_OVERRIDE;
+
+    bool operator==(const Modifier& other) const U_OVERRIDE;
+
     /**
      * Returns the string that substitutes a given symbol type in a pattern.
      */
index 974ab3adb614ca34637981d03bfbb530f10c6f22..4c7cdf36ee873903543351dc11d990f62b4e9ba7 100644 (file)
@@ -30,6 +30,10 @@ class U_I18N_API ScientificModifier : public UMemory, public Modifier {
 
     bool isStrong() const U_OVERRIDE;
 
+    bool containsField(UNumberFormatFields field) const U_OVERRIDE;
+
+    bool operator==(const Modifier& other) const U_OVERRIDE;
+
   private:
     int32_t fExponent;
     const ScientificHandler *fHandler;
index 57da72f8aa0ac1ffe122d761009a898a368636ed..db13a23133487226547e7e64630c1fce99439d8a 100644 (file)
@@ -127,6 +127,7 @@ class U_I18N_API AffixPatternProvider {
     virtual bool hasBody() const = 0;
 };
 
+
 /**
  * A Modifier is an object that can be passed through the formatting pipeline until it is finally applied to the string
  * builder. A Modifier usually contains a prefix and a suffix that are applied, but it could contain something else,
@@ -177,6 +178,16 @@ class U_I18N_API Modifier {
      * @return Whether the modifier is strong.
      */
     virtual bool isStrong() const = 0;
+
+    /**
+     * Whether the modifier contains at least one occurrence of the given field.
+     */
+    virtual bool containsField(UNumberFormatFields field) const = 0;
+
+    /**
+     * Returns whether the affixes owned by this modifier are equal to the ones owned by the given modifier.
+     */
+    virtual bool operator==(const Modifier& other) const = 0;
 };
 
 /**
index 5dcd38554c77b83b37f6b6ac202ebd5334cf742a..6a2704e5fdf34e0d6305a621b0afbd4ba8174cc5 100644 (file)
@@ -9,7 +9,7 @@
 // Helpful in toString methods and elsewhere.
 #define UNISTR_FROM_STRING_EXPLICIT
 
-#include "numrange_types.h"
+#include "numrange_impl.h"
 #include "util.h"
 #include "number_utypes.h"
 
@@ -241,7 +241,9 @@ FormattedNumberRange LocalizedNumberRangeFormatter::formatFormattableRange(
 
 void LocalizedNumberRangeFormatter::formatImpl(
         UFormattedNumberRangeData* results, UErrorCode& status) const {
-    // TODO: This is a placeholder implementation.
+
+    MicroProps microsFirst;
+    MicroProps microsSecond;
 
     UFormattedNumberData r1;
     r1.quantity = results->quantity1;
index 1b3a685771828595e527db0fab15aebd4c8d5823..be109e48175f355d3b3d403ae0ee2ec37a60f2c9 100644 (file)
 // Helpful in toString methods and elsewhere.
 #define UNISTR_FROM_STRING_EXPLICIT
 
-#include "numrange_types.h"
+#include "numrange_impl.h"
 
 using namespace icu;
 using namespace icu::number;
 using namespace icu::number::impl;
 
+namespace {
 
+// Helper function for 2-dimensional switch statement
+constexpr int8_t identity2d(UNumberIdentityFallback a, UNumberRangeIdentityResult b) {
+    return static_cast<int8_t>(a) | (static_cast<int8_t>(b) << 4);
+}
 
+} // namespace
+
+void NumberRangeFormatterImpl::format(UFormattedNumberRangeData& data, bool equalBeforeRounding, UErrorCode& status) const {
+    if (U_FAILURE(status)) {
+        return;
+    }
+
+    // Identity case 1: equal before rounding
+    if (equalBeforeRounding) {
+    }
+
+    MicroProps micros1;
+    MicroProps micros2;
+    formatterImpl1.preProcess(data.quantity1, micros1, status);
+    formatterImpl2.preProcess(data.quantity2, micros2, status);
+    if (U_FAILURE(status)) {
+        return;
+    }
+
+    // Check for identity
+    if (equalBeforeRounding) {
+        data.identityResult = UNUM_IDENTITY_RESULT_EQUAL_BEFORE_ROUNDING;
+    } else if (data.quantity1 == data.quantity2) {
+        data.identityResult = UNUM_IDENTITY_RESULT_EQUAL_AFTER_ROUNDING;
+    } else {
+        data.identityResult = UNUM_IDENTITY_RESULT_NOT_EQUAL;
+    }
+
+    switch (identity2d(fIdentityFallback, data.identityResult)) {
+        case identity2d(UNUM_IDENTITY_FALLBACK_RANGE,
+                        UNUM_IDENTITY_RESULT_NOT_EQUAL):
+        case identity2d(UNUM_IDENTITY_FALLBACK_RANGE,
+                        UNUM_IDENTITY_RESULT_EQUAL_AFTER_ROUNDING):
+        case identity2d(UNUM_IDENTITY_FALLBACK_RANGE,
+                        UNUM_IDENTITY_RESULT_EQUAL_BEFORE_ROUNDING):
+        case identity2d(UNUM_IDENTITY_FALLBACK_APPROXIMATELY,
+                        UNUM_IDENTITY_RESULT_NOT_EQUAL):
+        case identity2d(UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE,
+                        UNUM_IDENTITY_RESULT_NOT_EQUAL):
+        case identity2d(UNUM_IDENTITY_FALLBACK_SINGLE_VALUE,
+                        UNUM_IDENTITY_RESULT_NOT_EQUAL):
+            formatRange(data, micros1, micros2, status);
+            break;
+
+        case identity2d(UNUM_IDENTITY_FALLBACK_APPROXIMATELY,
+                        UNUM_IDENTITY_RESULT_EQUAL_AFTER_ROUNDING):
+        case identity2d(UNUM_IDENTITY_FALLBACK_APPROXIMATELY,
+                        UNUM_IDENTITY_RESULT_EQUAL_BEFORE_ROUNDING):
+        case identity2d(UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE,
+                        UNUM_IDENTITY_RESULT_EQUAL_AFTER_ROUNDING):
+            formatApproximately(data, micros1, micros2, status);
+            break;
+
+        case identity2d(UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE,
+                        UNUM_IDENTITY_RESULT_EQUAL_BEFORE_ROUNDING):
+        case identity2d(UNUM_IDENTITY_FALLBACK_SINGLE_VALUE,
+                        UNUM_IDENTITY_RESULT_EQUAL_AFTER_ROUNDING):
+        case identity2d(UNUM_IDENTITY_FALLBACK_SINGLE_VALUE,
+                        UNUM_IDENTITY_RESULT_EQUAL_BEFORE_ROUNDING):
+            formatSingleValue(data, micros1, micros2, status);
+            break;
+
+        default:
+            U_ASSERT(false);
+            break;
+    }
+}
+
+
+void NumberRangeFormatterImpl::formatSingleValue(UFormattedNumberRangeData& data,
+                                                 MicroProps& micros1, MicroProps& micros2,
+                                                 UErrorCode& status) const {
+    if (fSameFormatters) {
+        formatterImpl1.format(data.quantity1, data.string, status);
+    } else {
+        formatRange(data, micros1, micros2, status);
+    }
+}
+
+
+void NumberRangeFormatterImpl::formatApproximately (UFormattedNumberRangeData& data,
+                                                    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);
+    } else {
+        formatRange(data, micros1, micros2, status);
+    }
+}
+
+
+void NumberRangeFormatterImpl::formatRange(UFormattedNumberRangeData& data,
+                                           MicroProps& micros1, MicroProps& micros2,
+                                           UErrorCode& status) const {
+
+    // modInner is always notation (scientific); collapsable in ALL.
+    // modOuter is always units; collapsable in ALL, AUTO, and UNIT.
+    // modMiddle could be either; collapsable in ALL and sometimes AUTO and UNIT.
+    // Never collapse an outer mod but not an inner mod.
+    bool collapseOuter, collapseMiddle, collapseInner;
+    switch (fCollapse) {
+        case UNUM_RANGE_COLLAPSE_ALL:
+        case UNUM_RANGE_COLLAPSE_AUTO:
+        case UNUM_RANGE_COLLAPSE_UNIT:
+        {
+            // OUTER MODIFIER
+            collapseOuter = *micros1.modOuter == *micros2.modOuter;
+
+            if (!collapseOuter) {
+                // Never collapse inner mods if outer mods are not collapsable
+                collapseMiddle = false;
+                collapseInner = false;
+                break;
+            }
+
+            // MIDDLE MODIFIER
+            collapseMiddle = *micros1.modMiddle == *micros2.modMiddle;
+
+            if (!collapseMiddle) {
+                // Never collapse inner mods if outer mods are not collapsable
+                collapseInner = false;
+                break;
+            }
+
+            // MIDDLE MODIFIER HEURISTICS
+            // (could disable collapsing of the middle modifier)
+            // The modifiers are equal by this point, so we can look at just one of them.
+            const Modifier* mm = micros1.modMiddle;
+            if (mm == nullptr) {
+                // pass
+            } else if (fCollapse == UNUM_RANGE_COLLAPSE_UNIT) {
+                // Only collapse if the modifier is a unit.
+                // TODO: Handle case where the modifier has both notation and unit (compact currency)?
+                if (!mm->containsField(UNUM_CURRENCY_FIELD) && !mm->containsField(UNUM_PERCENT_FIELD)) {
+                    collapseMiddle = false;
+                }
+            } else if (fCollapse == UNUM_RANGE_COLLAPSE_AUTO) {
+                // Heuristic as of ICU 63: collapse only if the modifier is exactly one code point.
+                if (mm->getCodePointCount(status) != 1) {
+                    collapseMiddle = false;
+                }
+            }
+
+            if (!collapseMiddle || fCollapse != UNUM_RANGE_COLLAPSE_ALL) {
+                collapseInner = false;
+                break;
+            }
+
+            // INNER MODIFIER
+            collapseInner = *micros1.modInner == *micros2.modInner;
+
+            // All done checking for collapsability.
+            break;
+        }
+
+        default:
+            collapseOuter = false;
+            collapseMiddle = false;
+            collapseInner = false;
+            break;
+    }
+
+    NumberStringBuilder& string = data.string;
+    int32_t length1 = 0;
+    int32_t lengthShared = 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
+
+    // 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_0, status);
+
+    // TODO: Support padding?
+
+    if (collapseInner) {
+        lengthShared += 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);
+    } 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);
+    }
+
+    if (collapseOuter) {
+        micros1.modOuter->apply(string, UPRV_INDEX_0, UPRV_INDEX_3, status);
+    } else {
+        micros1.modOuter->apply(string, UPRV_INDEX_0, UPRV_INDEX_1, status);
+        micros1.modOuter->apply(string, UPRV_INDEX_2, UPRV_INDEX_3, status);
+    }
+}
 
 
 
similarity index 60%
rename from icu4c/source/i18n/numrange_types.h
rename to icu4c/source/i18n/numrange_impl.h
index 2a2820cda2418c02f8e31fdcb6a4a5ca8e5fdbac..d05ea85003773788d417bc42389744169e1ccc58 100644 (file)
@@ -11,6 +11,7 @@
 #include "unicode/numberrangeformatter.h"
 #include "number_types.h"
 #include "number_decimalquantity.h"
+#include "number_formatimpl.h"
 #include "number_stringbuilder.h"
 
 U_NAMESPACE_BEGIN namespace number {
@@ -36,12 +37,38 @@ struct UFormattedNumberRangeData : public UMemory {
     DecimalQuantity quantity1;
     DecimalQuantity quantity2;
     NumberStringBuilder string;
-    UNumberRangeIdentityResult identityResult = UNUM_IDENTITY_RESULT_EQUAL_BEFORE_ROUNDING;
+    UNumberRangeIdentityResult identityResult = UNUM_IDENTITY_RESULT_COUNT;
 
     // No C conversion methods (no C API yet)
 };
 
 
+class NumberRangeFormatterImpl : public UMemory {
+  public:
+    void format(UFormattedNumberRangeData& data, bool equalBeforeRounding, UErrorCode& status) const;
+
+  private:
+    NumberFormatterImpl formatterImpl1;
+    NumberFormatterImpl formatterImpl2;
+    bool fSameFormatters;
+
+    UNumberRangeCollapse fCollapse;
+    UNumberRangeIdentityFallback fIdentityFallback;
+
+    void formatSingleValue(UFormattedNumberRangeData& data,
+                           MicroProps& micros1, MicroProps& micros2,
+                           UErrorCode& status) const;
+
+    void formatApproximately(UFormattedNumberRangeData& data,
+                             MicroProps& micros1, MicroProps& micros2,
+                             UErrorCode& status) const;
+
+    void formatRange(UFormattedNumberRangeData& data,
+                     MicroProps& micros1, MicroProps& micros2,
+                     UErrorCode& status) const;
+};
+
+
 } // namespace impl
 } // namespace number
 U_NAMESPACE_END
index 64572cbf8c27a4df391d96995e7700064470e7e2..57bc1f30345216c338b0f33cb712d5eaf04d6a15 100644 (file)
@@ -144,6 +144,7 @@ class MultiplierFormatHandler;
 class CurrencySymbols;
 class GeneratorHelpers;
 class DecNum;
+class NumberRangeFormatterImpl;
 
 } // namespace impl
 
@@ -2188,6 +2189,9 @@ class U_I18N_API UnlocalizedNumberFormatter
 
     // To give NumberFormatter::with() access to this class's constructor:
     friend class NumberFormatter;
+
+    // Give NumberRangeFormatter access to the MacroProps
+    friend class NumberRangeFormatterImpl;
 };
 
 /**
index c5e2c0eb9ac18e6b186a35b72261a1f3521c4b3e..208e533e22cb47a42346d6b479159d27ae5f7ede 100644 (file)
@@ -148,7 +148,16 @@ typedef enum UNumberRangeIdentityResult {
      * @draft ICU 63
      * @see NumberRangeFormatter
      */
-    UNUM_IDENTITY_RESULT_NOT_EQUAL
+    UNUM_IDENTITY_RESULT_NOT_EQUAL,
+
+#ifndef U_HIDE_INTERNAL_API
+    /**
+     * The number of entries in this enum.
+     * @internal
+     */
+    UNUM_IDENTITY_RESULT_COUNT
+#endif
+
 } UNumberRangeIdentityResult;
 
 U_NAMESPACE_BEGIN